Convert all unifile classes to kotlin

This commit is contained in:
FooIbar authored and tarsin committed 2023-06-22 15:40:20 +08:00
1 parent 8cb4045589
commit 3c64486c3b
14 files changed
+873 -1122

No files matched your search

@@ -428,7 +428,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
@Synchronized
private fun writeSpiderInfoToLocal() {
if (!isReady) return
mSpiderDen.downloadDir?.run { createFile(SPIDER_INFO_FILENAME).also { mSpiderInfo.write(it) } }
mSpiderDen.downloadDir?.run { createFile(SPIDER_INFO_FILENAME)?.also { mSpiderInfo.write(it) } }
mSpiderInfo.saveToCache()
}
@@ -95,7 +95,7 @@ class ImageSearchLayout @JvmOverloads constructor(
fun formatListUrlBuilder(lub: ListUrlBuilder) {
if (path.isBlank()) throw EhException(context.getString(R.string.select_image_first))
val uri = Uri.parse(path)
val src = UniFile.fromUri(context, uri).imageSource
val src = UniFile.fromUri(context, uri)?.imageSource ?: return
val temp = AppConfig.createTempFile() ?: return
val bitmap = ImageDecoder.decodeBitmap(src, Image.imageSearchDecoderSampleListener)
temp.outputStream().use { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, it) }
@@ -13,80 +13,64 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.content.Context
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.ParcelFileDescriptor
import java.io.IOException
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.ImageDecoder;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import androidx.annotation.NonNull;
import java.io.IOException;
final class Contracts {
private Contracts() {
}
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 != null && c.moveToFirst() && !c.isNull(0)) {
return c.getString(0);
} else {
return defaultValue;
internal object Contracts {
fun queryForString(
context: Context,
self: Uri,
column: String,
defaultValue: String?,
): String? {
return runCatching {
context.contentResolver.query(self, arrayOf(column), null, null, null).use {
if (it != null && it.moveToFirst() && !it.isNull(0)) {
it.getString(0)
} else {
defaultValue
}
}
} catch (Throwable e) {
Utils.throwIfFatal(e);
return defaultValue;
} finally {
Utils.closeQuietly(c);
}.getOrElse {
Utils.throwIfFatal(it)
defaultValue
}
}
static int queryForInt(Context context, Uri self, String column,
int defaultValue) {
return (int) queryForLong(context, self, column, defaultValue);
fun queryForInt(context: Context, self: Uri, column: String?, defaultValue: Int): Int {
return queryForLong(context, self, column, defaultValue.toLong()).toInt()
}
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 != null && c.moveToFirst() && !c.isNull(0)) {
return c.getLong(0);
} else {
return defaultValue;
fun queryForLong(context: Context, self: Uri, column: String?, defaultValue: Long): Long {
return runCatching {
context.contentResolver.query(self, arrayOf(column), null, null, null).use {
if (it != null && it.moveToFirst() && !it.isNull(0)) {
it.getLong(0)
} else {
defaultValue
}
}
} catch (Throwable e) {
Utils.throwIfFatal(e);
return defaultValue;
} finally {
Utils.closeQuietly(c);
}.getOrElse {
Utils.throwIfFatal(it)
defaultValue
}
}
@NonNull
static ParcelFileDescriptor openFileDescriptor(Context context, Uri uri, String mode) throws IOException {
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, mode);
if (pfd == null) {
throw new IOException("Can't open ParcelFileDescriptor");
}
return pfd;
fun openFileDescriptor(
context: Context,
uri: Uri?,
mode: String?,
): ParcelFileDescriptor {
return context.contentResolver.openFileDescriptor(uri!!, mode!!)
?: throw IOException("Can't open ParcelFileDescriptor")
}
@NonNull
static ImageDecoder.Source getImageSource(Context context, Uri uri) {
return ImageDecoder.createSource(context.getContentResolver(), uri);
fun getImageSource(context: Context, uri: Uri?): ImageDecoder.Source {
return ImageDecoder.createSource(context.contentResolver, uri!!)
}
}
@@ -13,123 +13,126 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.provider.DocumentsContract
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.provider.DocumentsContract;
import android.text.TextUtils;
final class DocumentsContractApi19 {
private DocumentsContractApi19() {
internal object DocumentsContractApi19 {
fun isDocumentUri(context: Context, self: Uri): Boolean {
return DocumentsContract.isDocumentUri(context, self)
}
public static boolean isDocumentUri(Context context, Uri self) {
return DocumentsContract.isDocumentUri(context, self);
fun getName(context: Context, self: Uri): String? {
return Contracts.queryForString(
context,
self,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
null,
)
}
public static String getName(Context context, Uri self) {
return Contracts.queryForString(context, self, DocumentsContract.Document.COLUMN_DISPLAY_NAME, null);
private fun getRawType(context: Context, self: Uri): String? {
return Contracts.queryForString(
context,
self,
DocumentsContract.Document.COLUMN_MIME_TYPE,
null,
)
}
private static String getRawType(Context context, Uri self) {
return Contracts.queryForString(context, self, DocumentsContract.Document.COLUMN_MIME_TYPE, null);
fun getType(context: Context, self: Uri): String? {
return getRawType(context, self).takeUnless { it == DocumentsContract.Document.MIME_TYPE_DIR }
}
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;
}
fun isDirectory(context: Context, self: Uri): Boolean {
return DocumentsContract.Document.MIME_TYPE_DIR == getRawType(context, self)
}
public static boolean isDirectory(Context context, Uri self) {
return DocumentsContract.Document.MIME_TYPE_DIR.equals(getRawType(context, self));
fun isFile(context: Context, self: Uri): Boolean {
val type = getRawType(context, self)
return !(DocumentsContract.Document.MIME_TYPE_DIR == type || type.isNullOrEmpty())
}
public static boolean isFile(Context context, Uri self) {
final String type = getRawType(context, self);
return !(DocumentsContract.Document.MIME_TYPE_DIR.equals(type) || TextUtils.isEmpty(type));
fun lastModified(context: Context, self: Uri): Long {
return Contracts.queryForLong(
context,
self,
DocumentsContract.Document.COLUMN_LAST_MODIFIED,
-1L,
)
}
public static long lastModified(Context context, Uri self) {
return Contracts.queryForLong(context, self, DocumentsContract.Document.COLUMN_LAST_MODIFIED, -1L);
fun length(context: Context, self: Uri): Long {
return Contracts.queryForLong(context, self, DocumentsContract.Document.COLUMN_SIZE, -1L)
}
public static long length(Context context, Uri self) {
return Contracts.queryForLong(context, self, DocumentsContract.Document.COLUMN_SIZE, -1L);
}
public static boolean canRead(Context context, Uri self) {
fun canRead(context: Context, self: Uri): Boolean {
// Ignore if grant doesn't allow read
if (context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_READ_URI_PERMISSION)
!= PackageManager.PERMISSION_GRANTED) {
return false;
return if (
context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_READ_URI_PERMISSION)
!= PackageManager.PERMISSION_GRANTED
) {
false
} else {
// Ignore documents without MIME
!getRawType(context, self).isNullOrEmpty()
}
// Ignore documents without MIME
return !TextUtils.isEmpty(getRawType(context, self));
}
public static boolean canWrite(Context context, Uri self) {
fun canWrite(context: Context, self: Uri): Boolean {
// Ignore if grant doesn't allow write
if (context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
!= PackageManager.PERMISSION_GRANTED) {
return false;
!= PackageManager.PERMISSION_GRANTED
) {
return false
}
final String type = getRawType(context, self);
final int flags = Contracts.queryForInt(context, self, DocumentsContract.Document.COLUMN_FLAGS, 0);
val type = getRawType(context, self)
val flags = Contracts.queryForInt(context, self, DocumentsContract.Document.COLUMN_FLAGS, 0)
// Ignore documents without MIME
if (TextUtils.isEmpty(type)) {
return false;
if (type.isNullOrEmpty()) {
return false
}
// Deletable documents considered writable
if ((flags & DocumentsContract.Document.FLAG_SUPPORTS_DELETE) != 0) {
return true;
if (flags and DocumentsContract.Document.FLAG_SUPPORTS_DELETE != 0) {
return true
}
// Writable normal files considered writable
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(type)
&& (flags & DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE) != 0) {
return if (DocumentsContract.Document.MIME_TYPE_DIR == type && flags and DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE != 0) {
// Directories that allow create considered writable
return true;
} else return !TextUtils.isEmpty(type)
&& (flags & DocumentsContract.Document.FLAG_SUPPORTS_WRITE) != 0;
}
public static boolean delete(Context context, Uri self) {
try {
return DocumentsContract.deleteDocument(context.getContentResolver(), self);
} catch (Throwable e) {
Utils.throwIfFatal(e);
return false;
true
} else {
flags and DocumentsContract.Document.FLAG_SUPPORTS_WRITE != 0
}
}
public static boolean exists(Context context, Uri self) {
final ContentResolver resolver = context.getContentResolver();
fun delete(context: Context, self: Uri): Boolean {
return try {
DocumentsContract.deleteDocument(context.contentResolver, self)
} catch (e: Throwable) {
Utils.throwIfFatal(e)
false
}
}
Cursor c = null;
try {
c = resolver.query(self, new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID}, null, null, null);
return null != c && c.getCount() > 0;
} catch (Throwable e) {
Utils.throwIfFatal(e);
return false;
} finally {
Utils.closeQuietly(c);
fun exists(context: Context, self: Uri): Boolean {
return runCatching {
context.contentResolver.query(
self,
arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID),
null,
null,
null,
).use { null != it && it.count > 0 }
}.getOrElse {
Utils.throwIfFatal(it)
false
}
}
}
@@ -13,91 +13,92 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.content.Context
import android.net.Uri
import android.provider.DocumentsContract
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.DocumentsContract;
import java.util.ArrayList;
import java.util.List;
final class DocumentsContractApi21 {
private static final String PATH_DOCUMENT = "document";
private static final String PATH_TREE = "tree";
private DocumentsContractApi21() {
}
public static Uri createFile(Context context, Uri self, String mimeType,
String displayName) {
try {
return DocumentsContract.createDocument(context.getContentResolver(), self, mimeType,
displayName);
} catch (Throwable e) {
Utils.throwIfFatal(e);
return null;
internal object DocumentsContractApi21 {
private const val PATH_DOCUMENT = "document"
private const val PATH_TREE = "tree"
fun createFile(context: Context, self: Uri, mimeType: String, displayName: String): Uri? {
return try {
DocumentsContract.createDocument(
context.contentResolver,
self,
mimeType,
displayName,
)
} catch (e: Throwable) {
Utils.throwIfFatal(e)
null
}
}
public static Uri createDirectory(Context context, Uri self, String displayName) {
return createFile(context, self, DocumentsContract.Document.MIME_TYPE_DIR, displayName);
fun createDirectory(context: Context, self: Uri, displayName: String): Uri? {
return createFile(context, self, DocumentsContract.Document.MIME_TYPE_DIR, displayName)
}
public static Uri prepareTreeUri(Uri treeUri) {
return DocumentsContract.buildDocumentUriUsingTree(treeUri,
DocumentsContract.getTreeDocumentId(treeUri));
fun prepareTreeUri(treeUri: Uri?): Uri {
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);
fun getTreeDocumentPath(documentUri: Uri): String {
val paths = documentUri.pathSegments
if (paths.size >= 4 && PATH_TREE == paths[0] && PATH_DOCUMENT == paths[2]) {
return paths[3]
}
throw new IllegalArgumentException("Invalid URI: " + documentUri);
throw IllegalArgumentException("Invalid URI: $documentUri")
}
public static Uri buildChildUri(Uri uri, String displayName) {
return DocumentsContract.buildDocumentUriUsingTree(uri,
getTreeDocumentPath(uri) + "/" + displayName);
fun buildChildUri(uri: Uri, displayName: String): Uri {
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<>();
Cursor c = null;
try {
c = resolver.query(childrenUri, new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID}, null, null, null);
if (null != c) {
while (c.moveToNext()) {
final String documentId = c.getString(0);
final Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(self,
documentId);
results.add(documentUri);
fun listFiles(context: Context, self: Uri): Array<Uri> {
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
self,
DocumentsContract.getDocumentId(self),
)
val results = ArrayList<Uri>()
runCatching {
context.contentResolver.query(
childrenUri,
arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID),
null,
null,
null,
).use {
if (null != it) {
while (it.moveToNext()) {
val documentId = it.getString(0)
val documentUri = DocumentsContract.buildDocumentUriUsingTree(
self,
documentId,
)
results.add(documentUri)
}
}
}
} catch (Throwable e) {
Utils.throwIfFatal(e);
} finally {
Utils.closeQuietly(c);
}.onFailure {
Utils.throwIfFatal(it)
}
return results.toArray(new Uri[0]);
return results.toTypedArray<Uri>()
}
public static Uri renameTo(Context context, Uri self, String displayName) {
try {
return DocumentsContract.renameDocument(context.getContentResolver(), self, displayName);
} catch (Throwable e) {
Utils.throwIfFatal(e);
return null;
fun renameTo(context: Context, self: Uri, displayName: String): Uri? {
return try {
DocumentsContract.renameDocument(context.contentResolver, self, displayName)
} catch (e: Throwable) {
Utils.throwIfFatal(e)
null
}
}
}
@@ -13,26 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile;
package com.hippo.unifile
/**
* An interface for filtering {@link UniFile} objects based on their names
* An interface for filtering [UniFile] objects based on their names
* or the directory they reside in.
*
* @see UniFile
* @see UniFile#listFiles(FilenameFilter)
*
* @see UniFile.listFiles
*/
public interface FilenameFilter {
interface FilenameFilter {
/**
* Indicates if a specific filename matches this filter.
*
* @param dir the directory in which the {@code filename} was found.
* @param filename the name of the file in {@code dir} to test.
* @return {@code true} if the filename matches the filter
* and can be included in the list, {@code false}
* @param dir the directory in which the `filename` was found.
* @param filename the name of the file in `dir` to test.
* @return `true` if the filename matches the filter
* and can be included in the list, `false`
* otherwise.
*/
boolean accept(UniFile dir, String filename);
fun accept(dir: UniFile?, filename: String?): Boolean
}
@@ -13,30 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.content.Context
import android.net.Uri
import android.provider.MediaStore
import android.content.Context;
import android.net.Uri;
import android.provider.MediaStore;
final class MediaContract {
private MediaContract() {
internal object MediaContract {
fun getName(context: Context, self: Uri): String? {
return Contracts.queryForString(context, self, MediaStore.MediaColumns.DISPLAY_NAME, null)
}
public static String getName(Context context, Uri self) {
return Contracts.queryForString(context, self, MediaStore.MediaColumns.DISPLAY_NAME, null);
fun getType(context: Context, self: Uri): String? {
return Contracts.queryForString(context, self, MediaStore.MediaColumns.MIME_TYPE, null)
}
public static String getType(Context context, Uri self) {
return Contracts.queryForString(context, self, MediaStore.MediaColumns.MIME_TYPE, null);
fun lastModified(context: Context, self: Uri): Long {
return Contracts.queryForLong(context, self, MediaStore.MediaColumns.DATE_MODIFIED, 0)
}
public static long lastModified(Context context, Uri self) {
return Contracts.queryForLong(context, self, MediaStore.MediaColumns.DATE_MODIFIED, 0);
}
public static long length(Context context, Uri self) {
return Contracts.queryForLong(context, self, MediaStore.MediaColumns.SIZE, 0);
fun length(context: Context, self: Uri): Long {
return Contracts.queryForLong(context, self, MediaStore.MediaColumns.SIZE, 0)
}
}
+62 -106
View File
@@ -13,150 +13,106 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.content.Context
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.ParcelFileDescriptor
import java.io.IOException
import android.content.Context;
import android.graphics.ImageDecoder;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
internal class MediaFile(context: Context, override val uri: Uri) : UniFile(null) {
private val mContext: Context
import androidx.annotation.NonNull;
import java.io.IOException;
class MediaFile extends UniFile {
private final Context mContext;
private final Uri mUri;
MediaFile(Context context, Uri uri) {
super(null);
mContext = context.getApplicationContext();
mUri = uri;
init {
mContext = context.applicationContext
}
static boolean isMediaUri(Context context, Uri uri) {
return null != MediaContract.getName(context, uri);
override fun createFile(displayName: String): UniFile? {
return null
}
@Override
public UniFile createFile(String displayName) {
return null;
override fun createDirectory(displayName: String): UniFile? {
return null
}
@Override
public UniFile createDirectory(String displayName) {
return null;
override val name: String?
get() = MediaContract.getName(mContext, uri)
override val type: String?
get() = MediaContract.getType(mContext, uri)
override val isDirectory: Boolean
get() = false
override val isFile: Boolean
get() = DocumentsContractApi19.isFile(mContext, uri)
override fun lastModified(): Long {
return MediaContract.lastModified(mContext, uri)
}
@Override
@NonNull
public Uri getUri() {
return mUri;
override fun length(): Long {
return MediaContract.length(mContext, uri)
}
@Override
public String getName() {
return MediaContract.getName(mContext, mUri);
override fun canRead(): Boolean {
return isFile
}
@Override
public String getType() {
return MediaContract.getType(mContext, mUri);
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public boolean isFile() {
return DocumentsContractApi19.isFile(mContext, mUri);
}
@Override
public long lastModified() {
return MediaContract.lastModified(mContext, mUri);
}
@Override
public long length() {
return MediaContract.length(mContext, mUri);
}
@Override
public boolean canRead() {
return isFile();
}
@Override
public boolean canWrite() {
override fun canWrite(): Boolean {
try {
var fd = openFileDescriptor("w");
fd.close();
} catch (IOException e) {
return false;
val fd = openFileDescriptor("w")
fd.close()
} catch (e: IOException) {
return false
}
return true;
return true
}
@Override
public boolean ensureDir() {
return false;
override fun ensureDir(): Boolean {
return false
}
@Override
public boolean ensureFile() {
return isFile();
override fun ensureFile(): Boolean {
return isFile
}
@Override
public UniFile subFile(String displayName) {
return null;
override fun subFile(displayName: String): UniFile? {
return null
}
@Override
public boolean delete() {
return false;
override fun delete(): Boolean {
return false
}
@Override
public boolean exists() {
return isFile();
override fun exists(): Boolean {
return isFile
}
@Override
public UniFile[] listFiles() {
return null;
override fun listFiles(): Array<UniFile>? {
return null
}
@Override
public UniFile[] listFiles(FilenameFilter filter) {
return null;
override fun listFiles(filter: FilenameFilter?): Array<UniFile>? {
return null
}
@Override
public UniFile findFile(String displayName) {
return null;
override fun findFile(displayName: String): UniFile? {
return null
}
@Override
public boolean renameTo(String displayName) {
return false;
override fun renameTo(displayName: String): Boolean {
return false
}
@NonNull
@Override
public ImageDecoder.Source getImageSource() {
return Contracts.getImageSource(mContext, mUri);
override val imageSource: ImageDecoder.Source
get() = Contracts.getImageSource(mContext, uri)
override fun openFileDescriptor(mode: String): ParcelFileDescriptor {
return Contracts.openFileDescriptor(mContext, uri, mode)
}
@NonNull
@Override
public ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException {
return Contracts.openFileDescriptor(mContext, mUri, mode);
companion object {
fun isMediaUri(context: Context, uri: Uri): Boolean {
return null != MediaContract.getName(context, uri)
}
}
}
+121 -194
View File
@@ -13,251 +13,178 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.util.Log
import android.webkit.MimeTypeMap
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.Locale
import android.graphics.ImageDecoder;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.webkit.MimeTypeMap;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
class RawFile extends UniFile {
private static final String TAG = RawFile.class.getSimpleName();
public File mFile;
RawFile(UniFile parent, File file) {
super(parent);
mFile = file;
}
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;
}
@Override
public UniFile createFile(String displayName) {
final File target = new File(mFile, displayName);
if (target.exists()) {
if (target.isFile()) {
return new RawFile(this, target);
internal class RawFile(parent: UniFile?, var mFile: File) : UniFile(parent) {
override fun createFile(displayName: String): UniFile? {
val target = File(mFile, displayName)
return if (target.exists()) {
if (target.isFile) {
RawFile(this, target)
} else {
return null;
null
}
} else {
OutputStream os = null;
try {
os = new FileOutputStream(target);
return new RawFile(this, target);
} catch (IOException e) {
Log.w(TAG, "Failed to createFile " + displayName + ": " + e);
return null;
} finally {
Utils.closeQuietly(os);
runCatching {
FileOutputStream(target).use {}
RawFile(this, target)
}.getOrElse {
Log.w(TAG, "Failed to createFile $displayName: $it")
null
}
}
}
@Override
public UniFile createDirectory(String displayName) {
final File target = new File(mFile, displayName);
if (target.isDirectory() || target.mkdirs()) {
return new RawFile(this, target);
override fun createDirectory(displayName: String): UniFile? {
val target = File(mFile, displayName)
return if (target.isDirectory || target.mkdirs()) {
RawFile(this, target)
} else {
return null;
null
}
}
@Override
@NonNull
public Uri getUri() {
return Uri.fromFile(mFile);
}
@Override
public String getName() {
return mFile.getName();
}
@Override
public String getType() {
if (mFile.isDirectory()) {
return null;
override val uri: Uri
get() = Uri.fromFile(mFile)
override val name: String
get() = mFile.name
override val type: String?
get() = if (mFile.isDirectory) {
null
} else {
return getTypeForName(mFile.getName());
getTypeForName(mFile.name)
}
override val isDirectory: Boolean
get() = mFile.isDirectory
override val isFile: Boolean
get() = mFile.isFile
override fun lastModified(): Long {
return mFile.lastModified()
}
@Override
public boolean isDirectory() {
return mFile.isDirectory();
override fun length(): Long {
return mFile.length()
}
@Override
public boolean isFile() {
return mFile.isFile();
override fun canRead(): Boolean {
return mFile.canRead()
}
@Override
public long lastModified() {
return mFile.lastModified();
override fun canWrite(): Boolean {
return mFile.canWrite()
}
@Override
public long length() {
return mFile.length();
override fun ensureDir(): Boolean {
return mFile.isDirectory || mFile.mkdirs()
}
@Override
public boolean canRead() {
return mFile.canRead();
}
@Override
public boolean canWrite() {
return mFile.canWrite();
}
@Override
public boolean ensureDir() {
return mFile.isDirectory() || mFile.mkdirs();
}
@Override
public boolean ensureFile() {
if (mFile.exists()) {
return mFile.isFile();
override fun ensureFile(): Boolean {
return if (mFile.exists()) {
mFile.isFile
} else {
OutputStream os = null;
try {
os = new FileOutputStream(mFile);
return true;
} catch (IOException e) {
return false;
} finally {
Utils.closeQuietly(os);
}
runCatching {
FileOutputStream(mFile).use {}
true
}.getOrDefault(false)
}
}
@Override
public UniFile subFile(String displayName) {
return new RawFile(this, new File(mFile, displayName));
override fun subFile(displayName: String): UniFile {
return RawFile(this, File(mFile, displayName))
}
@Override
public boolean delete() {
deleteContents(mFile);
return mFile.delete();
override fun delete(): Boolean {
deleteContents(mFile)
return mFile.delete()
}
@Override
public boolean exists() {
return mFile.exists();
override fun exists(): Boolean {
return mFile.exists()
}
@Override
public UniFile[] listFiles() {
final File[] files = mFile.listFiles();
if (files == null) {
return null;
}
int length = files.length;
UniFile[] results = new UniFile[length];
for (int i = 0; i < length; i++) {
results[i] = new RawFile(this, files[i]);
}
return results;
override fun listFiles(): Array<UniFile>? {
val files = mFile.listFiles() ?: return null
return files.map { RawFile(this, it) }.toTypedArray()
}
@Override
public UniFile[] listFiles(FilenameFilter filter) {
override fun listFiles(filter: FilenameFilter?): Array<UniFile>? {
if (filter == null) {
return listFiles();
return listFiles()
}
final File[] files = mFile.listFiles();
if (files == null) {
return null;
}
final ArrayList<UniFile> results = new ArrayList<>();
for (File file : files) {
if (filter.accept(this, file.getName())) {
results.add(new RawFile(this, file));
val files = mFile.listFiles() ?: return null
val results = ArrayList<UniFile>()
for (file in files) {
if (filter.accept(this, file.name)) {
results.add(RawFile(this, file))
}
}
return results.toArray(new UniFile[0]);
return results.toTypedArray<UniFile>()
}
@Override
public UniFile findFile(String displayName) {
final File child = new File(mFile, displayName);
return child.exists() ? new RawFile(this, child) : null;
override fun findFile(displayName: String): UniFile? {
val child = File(mFile, displayName)
return if (child.exists()) RawFile(this, child) else null
}
@Override
public boolean renameTo(String displayName) {
final File target = new File(mFile.getParentFile(), displayName);
if (mFile.renameTo(target)) {
mFile = target;
return true;
override fun renameTo(displayName: String): Boolean {
val target = File(mFile.parentFile, displayName)
return if (mFile.renameTo(target)) {
mFile = target
true
} else {
return false;
false
}
}
@NonNull
@Override
public ImageDecoder.Source getImageSource() {
return ImageDecoder.createSource(mFile);
override val imageSource: ImageDecoder.Source
get() = ImageDecoder.createSource(mFile)
override fun openFileDescriptor(mode: String): ParcelFileDescriptor {
val md = ParcelFileDescriptor.parseMode(mode)
return ParcelFileDescriptor.open(mFile, md)
?: throw IOException("Can't open ParcelFileDescriptor")
}
@NonNull
@Override
public ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException {
var md = ParcelFileDescriptor.parseMode(mode);
var pfd = ParcelFileDescriptor.open(mFile, md);
if (pfd == null) {
throw new IOException("Can't open ParcelFileDescriptor");
companion object {
private val TAG = RawFile::class.java.simpleName
private fun getTypeForName(name: String): String {
val lastDot = name.lastIndexOf('.')
if (lastDot >= 0) {
val extension = name.substring(lastDot + 1).lowercase(Locale.getDefault())
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
if (mime != null) {
return mime
}
}
return "application/octet-stream"
}
private fun deleteContents(dir: File): Boolean {
val files = dir.listFiles()
var success = true
if (files != null) {
for (file in files) {
if (file.isDirectory) {
success = success and deleteContents(file)
}
if (!file.delete()) {
Log.w(TAG, "Failed to delete $file")
success = false
}
}
}
return success
}
return pfd;
}
}
@@ -13,139 +13,94 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.content.Context
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.content.Context;
import android.graphics.ImageDecoder;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
internal class SingleDocumentFile(parent: UniFile?, context: Context, override val uri: Uri) :
UniFile(parent) {
private val mContext: Context
import androidx.annotation.NonNull;
import java.io.IOException;
class SingleDocumentFile extends UniFile {
private final Context mContext;
private final Uri mUri;
SingleDocumentFile(UniFile parent, Context context, Uri uri) {
super(parent);
mContext = context.getApplicationContext();
mUri = uri;
init {
mContext = context.applicationContext
}
@Override
public UniFile createFile(String displayName) {
return null;
override fun createFile(displayName: String): UniFile? {
return null
}
@Override
public UniFile createDirectory(String displayName) {
return null;
override fun createDirectory(displayName: String): UniFile? {
return null
}
@Override
@NonNull
public Uri getUri() {
return mUri;
override val name: String?
get() = DocumentsContractApi19.getName(mContext, uri)
override val type: String?
get() = DocumentsContractApi19.getType(mContext, uri)
override val isDirectory: Boolean
get() = DocumentsContractApi19.isDirectory(mContext, uri)
override val isFile: Boolean
get() = DocumentsContractApi19.isFile(mContext, uri)
override fun lastModified(): Long {
return DocumentsContractApi19.lastModified(mContext, uri)
}
@Override
public String getName() {
return DocumentsContractApi19.getName(mContext, mUri);
override fun length(): Long {
return DocumentsContractApi19.length(mContext, uri)
}
@Override
public String getType() {
return DocumentsContractApi19.getType(mContext, mUri);
override fun canRead(): Boolean {
return DocumentsContractApi19.canRead(mContext, uri)
}
@Override
public boolean isDirectory() {
return DocumentsContractApi19.isDirectory(mContext, mUri);
override fun canWrite(): Boolean {
return DocumentsContractApi19.canWrite(mContext, uri)
}
@Override
public boolean isFile() {
return DocumentsContractApi19.isFile(mContext, mUri);
override fun ensureDir(): Boolean {
return isDirectory
}
@Override
public long lastModified() {
return DocumentsContractApi19.lastModified(mContext, mUri);
override fun ensureFile(): Boolean {
return isFile
}
@Override
public long length() {
return DocumentsContractApi19.length(mContext, mUri);
override fun subFile(displayName: String): UniFile? {
return null
}
@Override
public boolean canRead() {
return DocumentsContractApi19.canRead(mContext, mUri);
override fun delete(): Boolean {
return DocumentsContractApi19.delete(mContext, uri)
}
@Override
public boolean canWrite() {
return DocumentsContractApi19.canWrite(mContext, mUri);
override fun exists(): Boolean {
return DocumentsContractApi19.exists(mContext, uri)
}
@Override
public boolean ensureDir() {
return isDirectory();
override fun listFiles(): Array<UniFile>? {
return null
}
@Override
public boolean ensureFile() {
return isFile();
override fun listFiles(filter: FilenameFilter?): Array<UniFile>? {
return null
}
@Override
public UniFile subFile(String displayName) {
return null;
override fun findFile(displayName: String): UniFile? {
return null
}
@Override
public boolean delete() {
return DocumentsContractApi19.delete(mContext, mUri);
override fun renameTo(displayName: String): Boolean {
return false
}
@Override
public boolean exists() {
return DocumentsContractApi19.exists(mContext, mUri);
}
override val imageSource: ImageDecoder.Source
get() = Contracts.getImageSource(mContext, uri)
@Override
public UniFile[] listFiles() {
return null;
}
@Override
public UniFile[] listFiles(FilenameFilter filter) {
return null;
}
@Override
public UniFile findFile(String displayName) {
return null;
}
@Override
public boolean renameTo(String displayName) {
return false;
}
@NonNull
@Override
public ImageDecoder.Source getImageSource() {
return Contracts.getImageSource(mContext, mUri);
}
@NonNull
@Override
public ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException {
return Contracts.openFileDescriptor(mContext, mUri, mode);
override fun openFileDescriptor(mode: String): ParcelFileDescriptor {
return Contracts.openFileDescriptor(mContext, uri, mode)
}
}
@@ -13,248 +13,239 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.content.Context
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.util.Log
import android.webkit.MimeTypeMap
import android.content.Context;
import android.graphics.ImageDecoder;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
internal class TreeDocumentFile : UniFile {
private val mContext: Context
override var uri: Uri
private set
private var mFilename: String? = null
import androidx.annotation.NonNull;
import java.io.IOException;
import java.util.ArrayList;
class TreeDocumentFile extends UniFile {
private static final String TAG = TreeDocumentFile.class.getSimpleName();
private final Context mContext;
private Uri mUri;
private String mFilename;
TreeDocumentFile(UniFile parent, Context context, Uri uri) {
super(parent);
mContext = context.getApplicationContext();
mUri = uri;
constructor(parent: UniFile?, context: Context, uri: Uri) : super(parent) {
mContext = context.applicationContext
this.uri = uri
}
private TreeDocumentFile(UniFile parent, Context context, Uri uri, String filename) {
super(parent);
mContext = context.getApplicationContext();
mUri = uri;
mFilename = filename;
private constructor(parent: UniFile, context: Context, uri: Uri, filename: String?) : super(
parent,
) {
mContext = context.applicationContext
this.uri = uri
mFilename = filename
}
@Override
public UniFile createFile(String displayName) {
UniFile child = findFile(displayName);
if (child != null) {
if (child.isFile()) {
return child;
override fun createFile(displayName: String): UniFile? {
val child = findFile(displayName)
return if (child != null) {
if (child.isFile) {
child
} else {
Log.w(TAG, "Try to create file " + displayName + ", but it is not file");
return null;
Log.w(
TAG,
"Try to create file $displayName, but it is not file",
)
null
}
} else {
int index = displayName.lastIndexOf('.');
val index = displayName.lastIndexOf('.')
if (index > 0) {
String name = displayName.substring(0, index);
String extension = displayName.substring(index + 1);
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (!TextUtils.isEmpty(mimeType)) {
final Uri result = DocumentsContractApi21.createFile(mContext, mUri, mimeType, name);
return (result != null) ? new TreeDocumentFile(this, mContext, result, displayName) : null;
val name = displayName.substring(0, index)
val extension = displayName.substring(index + 1)
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
if (!mimeType.isNullOrEmpty()) {
val result = DocumentsContractApi21.createFile(mContext, uri, mimeType, name)
return if (result != null) {
TreeDocumentFile(
this,
mContext,
result,
displayName,
)
} else {
null
}
}
}
// Not dot in displayName or dot is the first char or can't get MimeType
final Uri result = DocumentsContractApi21.createFile(mContext, mUri, "application/octet-stream", displayName);
return (result != null) ? new TreeDocumentFile(this, mContext, result, displayName) : null;
}
}
@Override
public UniFile createDirectory(String displayName) {
UniFile child = findFile(displayName);
if (child != null) {
if (child.isDirectory()) {
return child;
val result = DocumentsContractApi21.createFile(
mContext,
uri,
"application/octet-stream",
displayName,
)
if (result != null) {
TreeDocumentFile(
this,
mContext,
result,
displayName,
)
} else {
return null;
null
}
}
}
override fun createDirectory(displayName: String): UniFile? {
val child = findFile(displayName)
return if (child != null) {
if (child.isDirectory) {
child
} else {
null
}
} else {
final Uri result = DocumentsContractApi21.createDirectory(mContext, mUri, displayName);
return (result != null) ? new TreeDocumentFile(this, mContext, result, displayName) : null;
val result = DocumentsContractApi21.createDirectory(mContext, uri, displayName)
if (result != null) {
TreeDocumentFile(
this,
mContext,
result,
displayName,
)
} else {
null
}
}
}
@NonNull
@Override
public Uri getUri() {
return mUri;
override val name: String?
get() = DocumentsContractApi19.getName(mContext, uri)
override val type: String?
get() = DocumentsContractApi19.getType(mContext, uri)
override val isDirectory: Boolean
get() = DocumentsContractApi19.isDirectory(mContext, uri)
override val isFile: Boolean
get() = DocumentsContractApi19.isFile(mContext, uri)
override fun lastModified(): Long {
return DocumentsContractApi19.lastModified(mContext, uri)
}
@Override
public String getName() {
return DocumentsContractApi19.getName(mContext, mUri);
override fun length(): Long {
return DocumentsContractApi19.length(mContext, uri)
}
@Override
public String getType() {
return DocumentsContractApi19.getType(mContext, mUri);
override fun canRead(): Boolean {
return DocumentsContractApi19.canRead(mContext, uri)
}
@Override
public boolean isDirectory() {
return DocumentsContractApi19.isDirectory(mContext, mUri);
override fun canWrite(): Boolean {
return DocumentsContractApi19.canWrite(mContext, uri)
}
@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 ensureDir() {
if (isDirectory()) {
return true;
} else if (isFile()) {
return false;
override fun ensureDir(): Boolean {
if (isDirectory) {
return true
} else if (isFile) {
return false
}
UniFile parent = getParentFile();
if (parent != null && parent.ensureDir() && mFilename != null) {
return parent.createDirectory(mFilename) != null;
val parent = parentFile
return if (parent != null && parent.ensureDir() && mFilename != null) {
parent.createDirectory(mFilename!!) != null
} else {
return false;
false
}
}
@Override
public boolean ensureFile() {
if (isFile()) {
return true;
} else if (isDirectory()) {
return false;
override fun ensureFile(): Boolean {
if (isFile) {
return true
} else if (isDirectory) {
return false
}
UniFile parent = getParentFile();
if (parent != null && parent.ensureDir() && mFilename != null) {
return parent.createFile(mFilename) != null;
val parent = parentFile
return if (parent != null && parent.ensureDir() && mFilename != null) {
parent.createFile(mFilename!!) != null
} else {
return false;
false
}
}
@Override
public UniFile subFile(String displayName) {
Uri childUri = DocumentsContractApi21.buildChildUri(mUri, displayName);
return new TreeDocumentFile(this, mContext, childUri, displayName);
override fun subFile(displayName: String): UniFile {
val childUri = DocumentsContractApi21.buildChildUri(uri, displayName)
return TreeDocumentFile(this, mContext, childUri, displayName)
}
@Override
public boolean delete() {
return DocumentsContractApi19.delete(mContext, mUri);
override fun delete(): Boolean {
return DocumentsContractApi19.delete(mContext, uri)
}
@Override
public boolean exists() {
return DocumentsContractApi19.exists(mContext, mUri);
override fun exists(): Boolean {
return DocumentsContractApi19.exists(mContext, uri)
}
private String getFilenameForUri(Uri uri) {
String path = uri.getPath();
private fun getFilenameForUri(uri: Uri): String? {
val path = uri.path
if (path != null) {
int index = path.lastIndexOf('/');
val index = path.lastIndexOf('/')
if (index >= 0) {
return path.substring(index + 1);
return path.substring(index + 1)
}
}
return null;
return null
}
@Override
public UniFile[] listFiles() {
final Uri[] result = DocumentsContractApi21.listFiles(mContext, mUri);
final UniFile[] resultFiles = new UniFile[result.length];
for (int i = 0, n = result.length; i < n; i++) {
Uri uri = result[i];
resultFiles[i] = new TreeDocumentFile(this, mContext, uri, getFilenameForUri(uri));
}
return resultFiles;
override fun listFiles(): Array<UniFile> {
val result = DocumentsContractApi21.listFiles(mContext, uri)
return result.map { TreeDocumentFile(this, mContext, it, getFilenameForUri(it)) }.toTypedArray()
}
@Override
public UniFile[] listFiles(FilenameFilter filter) {
override fun listFiles(filter: FilenameFilter?): Array<UniFile> {
if (filter == null) {
return listFiles();
return listFiles()
}
final Uri[] result = DocumentsContractApi21.listFiles(mContext, mUri);
final ArrayList<UniFile> results = new ArrayList<>();
for (Uri uri : result) {
String name = getFilenameForUri(uri);
val result = DocumentsContractApi21.listFiles(mContext, uri)
val results = ArrayList<UniFile>()
for (uri in result) {
val name = getFilenameForUri(uri)
if (filter.accept(this, name)) {
results.add(new TreeDocumentFile(this, mContext, uri, name));
results.add(TreeDocumentFile(this, mContext, uri, name))
}
}
return results.toArray(new UniFile[0]);
return results.toTypedArray<UniFile>()
}
@Override
public UniFile findFile(String displayName) {
Uri childUri = DocumentsContractApi21.buildChildUri(mUri, displayName);
return DocumentsContractApi19.exists(mContext, childUri) ?
new TreeDocumentFile(this, mContext, childUri, displayName) : null;
}
@Override
public boolean renameTo(String displayName) {
final Uri result = DocumentsContractApi21.renameTo(mContext, mUri, displayName);
if (result != null) {
mUri = result;
return true;
override fun findFile(displayName: String): UniFile? {
val childUri = DocumentsContractApi21.buildChildUri(uri, displayName)
return if (DocumentsContractApi19.exists(mContext, childUri)) {
TreeDocumentFile(
this,
mContext,
childUri,
displayName,
)
} else {
return false;
null
}
}
@NonNull
@Override
public ImageDecoder.Source getImageSource() {
return Contracts.getImageSource(mContext, mUri);
override fun renameTo(displayName: String): Boolean {
val result = DocumentsContractApi21.renameTo(mContext, uri, displayName)
return if (result != null) {
uri = result
true
} else {
false
}
}
@NonNull
@Override
public ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException {
return Contracts.openFileDescriptor(mContext, mUri, mode);
override val imageSource: ImageDecoder.Source
get() = Contracts.getImageSource(mContext, uri)
override fun openFileDescriptor(mode: String): ParcelFileDescriptor {
return Contracts.openFileDescriptor(mContext, uri, mode)
}
companion object {
private val TAG = TreeDocumentFile::class.java.simpleName
}
}
+239 -265
View File
@@ -13,408 +13,382 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.graphics.ImageDecoder;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.os.ParcelFileDescriptor
import java.io.File
/**
* In Android files can be accessed via {@link java.io.File} and {@link android.net.Uri}.
* In Android files can be accessed via [java.io.File] and [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)));
}
abstract class UniFile internal constructor(private val parent: UniFile?) {
/**
* 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)
* @see android.provider.DocumentsContract.createDocument
*/
public abstract UniFile createFile(String displayName);
abstract fun createFile(displayName: String): UniFile?
/**
* 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)
* @see android.provider.DocumentsContract.createDocument
*/
public abstract UniFile createDirectory(String displayName);
abstract fun createDirectory(displayName: String): UniFile?
/**
* 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
* underlying content. You can use [.isTreeUri] to
* test if the returned Uri is backed by a
* {@link android.provider.DocumentsProvider}.
* [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)
* @see Intent.setData
* @see Intent.setClipData
* @see ContentResolver.openInputStream
* @see ContentResolver.openOutputStream
* @see ContentResolver.openFileDescriptor
*/
@NonNull
public abstract Uri getUri();
abstract val uri: Uri
/**
* Return the display name of this file.
*
* @return name of the file, or null if failed
* @see android.provider.DocumentsContract.Document#COLUMN_DISPLAY_NAME
* @see android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME
*/
@Nullable
public abstract String getName();
abstract val name: String?
/**
* 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
* @see android.provider.DocumentsContract.Document.COLUMN_MIME_TYPE
*/
@Nullable
public abstract String getType();
abstract val type: String?
/**
* 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
*
*
* The underlying [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;
}
val parentFile: UniFile?
get() = parent
/**
* Indicates if this file represents a <em>directory</em>.
* Indicates if this file represents a *directory*.
*
* @return {@code true} if this file is a directory, {@code false}
* @return `true` if this file is a directory, `false`
* otherwise.
* @see android.provider.DocumentsContract.Document#MIME_TYPE_DIR
* @see android.provider.DocumentsContract.Document.MIME_TYPE_DIR
*/
public abstract boolean isDirectory();
abstract val isDirectory: Boolean
/**
* Indicates if this file represents a <em>file</em>.
* Indicates if this file represents a *file*.
*
* @return {@code true} if this file is a file, {@code false} otherwise.
* @see android.provider.DocumentsContract.Document#COLUMN_MIME_TYPE
* @return `true` if this file is a file, `false` otherwise.
* @see android.provider.DocumentsContract.Document.COLUMN_MIME_TYPE
*/
public abstract boolean isFile();
abstract val isFile: Boolean
/**
* 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
* @return the time when this file was last modified, `-1L` if can't get it
* @see android.provider.DocumentsContract.Document.COLUMN_LAST_MODIFIED
*/
public abstract long lastModified();
abstract fun lastModified(): Long
/**
* 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
* @return the number of bytes in this file, `-1L` if can't get it
* @see android.provider.DocumentsContract.Document.COLUMN_SIZE
*/
public abstract long length();
abstract fun length(): Long
/**
* Indicates whether the current context is allowed to read from this file.
*
* @return {@code true} if this file can be read, {@code false} otherwise.
* @return `true` if this file can be read, `false` otherwise.
*/
public abstract boolean canRead();
abstract fun canRead(): Boolean
/**
* Indicates whether the current context is allowed to write to this file.
*
* @return {@code true} if this file can be written, {@code false}
* @return `true` if this file can be written, `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
* @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();
abstract fun canWrite(): Boolean
/**
* It works like mkdirs, but it will return true if the UniFile is directory
*
* @return {@code true} if the directory was created
* @return `true` if the directory was created
* or if the directory already existed.
*/
public abstract boolean ensureDir();
abstract fun ensureDir(): Boolean
/**
* Make sure the UniFile is file
*
* @return {@code true} if the file can be created
* @return `true` if the file can be created
* or if the file already existed.
*/
public abstract boolean ensureFile();
abstract fun ensureFile(): Boolean
/**
* Get child file of this directory, the child might not exist.
*
* @return the child file, {@code null} if not supported
* @return the child file, `null` if not supported
*/
@Nullable
public abstract UniFile subFile(String displayName);
abstract fun subFile(displayName: String): UniFile?
/**
* Deletes this file.
* <p>
* Note that this method does <i>not</i> throw {@code IOException} on
*
*
* Note that this method does *not* throw `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)
* @return `true` if this file was deleted, `false` otherwise.
* @see android.provider.DocumentsContract.deleteDocument
*/
public abstract boolean delete();
abstract fun delete(): Boolean
/**
* Returns a boolean indicating whether this file can be found.
*
* @return {@code true} if this file exists, {@code false} otherwise.
* @return `true` if this file exists, `false` otherwise.
*/
public abstract boolean exists();
abstract fun exists(): Boolean
/**
* 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)
* @return an array of files or `null`.
* @see android.provider.DocumentsContract.buildChildDocumentsUriUsingTree
*/
@Nullable
public abstract UniFile[] listFiles();
abstract fun listFiles(): Array<UniFile>?
/**
* 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}.
* @param filter the filter to match names against, may be `null`.
* @return an array of files or `null`.
*/
@Nullable
public abstract UniFile[] listFiles(FilenameFilter filter);
abstract fun listFiles(filter: FilenameFilter?): Array<UniFile>?
/**
* Test there is a file with the display name in the directory.
*
* @return the file if found it, or {@code null}.
* @return the file if found it, or `null`.
*/
@Nullable
public abstract UniFile findFile(String displayName);
abstract fun findFile(displayName: String): UniFile?
/**
* Renames this file to {@code displayName}.
* <p>
* Note that this method does <i>not</i> throw {@code IOException} on
* Renames this file to `displayName`.
*
*
* Note that this method does *not* throw `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>
* potentially with a different MIME type, so [.getUri] and
* [.getType] may change to reflect the rename.
*
*
* When renaming a directory, children previously enumerated through
* {@link #listFiles()} may no longer be valid.
* [.listFiles] may no longer be valid.
*
* @param displayName the new display name.
* @return true on success.
* @see android.provider.DocumentsContract#renameDocument(ContentResolver,
* Uri, String)
* @see android.provider.DocumentsContract.renameDocument
*/
public abstract boolean renameTo(String displayName);
abstract fun renameTo(displayName: String): Boolean
abstract val imageSource: ImageDecoder.Source
@NonNull
public abstract ImageDecoder.Source getImageSource();
abstract fun openFileDescriptor(mode: String): ParcelFileDescriptor
@NonNull
public abstract ParcelFileDescriptor openFileDescriptor(@NonNull String mode) throws IOException;
companion object {
private var sUriHandlerArray: MutableList<UriHandler>? = null
/**
* Add a UriHandler to get UniFile from uri
*/
fun addUriHandler(handler: UriHandler) {
if (sUriHandlerArray == null) {
sUriHandlerArray = ArrayList()
}
sUriHandlerArray!!.add(handler)
}
/**
* Remove the UriHandler added before
*/
fun removeUriHandler(handler: UriHandler) {
if (sUriHandlerArray != null) {
sUriHandlerArray!!.remove(handler)
}
}
/**
* Create a [UniFile] representing the given [File].
*
* @param file the file to wrap
* @return the [UniFile] representing the given [File].
*/
fun fromFile(file: File?): UniFile? {
return if (file != null) RawFile(null, file) else null
}
/**
* Create a [UniFile] representing the single document at the
* given [Uri]. This is only useful on devices running
* [android.os.Build.VERSION_CODES.KITKAT] or later, and will return
* `null` when called on earlier platform versions.
*
* @param singleUri the [Intent.getData] from a successful
* [Intent.ACTION_OPEN_DOCUMENT] or
* [Intent.ACTION_CREATE_DOCUMENT] request.
* @return the [UniFile] representing the given [Uri].
*/
fun fromSingleUri(context: Context, singleUri: Uri): UniFile? {
val version = Build.VERSION.SDK_INT
return if (version >= 19) {
SingleDocumentFile(null, context, singleUri)
} else {
null
}
}
/**
* Create a [UniFile] representing the document tree rooted at
* the given [Uri]. This is only useful on devices running
* [Build.VERSION_CODES.LOLLIPOP] or later, and will return
* `null` when called on earlier platform versions.
*
* @param treeUri the [Intent.getData] from a successful
* [Intent.ACTION_OPEN_DOCUMENT_TREE] request.
* @return the [UniFile] representing the given [Uri].
*/
fun fromTreeUri(context: Context, treeUri: Uri): UniFile? {
val version = Build.VERSION.SDK_INT
return if (version >= 21) {
TreeDocumentFile(
null,
context,
DocumentsContractApi21.prepareTreeUri(treeUri),
)
} else {
null
}
}
/**
* Create a [UniFile] representing the media file rooted at
* the given [Uri].
*
* @param mediaUri the media uri to wrap
* @return the [UniFile] representing the given [Uri].
*/
fun fromMediaUri(context: Context, mediaUri: Uri): UniFile {
return MediaFile(context, mediaUri)
}
/**
* Create a [UniFile] representing the given [Uri].
*/
fun fromUri(context: Context, uri: Uri): UniFile? {
// Custom handler
if (sUriHandlerArray != null) {
var i = 0
val size = sUriHandlerArray!!.size
while (i < size) {
val file = sUriHandlerArray!![i].fromUri(context, uri)
if (file != null) {
return file
}
i++
}
}
return if (isFileUri(uri)) {
fromFile(File(uri.path!!))
} else if (isDocumentUri(context, uri)) {
if (isTreeUri(uri)) {
fromTreeUri(context, uri)
} else {
fromSingleUri(context, uri)
}
} else if (MediaFile.isMediaUri(context, uri)) {
MediaFile(context, uri)
} else {
null
}
}
/**
* Test if given Uri is FileUri
*/
fun isFileUri(uri: Uri): Boolean {
return ContentResolver.SCHEME_FILE == uri.scheme
}
/**
* Test if given Uri is backed by a
* [android.provider.DocumentsProvider].
*/
fun isDocumentUri(context: Context, uri: Uri): Boolean {
val version = Build.VERSION.SDK_INT
return if (version >= 19) {
DocumentsContractApi19.isDocumentUri(context, uri)
} else {
false
}
}
/**
* Test if given Uri is TreeUri
*/
fun isTreeUri(uri: Uri): Boolean {
val paths = uri.pathSegments
return ContentResolver.SCHEME_CONTENT == uri.scheme && paths.size >= 2 && "tree" == paths[0]
}
}
}
@@ -13,23 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.content.Context
import android.net.Uri
/*
* Created by Hippo on 8/16/2016.
*/
import android.content.Context;
import android.net.Uri;
/**
* A UriHandler is to get UniFile from custom uri for extensions
*/
public interface UriHandler {
interface UriHandler {
/**
* Create a {@link UniFile} representing the uri
* Create a [UniFile] representing the uri
*/
UniFile fromUri(Context context, Uri uri);
fun fromUri(context: Context?, uri: Uri?): UniFile?
}
+5 -37
View File
@@ -15,45 +15,13 @@
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.unifile
package com.hippo.unifile;
import android.database.Cursor;
import androidx.annotation.NonNull;
import java.io.Closeable;
class Utils {
static void closeQuietly(Cursor c) {
if (c != null) {
try {
c.close();
} catch (Throwable e) {
throwIfFatal(e);
}
}
}
static void closeQuietly(Closeable is) {
if (is != null) {
try {
is.close();
} catch (Throwable e) {
throwIfFatal(e);
}
}
}
static void throwIfFatal(@NonNull Throwable t) {
internal object Utils {
fun throwIfFatal(t: Throwable) {
// values here derived from https://github.com/ReactiveX/RxJava/issues/748#issuecomment-32471495
if (t instanceof VirtualMachineError) {
throw (VirtualMachineError) t;
} else if (t instanceof ThreadDeath) {
throw (ThreadDeath) t;
} else if (t instanceof LinkageError) {
throw (LinkageError) t;
when (t) {
is VirtualMachineError, is ThreadDeath, is LinkageError -> throw t
}
}
}