Convert all unifile classes to kotlin
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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?
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user