Skip to content

Commit

Permalink
Add MIUI and HyperOS specific specs for ACS based force-stopping
Browse files Browse the repository at this point in the history
  • Loading branch information
d4rken committed Jan 19, 2025
1 parent d7e44d8 commit 527d9bf
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import eu.darken.sdmse.appcontrol.core.automation.specs.AppControlLabelDebugger
import eu.darken.sdmse.appcontrol.core.automation.specs.AppControlSpecGenerator
import eu.darken.sdmse.appcontrol.core.automation.specs.androidtv.AndroidTVSpecs
import eu.darken.sdmse.appcontrol.core.automation.specs.aosp.AOSPSpecs
import eu.darken.sdmse.appcontrol.core.automation.specs.hyperos.HyperOsSpecs
import eu.darken.sdmse.appcontrol.core.automation.specs.miui.MIUISpecs
import eu.darken.sdmse.appcontrol.core.automation.specs.samsung.SamsungSpecs
import eu.darken.sdmse.appcontrol.core.forcestop.ForceStopAutomationTask
import eu.darken.sdmse.automation.core.AutomationHost
Expand Down Expand Up @@ -66,7 +68,8 @@ class AppControlAutomation @AssistedInject constructor(
.onEach { log(TAG, VERBOSE) { "Loaded: $it" } }
.sortedByDescending { generator: AppControlSpecGenerator ->
when (generator) {
// is MIUISpecs -> 190
is MIUISpecs -> 190
is HyperOsSpecs -> 180
is SamsungSpecs -> 170
// is AlcatelSpecs -> 160
// is RealmeSpecs -> 150
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package eu.darken.sdmse.appcontrol.core.automation.specs.hyperos

import android.content.Context
import dagger.Reusable
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.sdmse.appcontrol.core.automation.specs.AppControlLabelSource
import eu.darken.sdmse.common.debug.logging.logTag
import eu.darken.sdmse.common.pkgs.toPkgId
import javax.inject.Inject

@Reusable
class HyperOsLabels @Inject constructor(
@ApplicationContext private val context: Context,
) : AppControlLabelSource {

fun getForceStopButtonDynamic(): Set<String> = setOf(
"force_stop",
).getAsStringResources(context, SETTINGS_PKG)

fun getForceStopDialogTitleDynamic(): Set<String> = setOf(
"force_stop_dlg_title",
).getAsStringResources(context, SETTINGS_PKG)

fun getForceStopDialogOkDynamic(): Set<String> = setOf(
"okay",
"dlg_ok",
).getAsStringResources(context, SETTINGS_PKG)

fun getForceStopDialogCancelDynamic(): Set<String> = setOf(
"cancel",
"dlg_cancel",
).getAsStringResources(context, SETTINGS_PKG)

companion object {
val SETTINGS_PKG = "com.miui.securitycenter".toPkgId()
val TAG: String = logTag("AppControl", "Automation", "HyperOs", "Labels")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package eu.darken.sdmse.appcontrol.core.automation.specs.hyperos

import android.view.accessibility.AccessibilityNodeInfo
import dagger.Binds
import dagger.Module
import dagger.Reusable
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoSet
import eu.darken.sdmse.R
import eu.darken.sdmse.appcontrol.core.automation.specs.AppControlSpecGenerator
import eu.darken.sdmse.automation.core.common.StepProcessor
import eu.darken.sdmse.automation.core.common.clickableParent
import eu.darken.sdmse.automation.core.common.crawl
import eu.darken.sdmse.automation.core.common.defaultClick
import eu.darken.sdmse.automation.core.common.defaultWindowFilter
import eu.darken.sdmse.automation.core.common.defaultWindowIntent
import eu.darken.sdmse.automation.core.common.getDefaultNodeRecovery
import eu.darken.sdmse.automation.core.common.getSysLocale
import eu.darken.sdmse.automation.core.common.pkgId
import eu.darken.sdmse.automation.core.common.textMatchesAny
import eu.darken.sdmse.automation.core.common.windowCriteriaAppIdentifier
import eu.darken.sdmse.automation.core.specs.AutomationExplorer
import eu.darken.sdmse.automation.core.specs.AutomationSpec
import eu.darken.sdmse.common.ca.toCaString
import eu.darken.sdmse.common.datastore.value
import eu.darken.sdmse.common.debug.Bugs
import eu.darken.sdmse.common.debug.logging.Logging.Priority.INFO
import eu.darken.sdmse.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.sdmse.common.debug.logging.log
import eu.darken.sdmse.common.debug.logging.logTag
import eu.darken.sdmse.common.device.DeviceDetective
import eu.darken.sdmse.common.device.RomType
import eu.darken.sdmse.common.funnel.IPCFunnel
import eu.darken.sdmse.common.pkgs.features.Installed
import eu.darken.sdmse.common.pkgs.toPkgId
import eu.darken.sdmse.common.progress.withProgress
import eu.darken.sdmse.main.core.GeneralSettings
import javax.inject.Inject

@Reusable
class HyperOsSpecs @Inject constructor(
private val ipcFunnel: IPCFunnel,
private val deviceDetective: DeviceDetective,
private val hyperOsLabels: HyperOsLabels,
private val generalSettings: GeneralSettings,
) : AppControlSpecGenerator {

override val tag: String = TAG

override suspend fun isResponsible(pkg: Installed): Boolean {
val romType = generalSettings.romTypeDetection.value()
if (romType == RomType.HYPEROS) return true
if (romType != RomType.AUTO) return false

return deviceDetective.getROMType() == RomType.HYPEROS
}

override suspend fun getForceStop(pkg: Installed): AutomationSpec = object : AutomationSpec.Explorer {
override val tag: String = TAG
override suspend fun createPlan(): suspend AutomationExplorer.Context.() -> Unit = {
mainPlan(pkg)
}
}

private val mainPlan: suspend AutomationExplorer.Context.(Installed) -> Unit = plan@{ pkg ->
log(TAG, INFO) { "Executing plan for ${pkg.installId} with context $this" }

val locale = getSysLocale()
val lang = locale.language
val script = locale.script

log(VERBOSE) { "Getting specs for ${pkg.packageName} (lang=$lang, script=$script)" }

val forceStopLabels = hyperOsLabels.getForceStopButtonDynamic()
var wasDisabled = false

run {
val step = StepProcessor.Step(
source = TAG,
descriptionInternal = "Force stop button",
label = R.string.appcontrol_automation_progress_find_force_stop.toCaString(forceStopLabels),
windowIntent = defaultWindowIntent(pkg),
windowEventFilter = defaultWindowFilter(SETTINGS_PKG),
windowNodeTest = windowCriteriaAppIdentifier(SETTINGS_PKG, ipcFunnel, pkg),
nodeTest = storageFilter@{ node ->
node.textMatchesAny(forceStopLabels)
},
nodeRecovery = getDefaultNodeRecovery(pkg),
nodeMapping = clickableParent(),
action = defaultClick(onDisabled = {
wasDisabled = true
true
}),
)
stepper.withProgress(this) { process(step) }
}

if (wasDisabled) {
log(TAG) { "Force stop button was disabled, app is already stopped." }
return@plan
}

run {
val titleLbl = hyperOsLabels.getForceStopDialogTitleDynamic() + forceStopLabels.map { "$it?" }
val okLbl = hyperOsLabels.getForceStopDialogOkDynamic()
val cancelLbl = hyperOsLabels.getForceStopDialogCancelDynamic()

val windowCriteria = fun(node: AccessibilityNodeInfo): Boolean {
if (node.pkgId != SETTINGS_PKG) return false
return node.crawl().map { it.node }.any { subNode ->
return@any subNode.textMatchesAny(titleLbl)
}
}

val buttonFilter = fun(node: AccessibilityNodeInfo): Boolean = when (Bugs.isDryRun) {
true -> node.textMatchesAny(cancelLbl)
false -> node.textMatchesAny(okLbl)
}

val step = StepProcessor.Step(
source = TAG,
descriptionInternal = "Confirm force stop",
label = R.string.appcleaner_automation_progress_find_ok_confirmation.toCaString(titleLbl + okLbl),
windowNodeTest = windowCriteria,
nodeTest = buttonFilter,
nodeMapping = clickableParent(),
action = defaultClick(),
)
stepper.withProgress(this) { process(step) }
}
}

@Module @InstallIn(SingletonComponent::class)
abstract class DIM {
@Binds @IntoSet abstract fun mod(mod: HyperOsSpecs): AppControlSpecGenerator
}

companion object {
val SETTINGS_PKG = "com.miui.securitycenter".toPkgId()

val TAG: String = logTag("AppControl", "Automation", "HyperOs", "Specs")
}

}

0 comments on commit 527d9bf

Please sign in to comment.