Adding cancel button to file operation notifications

This commit is contained in:
Matéo Duparc 2020-12-31 15:57:52 +01:00
parent 0a3cb8903f
commit 966e78b66f
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
3 changed files with 82 additions and 6 deletions

View File

@ -88,6 +88,12 @@
<service android:name=".file_operations.FileOperationService" android:exported="false"/> <service android:name=".file_operations.FileOperationService" android:exported="false"/>
<receiver android:name=".file_operations.NotificationBroadcastReceiver" android:exported="false">
<intent-filter>
<action android:name="file_operation_cancel"/>
</intent-filter>
</receiver>
<provider <provider
android:name=".content_providers.RestrictedFileProvider" android:name=".content_providers.RestrictedFileProvider"
android:authorities="${applicationId}.temporary_provider" android:authorities="${applicationId}.temporary_provider"

View File

@ -1,11 +1,9 @@
package sushi.hardcore.droidfs.file_operations package sushi.hardcore.droidfs.file_operations
import android.app.Notification import android.app.*
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Icon
import android.net.Uri import android.net.Uri
import android.os.* import android.os.*
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
@ -20,11 +18,13 @@ import java.io.FileNotFoundException
class FileOperationService : Service() { class FileOperationService : Service() {
companion object { companion object {
const val NOTIFICATION_CHANNEL_ID = "FileOperations" const val NOTIFICATION_CHANNEL_ID = "FileOperations"
const val ACTION_CANCEL = "file_operation_cancel"
} }
private val binder = LocalBinder() private val binder = LocalBinder()
private lateinit var gocryptfsVolume: GocryptfsVolume private lateinit var gocryptfsVolume: GocryptfsVolume
private lateinit var notificationManager: NotificationManager private lateinit var notificationManager: NotificationManager
private var notifications = HashMap<Int, Boolean>()
private var lastNotificationId = 0 private var lastNotificationId = 0
inner class LocalBinder : Binder() { inner class LocalBinder : Binder() {
@ -39,6 +39,7 @@ class FileOperationService : Service() {
} }
private fun showNotification(message: Int, total: Int): FileOperationNotification { private fun showNotification(message: Int, total: Int): FileOperationNotification {
++lastNotificationId
if (!::notificationManager.isInitialized){ if (!::notificationManager.isInitialized){
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
} }
@ -49,13 +50,35 @@ class FileOperationService : Service() {
} else { } else {
Notification.Builder(this) Notification.Builder(this)
} }
val cancelIntent = Intent(this, NotificationBroadcastReceiver::class.java).apply {
val bundle = Bundle()
bundle.putBinder("binder", LocalBinder())
bundle.putInt("notificationId", lastNotificationId)
putExtra("bundle", bundle)
action = ACTION_CANCEL
}
val cancelPendingIntent = PendingIntent.getBroadcast(this, 0, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val notificationAction = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Notification.Action.Builder(
Icon.createWithResource(this, R.drawable.icon_close),
getString(R.string.cancel),
cancelPendingIntent
)
} else {
Notification.Action.Builder(
R.drawable.icon_close,
getString(R.string.cancel),
cancelPendingIntent
)
}
notificationBuilder notificationBuilder
.setOngoing(true)
.setContentTitle(getString(message)) .setContentTitle(getString(message))
.setContentText("0/$total") .setContentText("0/$total")
.setSmallIcon(R.mipmap.icon_launcher) .setSmallIcon(R.mipmap.icon_launcher)
.setOngoing(true)
.setProgress(total, 0, false) .setProgress(total, 0, false)
++lastNotificationId .addAction(notificationAction.build())
notifications[lastNotificationId] = false
notificationManager.notify(lastNotificationId, notificationBuilder.build()) notificationManager.notify(lastNotificationId, notificationBuilder.build())
return FileOperationNotification(notificationBuilder, lastNotificationId) return FileOperationNotification(notificationBuilder, lastNotificationId)
} }
@ -71,6 +94,10 @@ class FileOperationService : Service() {
notificationManager.cancel(notification.notificationId) notificationManager.cancel(notification.notificationId)
} }
fun cancelOperation(notificationId: Int){
notifications[notificationId] = true
}
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)
@ -105,6 +132,10 @@ class FileOperationService : Service() {
val notification = showNotification(R.string.file_op_copy_msg, items.size) val notification = showNotification(R.string.file_op_copy_msg, items.size)
var failedItem: String? = null var failedItem: String? = null
for (i in 0 until items.size){ for (i in 0 until items.size){
if (notifications[notification.notificationId]!!){
cancelNotification(notification)
return@Thread
}
if (items[i].explorerElement.isDirectory){ if (items[i].explorerElement.isDirectory){
if (!gocryptfsVolume.pathExists(items[i].dstPath!!)) { if (!gocryptfsVolume.pathExists(items[i].dstPath!!)) {
if (!gocryptfsVolume.mkdir(items[i].dstPath!!)) { if (!gocryptfsVolume.mkdir(items[i].dstPath!!)) {
@ -133,6 +164,10 @@ class FileOperationService : Service() {
val mergedFolders = ArrayList<String>() val mergedFolders = ArrayList<String>()
var failedItem: String? = null var failedItem: String? = null
for (i in 0 until items.size){ for (i in 0 until items.size){
if (notifications[notification.notificationId]!!){
cancelNotification(notification)
return@Thread
}
if (items[i].explorerElement.isDirectory && gocryptfsVolume.pathExists(items[i].dstPath!!)){ //folder will be merged if (items[i].explorerElement.isDirectory && gocryptfsVolume.pathExists(items[i].dstPath!!)){ //folder will be merged
mergedFolders.add(items[i].explorerElement.fullPath) mergedFolders.add(items[i].explorerElement.fullPath)
} else { } else {
@ -146,6 +181,10 @@ class FileOperationService : Service() {
} }
if (failedItem == null){ if (failedItem == null){
for (i in 0 until mergedFolders.size) { for (i in 0 until mergedFolders.size) {
if (notifications[notification.notificationId]!!){
cancelNotification(notification)
return@Thread
}
if (!gocryptfsVolume.rmdir(mergedFolders[i])){ if (!gocryptfsVolume.rmdir(mergedFolders[i])){
failedItem = mergedFolders[i] failedItem = mergedFolders[i]
break break
@ -164,6 +203,10 @@ class FileOperationService : Service() {
val notification = showNotification(R.string.file_op_import_msg, items.size) val notification = showNotification(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) {
if (notifications[notification.notificationId]!!){
cancelNotification(notification)
return@Thread
}
try { try {
if (!gocryptfsVolume.importFile(this, uris[i], items[i].dstPath!!)){ if (!gocryptfsVolume.importFile(this, uris[i], items[i].dstPath!!)){
failedIndex = i failedIndex = i
@ -191,6 +234,10 @@ class FileOperationService : Service() {
val notification = showNotification(R.string.file_op_wiping_msg, uris.size) val notification = showNotification(R.string.file_op_wiping_msg, uris.size)
var errorMsg: String? = null var errorMsg: String? = null
for (i in uris.indices) { for (i in uris.indices) {
if (notifications[notification.notificationId]!!){
cancelNotification(notification)
return@Thread
}
errorMsg = Wiper.wipe(this, uris[i]) errorMsg = Wiper.wipe(this, uris[i])
if (errorMsg == null) { if (errorMsg == null) {
updateNotificationProgress(notification, i, uris.size) updateNotificationProgress(notification, i, uris.size)
@ -240,6 +287,10 @@ class FileOperationService : Service() {
val notification = showNotification(R.string.file_op_export_msg, items.size) val notification = showNotification(R.string.file_op_export_msg, items.size)
var failedItem: String? = null var failedItem: String? = null
for (i in items.indices) { for (i in items.indices) {
if (notifications[notification.notificationId]!!){
cancelNotification(notification)
return@Thread
}
failedItem = if (items[i].isDirectory) { failedItem = if (items[i].isDirectory) {
recursiveExportDirectory(items[i].fullPath, treeDocumentFile) recursiveExportDirectory(items[i].fullPath, treeDocumentFile)
} else { } else {

View File

@ -0,0 +1,19 @@
package sushi.hardcore.droidfs.file_operations
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
class NotificationBroadcastReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == FileOperationService.ACTION_CANCEL){
intent.getBundleExtra("bundle")?.let { bundle ->
(bundle.getBinder("binder") as FileOperationService.LocalBinder?)?.let { binder ->
val notificationId = bundle.getInt("notificationId")
val service = binder.getService()
service.cancelOperation(notificationId)
}
}
}
}
}