From 653c981a998d76f739adcd39d1f5dbbca3903d70 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Fri, 22 Apr 2022 03:09:34 +0800 Subject: [PATCH] Move native code to plugin --- .../nc_photos/MediaStoreChannelHandler.kt | 113 +++----------- .../com/nkming/nc_photos/plugin/Exception.kt | 3 + .../nkming/nc_photos/plugin/MediaStoreUtil.kt | 143 ++++++++++++++++++ 3 files changed, 163 insertions(+), 96 deletions(-) create mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Exception.kt create mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreUtil.kt diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/MediaStoreChannelHandler.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/MediaStoreChannelHandler.kt index e6ed37b2..57afea50 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/MediaStoreChannelHandler.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/MediaStoreChannelHandler.kt @@ -1,17 +1,10 @@ package com.nkming.nc_photos import android.app.Activity -import android.content.ContentValues -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.os.Environment -import android.provider.MediaStore -import androidx.annotation.RequiresApi -import androidx.core.content.FileProvider +import com.nkming.nc_photos.plugin.MediaStoreUtil +import com.nkming.nc_photos.plugin.PermissionException import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -import java.io.* /* * Save downloaded item on device @@ -55,100 +48,28 @@ class MediaStoreChannelHandler(activity: Activity) : private fun saveFileToDownload( fileName: String, content: ByteArray, result: MethodChannel.Result ) { - val stream = ByteArrayInputStream(content) - writeFileToDownload(fileName, stream, result) + try { + val uri = + MediaStoreUtil.saveFileToDownload(_context, fileName, content) + result.success(uri.toString()) + } catch (e: PermissionException) { + PermissionHandler.ensureWriteExternalStorage(_activity) + result.error("permissionError", "Permission not granted", null) + } } private fun copyFileToDownload( toFileName: String, fromFilePath: String, result: MethodChannel.Result ) { - val file = File(fromFilePath) - val stream = file.inputStream() - writeFileToDownload(toFileName, stream, result) - } - - private fun writeFileToDownload( - fileName: String, data: InputStream, result: MethodChannel.Result - ) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - writeFileToDownload29(fileName, data, result) - } else { - writeFileToDownload0(fileName, data, result) - } - } - - @RequiresApi(Build.VERSION_CODES.Q) - private fun writeFileToDownload29( - fileName: String, data: InputStream, result: MethodChannel.Result - ) { - // Add a media item that other apps shouldn't see until the item is - // fully written to the media store. - val resolver = _context.applicationContext.contentResolver - - // Find all audio files on the primary external storage device. - val collection = MediaStore.Downloads.getContentUri( - MediaStore.VOLUME_EXTERNAL_PRIMARY - ) - val file = File(fileName) - val details = ContentValues().apply { - put(MediaStore.Downloads.DISPLAY_NAME, file.name) - if (file.parent != null) { - put( - MediaStore.Downloads.RELATIVE_PATH, - "${Environment.DIRECTORY_DOWNLOADS}/${file.parent}" - ) - } - } - - val contentUri = resolver.insert(collection, details) - - resolver.openFileDescriptor(contentUri!!, "w", null).use { pfd -> - // Write data into the pending audio file. - BufferedOutputStream(FileOutputStream(pfd!!.fileDescriptor)).use { stream -> - data.copyTo(stream) - } - } - result.success(contentUri.toString()) - } - - private fun writeFileToDownload0( - fileName: String, data: InputStream, result: MethodChannel.Result - ) { - if (!PermissionHandler.ensureWriteExternalStorage(_activity)) { + try { + val uri = MediaStoreUtil.copyFileToDownload( + _context, toFileName, fromFilePath + ) + result.success(uri.toString()) + } catch (e: PermissionException) { + PermissionHandler.ensureWriteExternalStorage(_activity) result.error("permissionError", "Permission not granted", null) - return } - - val path = Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_DOWNLOADS - ) - var file = File(path, fileName) - var count = 1 - while (file.exists()) { - val f = File(fileName) - file = - File(path, "${f.nameWithoutExtension} ($count).${f.extension}") - ++count - } - file.parentFile?.mkdirs() - BufferedOutputStream(FileOutputStream(file)).use { stream -> - data.copyTo(stream) - } - - val fileUri = Uri.fromFile(file) - triggerMediaScan(fileUri) - val contentUri = FileProvider.getUriForFile( - _context, "${BuildConfig.APPLICATION_ID}.fileprovider", file - ) - result.success(contentUri.toString()) - } - - private fun triggerMediaScan(uri: Uri) { - val scanIntent = Intent().apply { - action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE - data = uri - } - _context.sendBroadcast(scanIntent) } private val _activity = activity diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Exception.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Exception.kt new file mode 100644 index 00000000..e132c15a --- /dev/null +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Exception.kt @@ -0,0 +1,3 @@ +package com.nkming.nc_photos.plugin + +class PermissionException(message: String) : Exception(message) diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreUtil.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreUtil.kt new file mode 100644 index 00000000..ecd2bf44 --- /dev/null +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreUtil.kt @@ -0,0 +1,143 @@ +package com.nkming.nc_photos.plugin + +import android.Manifest +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import java.io.* + +interface MediaStoreUtil { + companion object { + /** + * Save the @c content as a file under the user Download dir + * + * @param context + * @param filename Filename of the new file + * @param content + * @return Uri of the created file + */ + fun saveFileToDownload( + context: Context, filename: String, content: ByteArray + ): Uri { + val stream = ByteArrayInputStream(content) + return writeFileToDownload(context, filename, stream) + } + + /** + * Copy a file from @c fromFilePath to the user Download dir + * + * @param context + * @param toFilename Filename of the new file + * @param fromFilePath Path of the file to be copied + * @return Uri of the created file + */ + fun copyFileToDownload( + context: Context, toFilename: String, fromFilePath: String + ): Uri { + val file = File(fromFilePath) + val stream = file.inputStream() + return writeFileToDownload(context, toFilename, stream) + } + + private fun writeFileToDownload( + context: Context, filename: String, data: InputStream + ): Uri { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + writeFileToDownload29(context, filename, data) + } else { + writeFileToDownload0(context, filename, data) + } + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun writeFileToDownload29( + context: Context, filename: String, data: InputStream + ): Uri { + // Add a media item that other apps shouldn't see until the item is + // fully written to the media store. + val resolver = context.applicationContext.contentResolver + + // Find all audio files on the primary external storage device. + val collection = MediaStore.Downloads.getContentUri( + MediaStore.VOLUME_EXTERNAL_PRIMARY + ) + val file = File(filename) + val details = ContentValues().apply { + put(MediaStore.Downloads.DISPLAY_NAME, file.name) + if (file.parent != null) { + put( + MediaStore.Downloads.RELATIVE_PATH, + "${Environment.DIRECTORY_DOWNLOADS}/${file.parent}" + ) + } + } + + val contentUri = resolver.insert(collection, details) + + resolver.openFileDescriptor(contentUri!!, "w", null).use { pfd -> + // Write data into the pending audio file. + BufferedOutputStream( + FileOutputStream(pfd!!.fileDescriptor) + ).use { stream -> + data.copyTo(stream) + } + } + return contentUri + } + + private fun writeFileToDownload0( + context: Context, filename: String, data: InputStream + ): Uri { + if (ContextCompat.checkSelfPermission( + context, Manifest.permission.WRITE_EXTERNAL_STORAGE + ) != PackageManager.PERMISSION_GRANTED + ) { + throw PermissionException("Permission not granted") + } + + @Suppress("Deprecation") + val path = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS + ) + var file = File(path, filename) + var count = 1 + while (file.exists()) { + val f = File(filename) + file = File( + path, + "${f.nameWithoutExtension} ($count).${f.extension}" + ) + ++count + } + file.parentFile?.mkdirs() + BufferedOutputStream(FileOutputStream(file)).use { stream -> + data.copyTo(stream) + } + + val fileUri = Uri.fromFile(file) + triggerMediaScan(context, fileUri) + val contentUri = FileProvider.getUriForFile( + context, "${context.packageName}.fileprovider", file + ) + return contentUri + } + + private fun triggerMediaScan(context: Context, uri: Uri) { + val scanIntent = Intent().apply { + @Suppress("Deprecation") + action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE + data = uri + } + context.sendBroadcast(scanIntent) + } + + } +}