Skip to content

Commit

Permalink
Automation: Better return behavior after accessibility operations
Browse files Browse the repository at this point in the history
Instead of back-button presses (i.e. `GLOBAL_ACTION_BACK`) that don't work reliably on Android 14 anymore, we use intent navigation to return to SD Maid at the end.
So far, this proved reliable on API29, API31 and API34.
Opening the system settings activity was quick, and returning to SD Maid was quick too. No extra activity remained in the task-switcher.
  • Loading branch information
d4rken committed May 1, 2024
1 parent 1a1017f commit 1f9c090
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package eu.darken.sdmse.appcleaner.core.automation

import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.view.accessibility.AccessibilityEvent
import dagger.Binds
Expand Down Expand Up @@ -32,6 +31,7 @@ import eu.darken.sdmse.automation.core.AutomationModule
import eu.darken.sdmse.automation.core.AutomationTask
import eu.darken.sdmse.automation.core.errors.ScreenUnavailableException
import eu.darken.sdmse.automation.core.errors.UserCancelledAutomationException
import eu.darken.sdmse.automation.core.returnToSDMaid
import eu.darken.sdmse.automation.core.specs.AutomationExplorer
import eu.darken.sdmse.automation.core.specs.AutomationSpec
import eu.darken.sdmse.common.ca.CaString
Expand All @@ -48,12 +48,8 @@ import eu.darken.sdmse.common.progress.*
import eu.darken.sdmse.common.user.UserManager2
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import javax.inject.Provider
import kotlin.coroutines.EmptyCoroutineContext

class ClearCacheModule @AssistedInject constructor(
@Assisted automationHost: AutomationHost,
Expand Down Expand Up @@ -170,15 +166,7 @@ class ClearCacheModule @AssistedInject constructor(
}

// If we aborted due to an exception and the reason is "User has cancelled", then still clean up
withContext(if (cancelledByUser) NonCancellable else EmptyCoroutineContext) {
val backAction1 = host.service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
log(TAG, VERBOSE) { "Was back1 successful=$backAction1" }

delay(500)

val backAction2 = host.service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
log(TAG, VERBOSE) { "Was back2 successful=$backAction2" }
}
returnToSDMaid(cancelledByUser)

return ClearCacheTask.Result(
successful = successful,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package eu.darken.sdmse.appcontrol.core.automation

import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.view.accessibility.AccessibilityEvent
import dagger.Binds
Expand All @@ -22,6 +21,8 @@ import eu.darken.sdmse.automation.core.AutomationHost
import eu.darken.sdmse.automation.core.AutomationModule
import eu.darken.sdmse.automation.core.AutomationTask
import eu.darken.sdmse.automation.core.errors.ScreenUnavailableException
import eu.darken.sdmse.automation.core.errors.UserCancelledAutomationException
import eu.darken.sdmse.automation.core.returnToSDMaid
import eu.darken.sdmse.automation.core.specs.AutomationExplorer
import eu.darken.sdmse.automation.core.specs.AutomationSpec
import eu.darken.sdmse.common.ca.CaString
Expand Down Expand Up @@ -108,6 +109,7 @@ class AppControlAutomation @AssistedInject constructor(

updateProgressCount(Progress.Count.Percent(task.targets.size))

var cancelledByUser = false
val currentUserHandle = userManager2.currentUser().handle
for (target in task.targets) {
if (target.userHandle != currentUserHandle) {
Expand Down Expand Up @@ -137,8 +139,17 @@ class AppControlAutomation @AssistedInject constructor(
log(TAG, WARN) { "Timeout while processing $installed" }
failed.add(target)
} catch (e: CancellationException) {
log(TAG, WARN) { "We were cancelled" }
throw e
log(TAG, WARN) { "We were cancelled: ${e.asLog()}" }
updateProgressPrimary(eu.darken.sdmse.common.R.string.general_cancel_action)
updateProgressSecondary(CaString.EMPTY)
updateProgressCount(Progress.Count.Indeterminate())
if (e is UserCancelledAutomationException) {
log(TAG, INFO) { "User has cancelled automation process, aborting..." }
cancelledByUser = true
break
} else {
throw e
}
} catch (e: Exception) {
log(TAG, WARN) { "Failure for $target: ${e.asLog()}" }
if (e is UnsupportedOperationException) throw e
Expand All @@ -150,8 +161,8 @@ class AppControlAutomation @AssistedInject constructor(

delay(250)

val backAction1 = host.service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
log(TAG, VERBOSE) { "Was back1 successful=$backAction1" }
// If we aborted due to an exception and the reason is "User has cancelled", then still clean up
returnToSDMaid(cancelledByUser)

return ForceStopAutomationTask.Result(
successful = successful,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package eu.darken.sdmse.automation.core

import android.content.Intent
import android.view.accessibility.AccessibilityNodeInfo
import eu.darken.sdmse.common.debug.Bugs
import eu.darken.sdmse.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.sdmse.common.debug.logging.log
import eu.darken.sdmse.main.ui.MainActivity
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import kotlin.coroutines.EmptyCoroutineContext

suspend fun AutomationManager.canUseAcsNow(): Boolean = useAcs.first()

Expand All @@ -24,4 +29,16 @@ suspend fun AutomationHost.waitForWindowRoot(delayMs: Long = 250): Accessibility
}

return root ?: throw CancellationException("Cancelled while waiting for windowRoot")
}

suspend fun AutomationModule.returnToSDMaid(
userCancelled: Boolean
) = withContext(if (userCancelled) NonCancellable else EmptyCoroutineContext) {
val returnIntern = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_NO_ANIMATION
}
context.startActivity(returnIntern)
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ private val TAG: String = logTag("Automation", "Crawler", "Common")
fun AutomationExplorer.Context.defaultWindowIntent(
pkgInfo: Installed
): Intent = pkgInfo.getSettingsIntent(androidContext).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_NO_ANIMATION
}

Expand Down

0 comments on commit 1f9c090

Please sign in to comment.