File Operations Notification

This commit is contained in:
Matéo Duparc 2020-12-29 18:39:43 +01:00
parent 754ef3bc5a
commit f00901a717
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
2 changed files with 97 additions and 28 deletions

View File

@ -1,6 +1,10 @@
package sushi.hardcore.droidfs package sushi.hardcore.droidfs
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service import android.app.Service
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.* import android.os.*
@ -13,10 +17,16 @@ import sushi.hardcore.droidfs.util.Wiper
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
class FileOperationService : Service() { class FileOperationService : Service() {
companion object {
const val NOTIFICATION_ID = 1
const val NOTIFICATION_CHANNEL_ID = "FileOperations"
}
private val binder = LocalBinder() private val binder = LocalBinder()
private lateinit var gocryptfsVolume: GocryptfsVolume private lateinit var gocryptfsVolume: GocryptfsVolume
private lateinit var notificationManager: NotificationManager
inner class LocalBinder : Binder() { inner class LocalBinder : Binder() {
fun getService(): FileOperationService = this@FileOperationService fun getService(): FileOperationService = this@FileOperationService
@ -29,6 +39,35 @@ class FileOperationService : Service() {
return binder return binder
} }
private fun showNotification(message: String, total: Int): Notification.Builder {
if (!::notificationManager.isInitialized){
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
val notificationBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, getString(R.string.file_operations), NotificationManager.IMPORTANCE_LOW)
notificationManager.createNotificationChannel(channel)
Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
} else {
Notification.Builder(this)
}
notificationBuilder.setOngoing(true)
.setContentTitle(getString(R.string.file_op_notification_title))
.setContentText(message)
.setSmallIcon(R.mipmap.icon_launcher)
.setProgress(total, 0, false)
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
return notificationBuilder
}
private fun updateNotificationProgress(notificationBuilder: Notification.Builder, progress: Int, total: Int){
notificationBuilder.setProgress(total, progress, false)
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
}
private fun cancelNotification(){
notificationManager.cancel(NOTIFICATION_ID)
}
private fun copyFile(srcPath: String, dstPath: String, remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume): Boolean { private fun copyFile(srcPath: String, dstPath: String, remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume): Boolean {
var success = true var success = true
val srcHandleId = remoteGocryptfsVolume.openReadMode(srcPath) val srcHandleId = remoteGocryptfsVolume.openReadMode(srcPath)
@ -60,55 +99,66 @@ class FileOperationService : Service() {
fun copyElements(items: ArrayList<OperationFile>, remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume, callback: (String?) -> Unit){ fun copyElements(items: ArrayList<OperationFile>, remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume, callback: (String?) -> Unit){
Thread { Thread {
val notificationBuilder = showNotification(getString(R.string.file_op_copy_msg), items.size)
var failedItem: String? = null var failedItem: String? = null
for (item in items){ for (i in 0 until items.size){
if (item.explorerElement.isDirectory){ if (items[i].explorerElement.isDirectory){
if (!gocryptfsVolume.pathExists(item.dstPath!!)) { if (!gocryptfsVolume.pathExists(items[i].dstPath!!)) {
if (!gocryptfsVolume.mkdir(item.dstPath!!)) { if (!gocryptfsVolume.mkdir(items[i].dstPath!!)) {
failedItem = item.explorerElement.fullPath failedItem = items[i].explorerElement.fullPath
} }
} }
} else { } else {
if (!copyFile(item.explorerElement.fullPath, item.dstPath!!, remoteGocryptfsVolume)){ if (!copyFile(items[i].explorerElement.fullPath, items[i].dstPath!!, remoteGocryptfsVolume)){
failedItem = item.explorerElement.fullPath failedItem = items[i].explorerElement.fullPath
} }
} }
if (failedItem != null){ if (failedItem == null){
updateNotificationProgress(notificationBuilder, i, items.size)
} else {
break break
} }
} }
cancelNotification()
callback(failedItem) callback(failedItem)
}.start() }.start()
} }
fun moveElements(items: ArrayList<OperationFile>, callback: (String?) -> Unit){ fun moveElements(items: ArrayList<OperationFile>, callback: (String?) -> Unit){
Thread { Thread {
val notificationBuilder = showNotification(getString(R.string.file_op_move_msg), items.size)
val mergedFolders = ArrayList<String>() val mergedFolders = ArrayList<String>()
var failedItem: String? = null var failedItem: String? = null
for (item in items){ for (i in 0 until items.size){
if (item.explorerElement.isDirectory && gocryptfsVolume.pathExists(item.dstPath!!)){ //folder will be merged if (items[i].explorerElement.isDirectory && gocryptfsVolume.pathExists(items[i].dstPath!!)){ //folder will be merged
mergedFolders.add(item.explorerElement.fullPath) mergedFolders.add(items[i].explorerElement.fullPath)
} else { } else {
if (!gocryptfsVolume.rename(item.explorerElement.fullPath, item.dstPath!!)){ if (!gocryptfsVolume.rename(items[i].explorerElement.fullPath, items[i].dstPath!!)){
failedItem = item.explorerElement.fullPath failedItem = items[i].explorerElement.fullPath
break break
} else {
updateNotificationProgress(notificationBuilder, i, items.size)
} }
} }
} }
if (failedItem == null){ if (failedItem == null){
for (path in mergedFolders) { for (i in 0 until mergedFolders.size) {
if (!gocryptfsVolume.rmdir(path)){ if (!gocryptfsVolume.rmdir(mergedFolders[i])){
failedItem = path failedItem = mergedFolders[i]
break break
} else {
updateNotificationProgress(notificationBuilder, items.size-(mergedFolders.size-i), items.size)
} }
} }
} }
cancelNotification()
callback(failedItem) callback(failedItem)
}.start() }.start()
} }
fun importFilesFromUris(items: ArrayList<OperationFile>, uris: List<Uri>, callback: (String?) -> Unit){ fun importFilesFromUris(items: ArrayList<OperationFile>, uris: List<Uri>, callback: (String?) -> Unit){
Thread { Thread {
val notificationBuilder = showNotification(getString(R.string.file_op_import_msg), items.size)
var failedIndex = -1 var failedIndex = -1
for (i in 0 until items.size) { for (i in 0 until items.size) {
try { try {
@ -118,12 +168,16 @@ class FileOperationService : Service() {
} catch (e: FileNotFoundException){ } catch (e: FileNotFoundException){
failedIndex = i failedIndex = i
} }
if (failedIndex != -1){ if (failedIndex == -1) {
updateNotificationProgress(notificationBuilder, i, items.size)
} else {
cancelNotification()
callback(uris[failedIndex].toString()) callback(uris[failedIndex].toString())
break break
} }
} }
if (failedIndex == -1){ if (failedIndex == -1){
cancelNotification()
callback(null) callback(null)
} }
}.start() }.start()
@ -131,13 +185,17 @@ class FileOperationService : Service() {
fun wipeUris(uris: List<Uri>, callback: (String?) -> Unit){ fun wipeUris(uris: List<Uri>, callback: (String?) -> Unit){
Thread { Thread {
val notificationBuilder = showNotification(getString(R.string.file_op_wiping_msg), uris.size)
var errorMsg: String? = null var errorMsg: String? = null
for (uri in uris) { for (i in uris.indices) {
errorMsg = Wiper.wipe(this, uri) errorMsg = Wiper.wipe(this, uris[i])
if (errorMsg != null) { if (errorMsg == null) {
updateNotificationProgress(notificationBuilder, i, uris.size)
} else {
break break
} }
} }
cancelNotification()
callback(errorMsg) callback(errorMsg)
}.start() }.start()
} }
@ -146,10 +204,10 @@ class FileOperationService : Service() {
val outputStream = treeDocumentFile.createFile("*/*", File(srcPath).name)?.uri?.let { val outputStream = treeDocumentFile.createFile("*/*", File(srcPath).name)?.uri?.let {
contentResolver.openOutputStream(it) contentResolver.openOutputStream(it)
} }
return if (outputStream != null){ return if (outputStream == null) {
gocryptfsVolume.exportFile(srcPath, outputStream)
} else {
false false
} else {
gocryptfsVolume.exportFile(srcPath, outputStream)
} }
} }
@ -176,17 +234,21 @@ class FileOperationService : Service() {
Thread { Thread {
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
DocumentFile.fromTreeUri(this, uri)?.let { treeDocumentFile -> DocumentFile.fromTreeUri(this, uri)?.let { treeDocumentFile ->
val notificationBuilder = showNotification(getString(R.string.file_op_export_msg), items.size)
var failedItem: String? = null var failedItem: String? = null
for (element in items) { for (i in items.indices) {
failedItem = if (element.isDirectory) { failedItem = if (items[i].isDirectory) {
recursiveExportDirectory(element.fullPath, treeDocumentFile) recursiveExportDirectory(items[i].fullPath, treeDocumentFile)
} else { } else {
if (exportFileInto(element.fullPath, treeDocumentFile)) null else element.fullPath if (exportFileInto(items[i].fullPath, treeDocumentFile)) null else items[i].fullPath
} }
if (failedItem != null) { if (failedItem == null) {
updateNotificationProgress(notificationBuilder, i, items.size)
} else {
break break
} }
} }
cancelNotification()
callback(failedItem) callback(failedItem)
} }
}.start() }.start()

View File

@ -186,4 +186,11 @@
<string name="hidden_volume_warning">Hidden volumes are stored in the app\'s internal storage. Other apps can\'t see these volumes without root access. However, if you uninstall DroidFS or clear data of the app, all your hidden volumes will be LOST. Be sure to make backups !</string> <string name="hidden_volume_warning">Hidden volumes are stored in the app\'s internal storage. Other apps can\'t see these volumes without root access. However, if you uninstall DroidFS or clear data of the app, all your hidden volumes will be LOST. Be sure to make backups !</string>
<string name="camera_perm_needed">Camera permission is needed to take photo.</string> <string name="camera_perm_needed">Camera permission is needed to take photo.</string>
<string name="choose_resolution">Choose a resolution</string> <string name="choose_resolution">Choose a resolution</string>
<string name="file_operations">File Operations</string>
<string name="file_op_notification_title">File Operations running…</string>
<string name="file_op_copy_msg">Copying files…</string>
<string name="file_op_import_msg">Importing files…</string>
<string name="file_op_export_msg">Exporting files…</string>
<string name="file_op_move_msg">Moving files…</string>
<string name="file_op_wiping_msg">Wiping files…</string>
</resources> </resources>