Files
EhViewer/app/src/main/java/com/hippo/unifile/UniFile.java
T

422 lines
14 KiB
Java

/*
* 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 android.os.ParcelFileDescriptor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.image.Image;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* In Android files can be accessed via {@link java.io.File} and {@link android.net.Uri}.
* The UniFile is designed to emulate File interface for both File and Uri.
*/
public abstract class UniFile {
private static List<UriHandler> sUriHandlerArray;
private final UniFile mParent;
UniFile(UniFile parent) {
mParent = parent;
}
/**
* Add a UriHandler to get UniFile from uri
*/
public static void addUriHandler(@NonNull UriHandler handler) {
if (sUriHandlerArray == null) {
sUriHandlerArray = new ArrayList<>();
}
sUriHandlerArray.add(handler);
}
/**
* Remove the UriHandler added before
*/
public static void removeUriHandler(UriHandler handler) {
if (sUriHandlerArray != null) {
sUriHandlerArray.remove(handler);
}
}
/**
* Create a {@link UniFile} representing the given {@link File}.
*
* @param file the file to wrap
* @return the {@link UniFile} representing the given {@link File}.
*/
@Nullable
public static UniFile fromFile(@Nullable File file) {
return file != null ? new RawFile(null, file) : null;
}
/**
* Create a {@link UniFile} representing the single document at the
* given {@link Uri}. This is only useful on devices running
* {@link android.os.Build.VERSION_CODES#KITKAT} or later, and will return
* {@code null} when called on earlier platform versions.
*
* @param singleUri the {@link Intent#getData()} from a successful
* {@link Intent#ACTION_OPEN_DOCUMENT} or
* {@link Intent#ACTION_CREATE_DOCUMENT} request.
* @return the {@link UniFile} representing the given {@link Uri}.
*/
public static UniFile fromSingleUri(Context context, Uri singleUri) {
final int version = Build.VERSION.SDK_INT;
if (version >= 19) {
return new SingleDocumentFile(null, context, singleUri);
} else {
return null;
}
}
/**
* 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.
* @return the {@link UniFile} representing the given {@link Uri}.
*/
public static UniFile fromTreeUri(Context context, Uri treeUri) {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
return new TreeDocumentFile(null, context,
DocumentsContractApi21.prepareTreeUri(treeUri));
} else {
return null;
}
}
/**
* Create a {@link UniFile} representing the media file rooted at
* the given {@link Uri}.
*
* @param mediaUri the media uri to wrap
* @return the {@link UniFile} representing the given {@link Uri}.
*/
public static UniFile fromMediaUri(Context context, Uri mediaUri) {
return new MediaFile(context, mediaUri);
}
/**
* Create a {@link UniFile} representing the given {@link Uri}.
*/
public static UniFile fromUri(Context context, Uri uri) {
if (context == null || uri == null) {
return null;
}
// Custom handler
if (sUriHandlerArray != null) {
for (int i = 0, size = sUriHandlerArray.size(); i < size; i++) {
UniFile file = sUriHandlerArray.get(i).fromUri(context, uri);
if (file != null) {
return file;
}
}
}
if (isFileUri(uri)) {
return fromFile(new File(uri.getPath()));
} else if (isDocumentUri(context, uri)) {
if (isTreeUri(uri)) {
return fromTreeUri(context, uri);
} else {
return fromSingleUri(context, uri);
}
} else if (MediaFile.isMediaUri(context, uri)) {
return new MediaFile(context, uri);
} else {
return null;
}
}
/**
* Test if given Uri is FileUri
*/
public static boolean isFileUri(Uri uri) {
return uri != null && ContentResolver.SCHEME_FILE.equals(uri.getScheme());
}
/**
* 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;
}
}
/**
* Test if given Uri is TreeUri
*/
public static boolean isTreeUri(Uri uri) {
if (uri == null) {
return false;
}
final List<String> paths = uri.getPathSegments();
return (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
&& paths.size() >= 2 && "tree".equals(paths.get(0)));
}
/**
* Create a new file as a direct child of this directory.
*
* @param displayName name of new file
* @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 #isTreeUri(Uri)} to
* test if the returned Uri is backed by a
* {@link android.provider.DocumentsProvider}.
*
* @return uri of the file
* @see Intent#setData(Uri)
* @see Intent#setClipData(android.content.ClipData)
* @see ContentResolver#openInputStream(Uri)
* @see ContentResolver#openOutputStream(Uri)
* @see ContentResolver#openFileDescriptor(Uri, String)
*/
@NonNull
public abstract Uri getUri();
/**
* Return the display name of this file.
*
* @return name of the file, or null if failed
* @see android.provider.DocumentsContract.Document#COLUMN_DISPLAY_NAME
*/
@Nullable
public abstract String getName();
/**
* Return the MIME type of this file.
*
* @return MIME type of the file, or null if failed
* @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE
*/
@Nullable
public abstract String getType();
/**
* Return the parent file of this file. 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.
*
* @return parent of the file, or null if it is the top of the file tree
*/
@Nullable
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 -1 if the file
* does not exist, or if the modified time is unknown.
*
* @return the time when this file was last modified, <code>-1L</code> if can't get it
* @see android.provider.DocumentsContract.Document#COLUMN_LAST_MODIFIED
*/
public abstract long lastModified();
/**
* Returns the length of this file in bytes. Returns -1 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, <code>-1L</code> if can't get it
* @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();
/**
* It works like mkdirs, but it will return true if the UniFile is directory
*
* @return {@code true} if the directory was created
* or if the directory already existed.
*/
public abstract boolean ensureDir();
/**
* Make sure the UniFile is file
*
* @return {@code true} if the file can be created
* or if the file already existed.
*/
public abstract boolean ensureFile();
/**
* Get child file of this directory, the child might not exist.
*
* @return the child file, {@code null} if not supported
*/
@Nullable
public abstract UniFile subFile(String displayName);
/**
* 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 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)
*/
@Nullable
public abstract UniFile[] listFiles();
/**
* Gets a list of the files in the directory represented by this file. This
* list is then filtered through a FilenameFilter and the names of files
* with matching names are returned as an array of strings.
*
* @param filter the filter to match names against, may be {@code null}.
* @return an array of files or {@code null}.
*/
@Nullable
public abstract UniFile[] listFiles(FilenameFilter filter);
/**
* Test there is a file with the display name in the directory.
*
* @return the file if found it, or {@code null}.
*/
@Nullable
public abstract UniFile findFile(String displayName);
/**
* 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 file 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);
@NonNull
public abstract Image.CloseableSource getImageSource();
@NonNull
public abstract ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException;
}