Add UniFile
This commit is contained in:
1 parent
b6af27dcb0
commit
2b69aa10d5
5 files changed
+1012
No files matched your search
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.unifile;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
class DocumentFile extends UniFile {
|
||||
|
||||
private Context mContext;
|
||||
private Uri mUri;
|
||||
|
||||
DocumentFile(UniFile parent, Context context, Uri uri) {
|
||||
super(parent);
|
||||
mContext = context;
|
||||
mUri = uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniFile createFile(String displayName) {
|
||||
UniFile child = contains(displayName);
|
||||
|
||||
if (child != null) {
|
||||
if (child.isFile()) {
|
||||
return child;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
final Uri result = DocumentsContractApi21.createFile(mContext, mUri, "application/octet-stream", displayName);
|
||||
return (result != null) ? new DocumentFile(this, mContext, result) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniFile createDirectory(String displayName) {
|
||||
UniFile child = contains(displayName);
|
||||
|
||||
if (child != null) {
|
||||
if (child.isDirectory()) {
|
||||
return child;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
final Uri result = DocumentsContractApi21.createDirectory(mContext, mUri, displayName);
|
||||
return (result != null) ? new DocumentFile(this, mContext, result) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return DocumentsContractApi19.getName(mContext, mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return DocumentsContractApi19.getType(mContext, mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return DocumentsContractApi19.isDirectory(mContext, mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return DocumentsContractApi19.isFile(mContext, mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lastModified() {
|
||||
return DocumentsContractApi19.lastModified(mContext, mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return DocumentsContractApi19.length(mContext, mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
return DocumentsContractApi19.canRead(mContext, mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite() {
|
||||
return DocumentsContractApi19.canWrite(mContext, mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return DocumentsContractApi19.delete(mContext, mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return DocumentsContractApi19.exists(mContext, mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniFile contains(String displayName) {
|
||||
Uri childUri = DocumentsContractApi21.buildChildUri(mUri, displayName);
|
||||
return DocumentsContractApi19.exists(mContext, childUri) ?
|
||||
new DocumentFile(this, mContext, childUri) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniFile[] listFiles() {
|
||||
final Uri[] result = DocumentsContractApi21.listFiles(mContext, mUri);
|
||||
final UniFile[] resultFiles = new UniFile[result.length];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
resultFiles[i] = new DocumentFile(this, mContext, result[i]);
|
||||
}
|
||||
return resultFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameTo(String displayName) {
|
||||
final Uri result = DocumentsContractApi21.renameTo(mContext, mUri, displayName);
|
||||
if (result != null) {
|
||||
mUri = result;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
return mContext.getContentResolver().openOutputStream(mUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream(boolean append) throws IOException {
|
||||
return mContext.getContentResolver().openOutputStream(mUri, append ? "wa" : "w");
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
return mContext.getContentResolver().openInputStream(mUri);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.unifile;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
class DocumentsContractApi19 {
|
||||
private static final String TAG = "UniFile";
|
||||
|
||||
public static boolean isDocumentUri(Context context, Uri self) {
|
||||
return DocumentsContract.isDocumentUri(context, self);
|
||||
}
|
||||
|
||||
public static String getName(Context context, Uri self) {
|
||||
return queryForString(context, self, DocumentsContract.Document.COLUMN_DISPLAY_NAME, null);
|
||||
}
|
||||
|
||||
private static String getRawType(Context context, Uri self) {
|
||||
return queryForString(context, self, DocumentsContract.Document.COLUMN_MIME_TYPE, null);
|
||||
}
|
||||
|
||||
public static String getType(Context context, Uri self) {
|
||||
final String rawType = getRawType(context, self);
|
||||
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(rawType)) {
|
||||
return null;
|
||||
} else {
|
||||
return rawType;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDirectory(Context context, Uri self) {
|
||||
return DocumentsContract.Document.MIME_TYPE_DIR.equals(getRawType(context, self));
|
||||
}
|
||||
|
||||
public static boolean isFile(Context context, Uri self) {
|
||||
final String type = getRawType(context, self);
|
||||
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(type) || TextUtils.isEmpty(type)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static long lastModified(Context context, Uri self) {
|
||||
return queryForLong(context, self, DocumentsContract.Document.COLUMN_LAST_MODIFIED, 0);
|
||||
}
|
||||
|
||||
public static long length(Context context, Uri self) {
|
||||
return queryForLong(context, self, DocumentsContract.Document.COLUMN_SIZE, 0);
|
||||
}
|
||||
|
||||
public static boolean canRead(Context context, Uri self) {
|
||||
// Ignore if grant doesn't allow read
|
||||
if (context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore documents without MIME
|
||||
if (TextUtils.isEmpty(getRawType(context, self))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean canWrite(Context context, Uri self) {
|
||||
// Ignore if grant doesn't allow write
|
||||
if (context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String type = getRawType(context, self);
|
||||
final int flags = queryForInt(context, self, DocumentsContract.Document.COLUMN_FLAGS, 0);
|
||||
|
||||
// Ignore documents without MIME
|
||||
if (TextUtils.isEmpty(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deletable documents considered writable
|
||||
if ((flags & DocumentsContract.Document.FLAG_SUPPORTS_DELETE) != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(type)
|
||||
&& (flags & DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE) != 0) {
|
||||
// Directories that allow create considered writable
|
||||
return true;
|
||||
} else if (!TextUtils.isEmpty(type)
|
||||
&& (flags & DocumentsContract.Document.FLAG_SUPPORTS_WRITE) != 0) {
|
||||
// Writable normal files considered writable
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean delete(Context context, Uri self) {
|
||||
return DocumentsContract.deleteDocument(context.getContentResolver(), self);
|
||||
}
|
||||
|
||||
public static boolean exists(Context context, Uri self) {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = resolver.query(self, new String[] {
|
||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null);
|
||||
return c.getCount() > 0;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed query: " + e);
|
||||
return false;
|
||||
} finally {
|
||||
closeQuietly(c);
|
||||
}
|
||||
}
|
||||
|
||||
private static String queryForString(Context context, Uri self, String column,
|
||||
String defaultValue) {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = resolver.query(self, new String[] { column }, null, null, null);
|
||||
if (c.moveToFirst() && !c.isNull(0)) {
|
||||
return c.getString(0);
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed query: " + e);
|
||||
return defaultValue;
|
||||
} finally {
|
||||
closeQuietly(c);
|
||||
}
|
||||
}
|
||||
|
||||
private static int queryForInt(Context context, Uri self, String column,
|
||||
int defaultValue) {
|
||||
return (int) queryForLong(context, self, column, defaultValue);
|
||||
}
|
||||
|
||||
private static long queryForLong(Context context, Uri self, String column,
|
||||
long defaultValue) {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = resolver.query(self, new String[] { column }, null, null, null);
|
||||
if (c.moveToFirst() && !c.isNull(0)) {
|
||||
return c.getLong(0);
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed query: " + e);
|
||||
return defaultValue;
|
||||
} finally {
|
||||
closeQuietly(c);
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeQuietly(AutoCloseable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (RuntimeException rethrown) {
|
||||
throw rethrown;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.unifile;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
class DocumentsContractApi21 {
|
||||
private static final String TAG = "UniFile";
|
||||
|
||||
private static final String PATH_DOCUMENT = "document";
|
||||
private static final String PATH_TREE = "tree";
|
||||
|
||||
public static Uri createFile(Context context, Uri self, String mimeType,
|
||||
String displayName) {
|
||||
return DocumentsContract.createDocument(context.getContentResolver(), self, mimeType,
|
||||
displayName);
|
||||
}
|
||||
|
||||
public static Uri createDirectory(Context context, Uri self, String displayName) {
|
||||
return createFile(context, self, DocumentsContract.Document.MIME_TYPE_DIR, displayName);
|
||||
}
|
||||
|
||||
public static Uri prepareTreeUri(Uri treeUri) {
|
||||
return DocumentsContract.buildDocumentUriUsingTree(treeUri,
|
||||
DocumentsContract.getTreeDocumentId(treeUri));
|
||||
}
|
||||
|
||||
public static String getTreeDocumentPath(Uri documentUri) {
|
||||
final List<String> paths = documentUri.getPathSegments();
|
||||
if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2))) {
|
||||
return paths.get(3);
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid URI: " + documentUri);
|
||||
}
|
||||
|
||||
public static Uri buildChildUri(Uri uri, String displayName) {
|
||||
return DocumentsContract.buildDocumentUriUsingTree(uri,
|
||||
getTreeDocumentPath(uri) + "/" + displayName);
|
||||
}
|
||||
|
||||
public static Uri[] listFiles(Context context, Uri self) {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(self,
|
||||
DocumentsContract.getDocumentId(self));
|
||||
final ArrayList<Uri> results = new ArrayList<Uri>();
|
||||
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = resolver.query(childrenUri, new String[] {
|
||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null);
|
||||
while (c.moveToNext()) {
|
||||
final String documentId = c.getString(0);
|
||||
final Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(self,
|
||||
documentId);
|
||||
results.add(documentUri);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed query: " + e);
|
||||
} finally {
|
||||
closeQuietly(c);
|
||||
}
|
||||
|
||||
return results.toArray(new Uri[results.size()]);
|
||||
}
|
||||
|
||||
public static Uri renameTo(Context context, Uri self, String displayName) {
|
||||
return DocumentsContract.renameDocument(context.getContentResolver(), self, displayName);
|
||||
}
|
||||
|
||||
private static void closeQuietly(AutoCloseable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (RuntimeException rethrown) {
|
||||
throw rethrown;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.unifile;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
class RawFile extends UniFile {
|
||||
private File mFile;
|
||||
|
||||
RawFile(UniFile parent, File file) {
|
||||
super(parent);
|
||||
mFile = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniFile createFile(String displayName) {
|
||||
final File target = new File(mFile, displayName);
|
||||
try {
|
||||
target.createNewFile();
|
||||
return new RawFile(this, target);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to createFile: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniFile createDirectory(String displayName) {
|
||||
final File target = new File(mFile, displayName);
|
||||
if (target.isDirectory() || target.mkdir()) {
|
||||
return new RawFile(this, target);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getUri() {
|
||||
return Uri.fromFile(mFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return mFile.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
if (mFile.isDirectory()) {
|
||||
return null;
|
||||
} else {
|
||||
return getTypeForName(mFile.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return mFile.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return mFile.isFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lastModified() {
|
||||
return mFile.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return mFile.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
return mFile.canRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite() {
|
||||
return mFile.canWrite();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
deleteContents(mFile);
|
||||
return mFile.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return mFile.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniFile contains(String displayName) {
|
||||
final File child = new File(mFile, displayName);
|
||||
return child.exists() ? new RawFile(this, child) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniFile[] listFiles() {
|
||||
final ArrayList<UniFile> results = new ArrayList<UniFile>();
|
||||
final File[] files = mFile.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
results.add(new RawFile(this, file));
|
||||
}
|
||||
}
|
||||
return results.toArray(new UniFile[results.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameTo(String displayName) {
|
||||
final File target = new File(mFile.getParentFile(), displayName);
|
||||
if (mFile.renameTo(target)) {
|
||||
mFile = target;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
return new FileOutputStream(mFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream(boolean append) throws IOException {
|
||||
return new FileOutputStream(mFile, append);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
return new FileInputStream(mFile);
|
||||
}
|
||||
|
||||
private static String getTypeForName(String name) {
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = name.substring(lastDot + 1).toLowerCase();
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) {
|
||||
return mime;
|
||||
}
|
||||
}
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
private static boolean deleteContents(File dir) {
|
||||
File[] files = dir.listFiles();
|
||||
boolean success = true;
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
success &= deleteContents(file);
|
||||
}
|
||||
if (!file.delete()) {
|
||||
Log.w(TAG, "Failed to delete " + file);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.unifile;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Representation of a document backed by either a
|
||||
* {@link android.provider.DocumentsProvider} or a raw file on disk. This is a
|
||||
* utility class designed to emulate the traditional {@link File} interface. It
|
||||
* offers a simplified view of a tree of documents, but it has substantial
|
||||
* overhead. For optimal performance and a richer feature set, use the
|
||||
* {@link android.provider.DocumentsContract} methods and constants directly.
|
||||
* <p>
|
||||
* There are several differences between documents and traditional files:
|
||||
* <ul>
|
||||
* <li>Documents express their display name and MIME type as separate fields,
|
||||
* instead of relying on file extensions. Some documents providers may still
|
||||
* choose to append extensions to their display names, but that's an
|
||||
* implementation detail.
|
||||
* <li>A single document may appear as the child of multiple directories, so it
|
||||
* doesn't inherently know who its parent is. That is, documents don't have a
|
||||
* strong notion of path. You can easily traverse a tree of documents from
|
||||
* parent to child, but not from child to parent.
|
||||
* <li>Each document has a unique identifier within that provider. This
|
||||
* identifier is an <em>opaque</em> implementation detail of the provider, and
|
||||
* as such it must not be parsed.
|
||||
* </ul>
|
||||
* <p>
|
||||
* Before using this class, first consider if you really need access to an
|
||||
* entire subtree of documents. The principle of least privilege dictates that
|
||||
* you should only ask for access to documents you really need. If you only need
|
||||
* the user to pick a single file, use {@link Intent#ACTION_OPEN_DOCUMENT} or
|
||||
* {@link Intent#ACTION_GET_CONTENT}. If you want to let the user pick multiple
|
||||
* files, add {@link Intent#EXTRA_ALLOW_MULTIPLE}. If you only need the user to
|
||||
* save a single file, use {@link Intent#ACTION_CREATE_DOCUMENT}.
|
||||
* <p>
|
||||
* If you really do need full access to an entire subtree of documents, start by
|
||||
* launching {@link Intent#ACTION_OPEN_DOCUMENT_TREE} to let the user pick a
|
||||
* directory. Then pass the resulting {@link Intent#getData()} into
|
||||
* {@link #fromTreeUri(Context, Uri)} to start working with the user selected
|
||||
* tree.
|
||||
* <p>
|
||||
* As you navigate the tree of UniFile instances, you can always use
|
||||
* {@link #getUri()} to obtain the Uri representing the underlying document for
|
||||
* that object, for use with {@link ContentResolver#openInputStream(Uri)}, etc.
|
||||
* <p>
|
||||
* To simplify your code on devices running
|
||||
* {@link Build.VERSION_CODES#KITKAT} or earlier, you can use
|
||||
* {@link #fromFile(File)} which emulates the behavior of a
|
||||
* {@link android.provider.DocumentsProvider}.
|
||||
*
|
||||
* @see android.provider.DocumentsProvider
|
||||
* @see android.provider.DocumentsContract
|
||||
*/
|
||||
public abstract class UniFile {
|
||||
static final String TAG = "UniFile";
|
||||
|
||||
private final UniFile mParent;
|
||||
|
||||
UniFile(UniFile parent) {
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link UniFile} representing the filesystem tree rooted at
|
||||
* the given {@link Uri}. This doesn't give you any additional access to the
|
||||
* underlying files beyond what your app already has.
|
||||
* <p>
|
||||
* {@link #getUri()} will return {@code file://} Uris for files explored
|
||||
* through this tree.
|
||||
*/
|
||||
public static UniFile fromFile(File file) {
|
||||
return new RawFile(null, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link UniFile} representing the document tree rooted at
|
||||
* the given {@link Uri}. This is only useful on devices running
|
||||
* {@link Build.VERSION_CODES#LOLLIPOP} or later, and will return
|
||||
* {@code null} when called on earlier platform versions.
|
||||
*
|
||||
* @param treeUri the {@link Intent#getData()} from a successful
|
||||
* {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request.
|
||||
*/
|
||||
public static UniFile fromTreeUri(Context context, Uri treeUri) {
|
||||
final int version = Build.VERSION.SDK_INT;
|
||||
if (version >= 21) {
|
||||
return new DocumentFile(null, context,
|
||||
DocumentsContractApi21.prepareTreeUri(treeUri));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if given Uri is backed by a
|
||||
* {@link android.provider.DocumentsProvider}.
|
||||
*/
|
||||
public static boolean isDocumentUri(Context context, Uri uri) {
|
||||
final int version = Build.VERSION.SDK_INT;
|
||||
if (version >= 19) {
|
||||
return DocumentsContractApi19.isDocumentUri(context, uri);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new document as a direct child of this directory.
|
||||
*
|
||||
* @param displayName name of new document, without any file extension
|
||||
* appended; the underlying provider may choose to append the
|
||||
* extension
|
||||
* @return file representing newly created document, or null if failed
|
||||
* @see android.provider.DocumentsContract#createDocument(ContentResolver,
|
||||
* Uri, String, String)
|
||||
*/
|
||||
public abstract UniFile createFile(String displayName);
|
||||
|
||||
/**
|
||||
* Create a new directory as a direct child of this directory.
|
||||
*
|
||||
* @param displayName name of new directory
|
||||
* @return file representing newly created directory, or null if failed
|
||||
* @see android.provider.DocumentsContract#createDocument(ContentResolver,
|
||||
* Uri, String, String)
|
||||
*/
|
||||
public abstract UniFile createDirectory(String displayName);
|
||||
|
||||
/**
|
||||
* Return a Uri for the underlying document represented by this file. This
|
||||
* can be used with other platform APIs to manipulate or share the
|
||||
* underlying content. You can use {@link #isDocumentUri(Context, Uri)} to
|
||||
* test if the returned Uri is backed by a
|
||||
* {@link android.provider.DocumentsProvider}.
|
||||
*
|
||||
* @see Intent#setData(Uri)
|
||||
* @see Intent#setClipData(android.content.ClipData)
|
||||
* @see ContentResolver#openInputStream(Uri)
|
||||
* @see ContentResolver#openOutputStream(Uri)
|
||||
* @see ContentResolver#openFileDescriptor(Uri, String)
|
||||
*/
|
||||
public abstract Uri getUri();
|
||||
|
||||
/**
|
||||
* Return the display name of this document.
|
||||
*
|
||||
* @see android.provider.DocumentsContract.Document#COLUMN_DISPLAY_NAME
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
/**
|
||||
* Return the MIME type of this document.
|
||||
*
|
||||
* @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE
|
||||
*/
|
||||
public abstract String getType();
|
||||
|
||||
/**
|
||||
* Return the parent file of this document. Only defined inside of the
|
||||
* user-selected tree; you can never escape above the top of the tree.
|
||||
* <p>
|
||||
* The underlying {@link android.provider.DocumentsProvider} only defines a
|
||||
* forward mapping from parent to child, so the reverse mapping of child to
|
||||
* parent offered here is purely a convenience method, and it may be
|
||||
* incorrect if the underlying tree structure changes.
|
||||
*/
|
||||
public UniFile getParentFile() {
|
||||
return mParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this file represents a <em>directory</em>.
|
||||
*
|
||||
* @return {@code true} if this file is a directory, {@code false}
|
||||
* otherwise.
|
||||
* @see android.provider.DocumentsContract.Document#MIME_TYPE_DIR
|
||||
*/
|
||||
public abstract boolean isDirectory();
|
||||
|
||||
/**
|
||||
* Indicates if this file represents a <em>file</em>.
|
||||
*
|
||||
* @return {@code true} if this file is a file, {@code false} otherwise.
|
||||
* @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE
|
||||
*/
|
||||
public abstract boolean isFile();
|
||||
|
||||
/**
|
||||
* Returns the time when this file was last modified, measured in
|
||||
* milliseconds since January 1st, 1970, midnight. Returns 0 if the file
|
||||
* does not exist, or if the modified time is unknown.
|
||||
*
|
||||
* @return the time when this file was last modified.
|
||||
* @see android.provider.DocumentsContract.Document#COLUMN_LAST_MODIFIED
|
||||
*/
|
||||
public abstract long lastModified();
|
||||
|
||||
/**
|
||||
* Returns the length of this file in bytes. Returns 0 if the file does not
|
||||
* exist, or if the length is unknown. The result for a directory is not
|
||||
* defined.
|
||||
*
|
||||
* @return the number of bytes in this file.
|
||||
* @see android.provider.DocumentsContract.Document#COLUMN_SIZE
|
||||
*/
|
||||
public abstract long length();
|
||||
|
||||
/**
|
||||
* Indicates whether the current context is allowed to read from this file.
|
||||
*
|
||||
* @return {@code true} if this file can be read, {@code false} otherwise.
|
||||
*/
|
||||
public abstract boolean canRead();
|
||||
|
||||
/**
|
||||
* Indicates whether the current context is allowed to write to this file.
|
||||
*
|
||||
* @return {@code true} if this file can be written, {@code false}
|
||||
* otherwise.
|
||||
* @see android.provider.DocumentsContract.Document#COLUMN_FLAGS
|
||||
* @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE
|
||||
* @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE
|
||||
* @see android.provider.DocumentsContract.Document#FLAG_DIR_SUPPORTS_CREATE
|
||||
*/
|
||||
public abstract boolean canWrite();
|
||||
|
||||
/**
|
||||
* Deletes this file.
|
||||
* <p>
|
||||
* Note that this method does <i>not</i> throw {@code IOException} on
|
||||
* failure. Callers must check the return value.
|
||||
*
|
||||
* @return {@code true} if this file was deleted, {@code false} otherwise.
|
||||
* @see android.provider.DocumentsContract#deleteDocument(ContentResolver,
|
||||
* Uri)
|
||||
*/
|
||||
public abstract boolean delete();
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether this file can be found.
|
||||
*
|
||||
* @return {@code true} if this file exists, {@code false} otherwise.
|
||||
*/
|
||||
public abstract boolean exists();
|
||||
|
||||
/**
|
||||
* Returns the file contained in the directory represented by this
|
||||
* file.
|
||||
*
|
||||
* @return the file, or null if it does not exist.
|
||||
*/
|
||||
public abstract UniFile contains(String displayName);
|
||||
|
||||
/**
|
||||
* Returns an array of files contained in the directory represented by this
|
||||
* file.
|
||||
*
|
||||
* @return an array of files or {@code null}.
|
||||
* @see android.provider.DocumentsContract#buildChildDocumentsUriUsingTree(Uri,
|
||||
* String)
|
||||
*/
|
||||
public abstract UniFile[] listFiles();
|
||||
|
||||
/**
|
||||
* Search through {@link #listFiles()} for the first document matching the
|
||||
* given display name. Returns {@code null} when no matching document is
|
||||
* found.
|
||||
*/
|
||||
public UniFile findFile(String displayName) {
|
||||
for (UniFile doc : listFiles()) {
|
||||
if (displayName.equals(doc.getName())) {
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames this file to {@code displayName}.
|
||||
* <p>
|
||||
* Note that this method does <i>not</i> throw {@code IOException} on
|
||||
* failure. Callers must check the return value.
|
||||
* <p>
|
||||
* Some providers may need to create a new document to reflect the rename,
|
||||
* potentially with a different MIME type, so {@link #getUri()} and
|
||||
* {@link #getType()} may change to reflect the rename.
|
||||
* <p>
|
||||
* When renaming a directory, children previously enumerated through
|
||||
* {@link #listFiles()} may no longer be valid.
|
||||
*
|
||||
* @param displayName the new display name.
|
||||
* @return true on success.
|
||||
* @see android.provider.DocumentsContract#renameDocument(ContentResolver,
|
||||
* Uri, String)
|
||||
*/
|
||||
public abstract boolean renameTo(String displayName);
|
||||
|
||||
/**
|
||||
* Open a stream on to the content associated with the file, clean it if it exists
|
||||
*
|
||||
* @return the {@link OutputStream}
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract OutputStream openOutputStream() throws IOException;
|
||||
|
||||
/**
|
||||
* Open a stream on to the content associated with the file
|
||||
*
|
||||
* @param append {@code true} for do not clean it if it exists
|
||||
* @return the {@link OutputStream}
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract OutputStream openOutputStream(boolean append) throws IOException;
|
||||
|
||||
/**
|
||||
* Open a stream on to the content associated with the file
|
||||
*
|
||||
* @return the {@link InputStream}
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract InputStream openInputStream() throws IOException;
|
||||
}
|
||||
Reference in new issue
Block a user