diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5a30cd4..5b29193 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,6 +39,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/luyao/util/ktx/ui/ChooseFileActivity.kt b/app/src/main/java/luyao/util/ktx/ui/ChooseFileActivity.kt new file mode 100644 index 0000000..7109ab3 --- /dev/null +++ b/app/src/main/java/luyao/util/ktx/ui/ChooseFileActivity.kt @@ -0,0 +1,78 @@ +package luyao.util.ktx.ui + +import android.Manifest +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import kotlinx.android.synthetic.main.activity_choose_file.* +import luyao.ktx.R +import luyao.util.ktx.base.BaseActivity +import luyao.util.ktx.ext.startFileChooser +import luyao.util.ktx.ext.toRealFile + +/** + * Created by luyao + * on 2019/12/9 15:00 + */ +class ChooseFileActivity : BaseActivity() { + + private var allowMultiple = false + override fun getLayoutResId() = R.layout.activity_choose_file + + override fun initView() { + singleChoose.setOnClickListener { chooseFile(1001, false) } + multipleChoose.setOnClickListener { chooseFile(1001, true) } + } + + private fun chooseFile(requestCode: Int, multiple: Boolean) { + allowMultiple = multiple + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED + ) { + startFileChooser(requestCode, allowMultiple) + } else { + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + 1002 + ) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + startFileChooser(1001, allowMultiple) + } + } + + override fun initData() { + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if ((requestCode == 1001) && (resultCode == Activity.RESULT_OK)) { + + data?.data?.let { // single + uriResult.text = it.toRealFile()?.path ?: "" + } + + data?.clipData?.let { + // multiple + val builder = StringBuilder() + for (i in 0 until it.itemCount) { + it.getItemAt(i).uri.let { uri -> + builder.append("${uri.toRealFile()?.path}\n\n") + } + } + uriResult.text = builder.toString() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/luyao/util/ktx/ui/ExtActivity.kt b/app/src/main/java/luyao/util/ktx/ui/ExtActivity.kt index b9f3423..ed79d2e 100644 --- a/app/src/main/java/luyao/util/ktx/ui/ExtActivity.kt +++ b/app/src/main/java/luyao/util/ktx/ui/ExtActivity.kt @@ -22,6 +22,7 @@ class ExtActivity : CommonListActivity() { add(ItemBean("SharedPreferencesExt", SharedPreferencesActivity::class.java)) add(ItemBean("ShellExt", ShellExtActivity::class.java)) add(ItemBean("ToastExt", ToastExtActivity::class.java)) + add(ItemBean("UriExt",ChooseFileActivity::class.java)) } } diff --git a/app/src/main/res/layout/activity_choose_file.xml b/app/src/main/res/layout/activity_choose_file.xml new file mode 100644 index 0000000..c902dc5 --- /dev/null +++ b/app/src/main/res/layout/activity_choose_file.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ktx/src/main/java/luyao/util/ktx/ext/CommonExt.kt b/ktx/src/main/java/luyao/util/ktx/ext/CommonExt.kt index af097e7..017e760 100644 --- a/ktx/src/main/java/luyao/util/ktx/ext/CommonExt.kt +++ b/ktx/src/main/java/luyao/util/ktx/ext/CommonExt.kt @@ -32,7 +32,7 @@ val Context.screenWidth val Context.screenHeight get() = resources.displayMetrics.heightPixels - +fun fromKitKat() = fromSpecificVersion(Build.VERSION_CODES.KITKAT) fun fromM() = fromSpecificVersion(Build.VERSION_CODES.M) fun beforeM() = beforeSpecificVersion(Build.VERSION_CODES.M) fun fromN() = fromSpecificVersion(Build.VERSION_CODES.N) diff --git a/ktx/src/main/java/luyao/util/ktx/ext/IntentExt.kt b/ktx/src/main/java/luyao/util/ktx/ext/IntentExt.kt index b2ba03d..82da6d0 100644 --- a/ktx/src/main/java/luyao/util/ktx/ext/IntentExt.kt +++ b/ktx/src/main/java/luyao/util/ktx/ext/IntentExt.kt @@ -1,5 +1,6 @@ package luyao.util.ktx.ext +import android.app.Activity import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent @@ -153,4 +154,15 @@ fun Context.sendEmail(email: String, subject: String?, text: String?) { } } +fun Activity.startFileChooser(requestCode: Int, allowMultiple: Boolean = false) { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = "*/*" + intent.addCategory(Intent.CATEGORY_OPENABLE) + if (fromSpecificVersion(Build.VERSION_CODES.JELLY_BEAN_MR2)) intent.putExtra( + Intent.EXTRA_ALLOW_MULTIPLE, + allowMultiple + ) + startActivityForResult(Intent.createChooser(intent, "Choose File"), requestCode) +} + diff --git a/ktx/src/main/java/luyao/util/ktx/ext/UriExt.kt b/ktx/src/main/java/luyao/util/ktx/ext/UriExt.kt new file mode 100644 index 0000000..8fe6009 --- /dev/null +++ b/ktx/src/main/java/luyao/util/ktx/ext/UriExt.kt @@ -0,0 +1,161 @@ +package luyao.util.ktx.ext + +import android.content.ContentResolver +import android.content.ContentUris +import android.content.Context +import android.net.Uri +import android.os.Environment +import android.provider.DocumentsContract +import android.provider.MediaStore +import android.text.TextUtils +import luyao.util.ktx.Ktx +import java.io.File +import java.lang.reflect.Array + +/* + * from Blankj + * https://github.com/Blankj/AndroidUtilCode/blob/master/lib/utilcode/src/main/java/com/blankj/utilcode/util/UriUtils.java + */ + +/** + * Created by luyao + * on 2019/12/9 15:20 + */ +fun Uri.toRealFile(): File? { + val authority = authority + val scheme = scheme + val path = path + + if (fromN() && path != null && path.startsWith("/external/")) { + val file = File(Environment.getExternalStorageDirectory().absolutePath + path.replace("/external", "")) + if (file.exists()) { + return file + } + } + + if (ContentResolver.SCHEME_FILE == scheme) { + return if (path != null) File(path) else null + } else if (fromKitKat() && DocumentsContract.isDocumentUri(Ktx.app, this)) { + if ("com.android.externalstorage.documents" == authority) { + val docId = DocumentsContract.getDocumentId(this) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val type = split[0] + if ("primary".equals(type, ignoreCase = true)) { + return File(Environment.getExternalStorageDirectory().toString() + "/" + split[1]) + } else { + // Below logic is how External Storage provider build URI for documents + // http://stackoverflow.com/questions/28605278/android-5-sd-card-label + val mStorageManager = Ktx.app.storageManager + try { + val storageVolumeClazz = Class.forName("android.os.storage.StorageVolume") + val getVolumeList = mStorageManager?.javaClass?.getMethod("getVolumeList") + val getUuid = storageVolumeClazz.getMethod("getUuid") + val getState = storageVolumeClazz.getMethod("getState") + val getPath = storageVolumeClazz.getMethod("getPath") + val isPrimary = storageVolumeClazz.getMethod("isPrimary") + val isEmulated = storageVolumeClazz.getMethod("isEmulated") + + val result = getVolumeList?.invoke(mStorageManager) + + val length = Array.getLength(result) + for (i in 0 until length) { + val storageVolumeElement = Array.get(result, i) + //String uuid = (String) getUuid.invoke(storageVolumeElement); + + val mounted = + Environment.MEDIA_MOUNTED == getState.invoke(storageVolumeElement) || Environment.MEDIA_MOUNTED_READ_ONLY == getState.invoke( + storageVolumeElement + ) + + //if the media is not mounted, we need not get the volume details + if (!mounted) continue + + //Primary storage is already handled. + if (isPrimary.invoke(storageVolumeElement) as Boolean && + isEmulated.invoke(storageVolumeElement) as Boolean) { + continue + } + + val uuid = getUuid.invoke(storageVolumeElement) as String + + if (uuid == type) { + return File(getPath.invoke(storageVolumeElement).toString() + "/" + split[1]) + } + } + } catch (ex: Exception) { + ex.printStackTrace() + } + } + return null + } else if ("com.android.providers.downloads.documents" == authority) { + val id = DocumentsContract.getDocumentId(this) + if (!TextUtils.isEmpty(id)) { + try { + val contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), + java.lang.Long.valueOf(id) + ) + return Ktx.app.getFileFromUri(contentUri, "1_1") + } catch (e: NumberFormatException) { + if (id.startsWith("raw:")) { + return File(id.substring(4)) + } + } + } + return null + } else if ("com.android.providers.media.documents" == authority) { + val docId = DocumentsContract.getDocumentId(this) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val type = split[0] + val contentUri: Uri + contentUri = when (type) { + "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + else -> { + return null + } + } + val selection = "_id=?" + val selectionArgs = arrayOf(split[1]) + return Ktx.app.getFileFromUri(contentUri, selection, selectionArgs) + } else if (ContentResolver.SCHEME_CONTENT == scheme) { + return Ktx.app.getFileFromUri(this) + } else { + return null + } + } else if (ContentResolver.SCHEME_CONTENT == scheme) run { + return Ktx.app.getFileFromUri(this) + } else return null + +} + + +private fun Context.getFileFromUri( + uri: Uri, + selection: String? = null, + selectionArgs: kotlin.Array? = null +): File? { + val cursor = contentResolver.query( + uri, arrayOf("_data"), selection, selectionArgs, null + ) + if (cursor == null) { + return null + } + try { + return if (cursor.moveToFirst()) { + val columnIndex = cursor.getColumnIndex("_data") + if (columnIndex > -1) { + File(cursor.getString(columnIndex)) + } else { + null + } + } else { + null + } + } catch (e: Exception) { + return null + } finally { + cursor.close() + } +} \ No newline at end of file