Add FGS task manager manipulation type

This commit is contained in:
Jonas Lochmann 2022-07-11 02:00:00 +02:00
parent 5f61efbd14
commit 7021a9c699
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
19 changed files with 1461 additions and 82 deletions

File diff suppressed because it is too large Load diff

View file

@ -293,6 +293,12 @@ object DatabaseMigrations {
} }
} }
private val MIGRATE_TO_V42 = object: Migration(41, 42) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE device ADD COLUMN manipulation_flags INTEGER NOT NULL DEFAULT 0")
}
}
val ALL = arrayOf( val ALL = arrayOf(
MIGRATE_TO_V2, MIGRATE_TO_V2,
MIGRATE_TO_V3, MIGRATE_TO_V3,
@ -333,6 +339,7 @@ object DatabaseMigrations {
MIGRATE_TO_V38, MIGRATE_TO_V38,
MIGRATE_TO_V39, MIGRATE_TO_V39,
MIGRATE_TO_V40, MIGRATE_TO_V40,
MIGRATE_TO_V41 MIGRATE_TO_V41,
MIGRATE_TO_V42
) )
} }

View file

@ -53,7 +53,7 @@ import java.util.concurrent.TimeUnit
CategoryNetworkId::class, CategoryNetworkId::class,
ChildTask::class, ChildTask::class,
CategoryTimeWarning::class CategoryTimeWarning::class
], version = 41) ], version = 42)
abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database { abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database {
companion object { companion object {
private val lock = Object() private val lock = Object()

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -92,7 +92,9 @@ data class Device(
@ColumnInfo(name = "enable_activity_level_blocking") @ColumnInfo(name = "enable_activity_level_blocking")
val enableActivityLevelBlocking: Boolean, val enableActivityLevelBlocking: Boolean,
@ColumnInfo(name = "q_or_later") @ColumnInfo(name = "q_or_later")
val qOrLater: Boolean val qOrLater: Boolean,
@ColumnInfo(name = "manipulation_flags")
val manipulationFlags: Long
): JsonSerializable { ): JsonSerializable {
companion object { companion object {
private const val ID = "id" private const val ID = "id"
@ -126,6 +128,7 @@ data class Device(
private const val WAS_ACCESSIBILITY_SERVICE_ENABLED = "wase" private const val WAS_ACCESSIBILITY_SERVICE_ENABLED = "wase"
private const val ENABLE_ACTIVITY_LEVEL_BLOCKING = "ealb" private const val ENABLE_ACTIVITY_LEVEL_BLOCKING = "ealb"
private const val Q_OR_LATER = "qol" private const val Q_OR_LATER = "qol"
private const val MANIPULATION_FLAGS = "mf"
fun parse(reader: JsonReader): Device { fun parse(reader: JsonReader): Device {
var id: String? = null var id: String? = null
@ -159,6 +162,7 @@ data class Device(
var wasAccessibilityServiceEnabled = false var wasAccessibilityServiceEnabled = false
var enableActivityLevelBlocking = false var enableActivityLevelBlocking = false
var qOrLater = false var qOrLater = false
var manipulationFlags = 0L
reader.beginObject() reader.beginObject()
@ -195,6 +199,7 @@ data class Device(
WAS_ACCESSIBILITY_SERVICE_ENABLED -> wasAccessibilityServiceEnabled = reader.nextBoolean() WAS_ACCESSIBILITY_SERVICE_ENABLED -> wasAccessibilityServiceEnabled = reader.nextBoolean()
ENABLE_ACTIVITY_LEVEL_BLOCKING -> enableActivityLevelBlocking = reader.nextBoolean() ENABLE_ACTIVITY_LEVEL_BLOCKING -> enableActivityLevelBlocking = reader.nextBoolean()
Q_OR_LATER -> qOrLater = reader.nextBoolean() Q_OR_LATER -> qOrLater = reader.nextBoolean()
MANIPULATION_FLAGS -> manipulationFlags = reader.nextLong()
else -> reader.skipValue() else -> reader.skipValue()
} }
} }
@ -232,7 +237,8 @@ data class Device(
accessibilityServiceEnabled = accessibilityServiceEnabled, accessibilityServiceEnabled = accessibilityServiceEnabled,
wasAccessibilityServiceEnabled = wasAccessibilityServiceEnabled, wasAccessibilityServiceEnabled = wasAccessibilityServiceEnabled,
enableActivityLevelBlocking = enableActivityLevelBlocking, enableActivityLevelBlocking = enableActivityLevelBlocking,
qOrLater = qOrLater qOrLater = qOrLater,
manipulationFlags = manipulationFlags
) )
} }
} }
@ -303,6 +309,7 @@ data class Device(
writer.name(WAS_ACCESSIBILITY_SERVICE_ENABLED).value(wasAccessibilityServiceEnabled) writer.name(WAS_ACCESSIBILITY_SERVICE_ENABLED).value(wasAccessibilityServiceEnabled)
writer.name(ENABLE_ACTIVITY_LEVEL_BLOCKING).value(enableActivityLevelBlocking) writer.name(ENABLE_ACTIVITY_LEVEL_BLOCKING).value(enableActivityLevelBlocking)
writer.name(Q_OR_LATER).value(qOrLater) writer.name(Q_OR_LATER).value(qOrLater)
writer.name(MANIPULATION_FLAGS).value(manipulationFlags)
writer.endObject() writer.endObject()
} }
@ -331,7 +338,7 @@ data class Device(
manipulationOfAccessibilityService manipulationOfAccessibilityService
@Transient @Transient
val hasAnyManipulation = hasActiveManipulationWarning || hadManipulation val hasAnyManipulation = hasActiveManipulationWarning || hadManipulation || manipulationFlags != 0L
@Transient @Transient
val missingPermissionAtQOrLater = qOrLater && val missingPermissionAtQOrLater = qOrLater &&
@ -381,4 +388,8 @@ object HadManipulationFlag {
const val APP_VERSION = 1L shl 3 const val APP_VERSION = 1L shl 3
const val OVERLAY_PERMISSION = 1L shl 4 const val OVERLAY_PERMISSION = 1L shl 4
const val ACCESSIBILITY_SERVICE = 1L shl 5 const val ACCESSIBILITY_SERVICE = 1L shl 5
}
object ManipulationFlag {
const val USED_FGS_KILLER = 1L shl 0
} }

View file

@ -89,7 +89,7 @@ abstract class PlatformIntegration(
confirmationLevel: SystemPermissionConfirmationLevel = SystemPermissionConfirmationLevel.None confirmationLevel: SystemPermissionConfirmationLevel = SystemPermissionConfirmationLevel.None
): Boolean ): Boolean
abstract fun getExitLog(): List<ExitLogItem> abstract fun getExitLog(length: Int): List<ExitLogItem>
var installedAppsChangeListener: Runnable? = null var installedAppsChangeListener: Runnable? = null
var systemClockChangeListener: Runnable? = null var systemClockChangeListener: Runnable? = null

View file

@ -815,9 +815,9 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
} }
} }
override fun getExitLog(): List<ExitLogItem> { override fun getExitLog(length: Int): List<ExitLogItem> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
activityManager.getHistoricalProcessExitReasons(context.packageName, 0, 0) activityManager.getHistoricalProcessExitReasons(context.packageName, 0, length)
.map { ExitLogItem.fromApplicationExitInfo(it) } .map { ExitLogItem.fromApplicationExitInfo(it) }
} else emptyList() } else emptyList()
} }

View file

@ -190,5 +190,5 @@ class DummyIntegration(
confirmationLevel: SystemPermissionConfirmationLevel confirmationLevel: SystemPermissionConfirmationLevel
): Boolean = false ): Boolean = false
override fun getExitLog(): List<ExitLogItem> = emptyList() override fun getExitLog(length: Int): List<ExitLogItem> = emptyList()
} }

View file

@ -109,7 +109,8 @@ class AppSetupLogic(private val appLogic: AppLogic) {
accessibilityServiceEnabled = false, accessibilityServiceEnabled = false,
wasAccessibilityServiceEnabled = false, wasAccessibilityServiceEnabled = false,
enableActivityLevelBlocking = false, enableActivityLevelBlocking = false,
qOrLater = AndroidVersion.qOrLater qOrLater = AndroidVersion.qOrLater,
manipulationFlags = 0
) )
appLogic.database.device().addDeviceSync(device) appLogic.database.device().addDeviceSync(device)

View file

@ -26,16 +26,14 @@ import io.timelimit.android.coroutines.runAsync
import io.timelimit.android.coroutines.runAsyncExpectForever import io.timelimit.android.coroutines.runAsyncExpectForever
import io.timelimit.android.data.backup.DatabaseBackup import io.timelimit.android.data.backup.DatabaseBackup
import io.timelimit.android.data.model.ExperimentalFlags import io.timelimit.android.data.model.ExperimentalFlags
import io.timelimit.android.data.model.ManipulationFlag
import io.timelimit.android.data.model.UserType import io.timelimit.android.data.model.UserType
import io.timelimit.android.data.model.derived.UserRelatedData import io.timelimit.android.data.model.derived.UserRelatedData
import io.timelimit.android.date.DateInTimezone import io.timelimit.android.date.DateInTimezone
import io.timelimit.android.date.getMinuteOfWeek import io.timelimit.android.date.getMinuteOfWeek
import io.timelimit.android.extensions.MinuteOfDay import io.timelimit.android.extensions.MinuteOfDay
import io.timelimit.android.extensions.nextBlockedMinuteOfWeek import io.timelimit.android.extensions.nextBlockedMinuteOfWeek
import io.timelimit.android.integration.platform.AppStatusMessage import io.timelimit.android.integration.platform.*
import io.timelimit.android.integration.platform.ForegroundApp
import io.timelimit.android.integration.platform.NetworkId
import io.timelimit.android.integration.platform.ProtectionLevel
import io.timelimit.android.integration.platform.android.AccessibilityService import io.timelimit.android.integration.platform.android.AccessibilityService
import io.timelimit.android.livedata.* import io.timelimit.android.livedata.*
import io.timelimit.android.logic.blockingreason.AppBaseHandling import io.timelimit.android.logic.blockingreason.AppBaseHandling
@ -76,6 +74,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
runAsyncExpectForever { syncDeviceStatusLoop() } runAsyncExpectForever { syncDeviceStatusLoop() }
runAsyncExpectForever { backupDatabaseLoop() } runAsyncExpectForever { backupDatabaseLoop() }
runAsyncExpectForever { annoyUserOnManipulationLoop() } runAsyncExpectForever { annoyUserOnManipulationLoop() }
runAsync { checkForceKilled() }
runAsync { runAsync {
// this is effective after an reboot // this is effective after an reboot
@ -924,6 +923,33 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
} }
} }
private suspend fun checkForceKilled() {
appLogic.platformIntegration.getExitLog(1).singleOrNull()?.let { item ->
if (
item.reason == ExitReason.UserRequest &&
item.description != null &&
item.description.startsWith("fully stop ") &&
item.description.endsWith("by user request")
) {
appLogic.isInitialized.waitUntilValueMatches { it == true }
try {
ApplyActionUtil.applyAppLogicAction(
action = UpdateDeviceStatusAction.empty.copy(
addedManipulationFlags = ManipulationFlag.USED_FGS_KILLER
),
appLogic = appLogic,
ignoreIfDeviceIsNotConfigured = true
)
} catch (ex: Exception) {
if (BuildConfig.DEBUG) {
Log.w(LOG_TAG, "could not save a forced kill notification", ex)
}
}
}
}
}
private val syncDeviceStatusLock = Mutex() private val syncDeviceStatusLock = Mutex()
fun reportDeviceReboot() { fun reportDeviceReboot() {

View file

@ -188,7 +188,8 @@ object ApplyServerDataStatus {
accessibilityServiceEnabled = newDevice.accessibilityServiceEnabled, accessibilityServiceEnabled = newDevice.accessibilityServiceEnabled,
wasAccessibilityServiceEnabled = newDevice.wasAccessibilityServiceEnabled, wasAccessibilityServiceEnabled = newDevice.wasAccessibilityServiceEnabled,
enableActivityLevelBlocking = newDevice.enableActivityLevelBlocking, enableActivityLevelBlocking = newDevice.enableActivityLevelBlocking,
qOrLater = newDevice.qOrLater qOrLater = newDevice.qOrLater,
manipulationFlags = newDevice.manipulationFlags
)) ))
} else { } else {
// eventually update old entry // eventually update old entry
@ -222,7 +223,8 @@ object ApplyServerDataStatus {
accessibilityServiceEnabled = newDevice.accessibilityServiceEnabled, accessibilityServiceEnabled = newDevice.accessibilityServiceEnabled,
wasAccessibilityServiceEnabled = newDevice.wasAccessibilityServiceEnabled, wasAccessibilityServiceEnabled = newDevice.wasAccessibilityServiceEnabled,
enableActivityLevelBlocking = newDevice.enableActivityLevelBlocking, enableActivityLevelBlocking = newDevice.enableActivityLevelBlocking,
qOrLater = newDevice.qOrLater qOrLater = newDevice.qOrLater,
manipulationFlags = newDevice.manipulationFlags
) )
if (updatedDeviceEntry != oldDeviceEntry) { if (updatedDeviceEntry != oldDeviceEntry) {

View file

@ -1081,7 +1081,8 @@ data class UpdateDeviceStatusAction(
val newAccessibilityServiceEnabled: Boolean?, val newAccessibilityServiceEnabled: Boolean?,
val newAppVersion: Int?, val newAppVersion: Int?,
val didReboot: Boolean, val didReboot: Boolean,
val isQOrLaterNow: Boolean val isQOrLaterNow: Boolean,
val addedManipulationFlags: Long
): AppLogicAction() { ): AppLogicAction() {
companion object { companion object {
const val TYPE_VALUE = "UPDATE_DEVICE_STATUS" const val TYPE_VALUE = "UPDATE_DEVICE_STATUS"
@ -1093,6 +1094,7 @@ data class UpdateDeviceStatusAction(
private const val NEW_APP_VERSION = "appVersion" private const val NEW_APP_VERSION = "appVersion"
private const val DID_REBOOT = "didReboot" private const val DID_REBOOT = "didReboot"
private const val IS_Q_OR_LATER_NOW = "isQOrLaterNow" private const val IS_Q_OR_LATER_NOW = "isQOrLaterNow"
private const val ADDED_MANIPULATION_FLAGS = "addedManipulationFlags"
val empty = UpdateDeviceStatusAction( val empty = UpdateDeviceStatusAction(
newProtectionLevel = null, newProtectionLevel = null,
@ -1102,7 +1104,8 @@ data class UpdateDeviceStatusAction(
newAccessibilityServiceEnabled = null, newAccessibilityServiceEnabled = null,
newAppVersion = null, newAppVersion = null,
didReboot = false, didReboot = false,
isQOrLaterNow = false isQOrLaterNow = false,
addedManipulationFlags = 0L
) )
} }
@ -1159,6 +1162,10 @@ data class UpdateDeviceStatusAction(
writer.name(IS_Q_OR_LATER_NOW).value(true) writer.name(IS_Q_OR_LATER_NOW).value(true)
} }
if (addedManipulationFlags != 0L) {
writer.name(ADDED_MANIPULATION_FLAGS).value(addedManipulationFlags)
}
writer.endObject() writer.endObject()
} }
} }
@ -1174,7 +1181,8 @@ data class IgnoreManipulationAction(
val ignoreAccessibilityServiceManipulation: Boolean, val ignoreAccessibilityServiceManipulation: Boolean,
val ignoreReboot: Boolean, val ignoreReboot: Boolean,
val ignoreHadManipulation: Boolean, val ignoreHadManipulation: Boolean,
val ignoreHadManipulationFlags: Long val ignoreHadManipulationFlags: Long,
val ignoreManipulationFlags: Long
): ParentAction() { ): ParentAction() {
companion object { companion object {
const val TYPE_VALUE = "IGNORE_MANIPULATION" const val TYPE_VALUE = "IGNORE_MANIPULATION"
@ -1189,6 +1197,7 @@ data class IgnoreManipulationAction(
private const val IGNORE_HAD_MANIPULATION = "hadManipulation" private const val IGNORE_HAD_MANIPULATION = "hadManipulation"
private const val IGNORE_REBOOT = "reboot" private const val IGNORE_REBOOT = "reboot"
private const val IGNORE_HAD_MANIPULATION_FLAGS = "ignoreHadManipulationFlags" private const val IGNORE_HAD_MANIPULATION_FLAGS = "ignoreHadManipulationFlags"
private const val IGNORE_MANIPULATION_FLAGS = "ignoreManipulationFlags"
} }
init { init {
@ -1204,7 +1213,8 @@ data class IgnoreManipulationAction(
(!ignoreAccessibilityServiceManipulation) && (!ignoreAccessibilityServiceManipulation) &&
(!ignoreReboot) && (!ignoreReboot) &&
(!ignoreHadManipulation) && (!ignoreHadManipulation) &&
(ignoreHadManipulationFlags == 0L) (ignoreHadManipulationFlags == 0L) &&
(ignoreManipulationFlags == 0L)
override fun serialize(writer: JsonWriter) { override fun serialize(writer: JsonWriter) {
writer.beginObject() writer.beginObject()
@ -1222,6 +1232,8 @@ data class IgnoreManipulationAction(
writer.name(IGNORE_REBOOT).value(ignoreReboot) writer.name(IGNORE_REBOOT).value(ignoreReboot)
writer.name(IGNORE_HAD_MANIPULATION_FLAGS).value(ignoreHadManipulationFlags) writer.name(IGNORE_HAD_MANIPULATION_FLAGS).value(ignoreHadManipulationFlags)
if (ignoreManipulationFlags != 0L) writer.name(IGNORE_MANIPULATION_FLAGS).value(ignoreManipulationFlags)
writer.endObject() writer.endObject()
} }
} }

View file

@ -305,6 +305,10 @@ object LocalDatabaseAppLogicActionDispatcher {
device = device.copy(qOrLater = true) device = device.copy(qOrLater = true)
} }
if (action.addedManipulationFlags != 0L) {
device = device.copy(manipulationFlags = device.manipulationFlags or action.addedManipulationFlags)
}
database.device().updateDeviceEntry(device) database.device().updateDeviceEntry(device)
null null

View file

@ -465,6 +465,12 @@ object LocalDatabaseParentActionDispatcher {
} }
} }
if (action.ignoreManipulationFlags != 0L) {
deviceEntry = deviceEntry.copy(
manipulationFlags = deviceEntry.manipulationFlags and (action.ignoreManipulationFlags.inv())
)
}
database.device().updateDeviceEntry(deviceEntry) database.device().updateDeviceEntry(deviceEntry)
} }
is SetKeepSignedInAction -> { is SetKeepSignedInAction -> {

View file

@ -298,7 +298,8 @@ data class ServerDeviceData(
val accessibilityServiceEnabled: Boolean, val accessibilityServiceEnabled: Boolean,
val wasAccessibilityServiceEnabled: Boolean, val wasAccessibilityServiceEnabled: Boolean,
val enableActivityLevelBlocking: Boolean, val enableActivityLevelBlocking: Boolean,
val qOrLater: Boolean val qOrLater: Boolean,
val manipulationFlags: Long
) { ) {
companion object { companion object {
private const val DEVICE_ID = "deviceId" private const val DEVICE_ID = "deviceId"
@ -331,6 +332,7 @@ data class ServerDeviceData(
private const val WAS_ACCESSIBILITY_SERVICE_ENABLED = "wasAsEnabled" private const val WAS_ACCESSIBILITY_SERVICE_ENABLED = "wasAsEnabled"
private const val ENABLE_ACTIVITY_LEVEL_BLOCKING = "activityLevelBlocking" private const val ENABLE_ACTIVITY_LEVEL_BLOCKING = "activityLevelBlocking"
private const val Q_OR_LATER = "qOrLater" private const val Q_OR_LATER = "qOrLater"
private const val MANIPULATION_FLAGS = "mFlags"
fun parse(reader: JsonReader): ServerDeviceData { fun parse(reader: JsonReader): ServerDeviceData {
var deviceId: String? = null var deviceId: String? = null
@ -363,6 +365,7 @@ data class ServerDeviceData(
var wasAccessibilityServiceEnabled: Boolean? = null var wasAccessibilityServiceEnabled: Boolean? = null
var enableActivityLevelBlocking = false var enableActivityLevelBlocking = false
var qOrLater = false var qOrLater = false
var manipulationFlags = 0L
reader.beginObject() reader.beginObject()
while (reader.hasNext()) { while (reader.hasNext()) {
@ -397,6 +400,7 @@ data class ServerDeviceData(
WAS_ACCESSIBILITY_SERVICE_ENABLED -> wasAccessibilityServiceEnabled = reader.nextBoolean() WAS_ACCESSIBILITY_SERVICE_ENABLED -> wasAccessibilityServiceEnabled = reader.nextBoolean()
ENABLE_ACTIVITY_LEVEL_BLOCKING -> enableActivityLevelBlocking = reader.nextBoolean() ENABLE_ACTIVITY_LEVEL_BLOCKING -> enableActivityLevelBlocking = reader.nextBoolean()
Q_OR_LATER -> qOrLater = reader.nextBoolean() Q_OR_LATER -> qOrLater = reader.nextBoolean()
MANIPULATION_FLAGS -> manipulationFlags = reader.nextLong()
else -> reader.skipValue() else -> reader.skipValue()
} }
} }
@ -432,7 +436,8 @@ data class ServerDeviceData(
accessibilityServiceEnabled = accessibilityServiceEnabled!!, accessibilityServiceEnabled = accessibilityServiceEnabled!!,
wasAccessibilityServiceEnabled = wasAccessibilityServiceEnabled!!, wasAccessibilityServiceEnabled = wasAccessibilityServiceEnabled!!,
enableActivityLevelBlocking = enableActivityLevelBlocking, enableActivityLevelBlocking = enableActivityLevelBlocking,
qOrLater = qOrLater qOrLater = qOrLater,
manipulationFlags = manipulationFlags
) )
} }

View file

@ -31,7 +31,7 @@ import io.timelimit.android.ui.main.FragmentWithCustomTitle
class DiagnoseExitReasonFragment: Fragment(), FragmentWithCustomTitle { class DiagnoseExitReasonFragment: Fragment(), FragmentWithCustomTitle {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = DiagnoseExitReasonFragmentBinding.inflate(inflater, container, false) val binding = DiagnoseExitReasonFragmentBinding.inflate(inflater, container, false)
val data = DefaultAppLogic.with(requireContext()).platformIntegration.getExitLog() val data = DefaultAppLogic.with(requireContext()).platformIntegration.getExitLog(0)
val recycler = binding.recycler val recycler = binding.recycler
val adapter = DiagnoseExitReasonAdapter() val adapter = DiagnoseExitReasonAdapter()

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -25,6 +25,7 @@ import com.google.android.material.snackbar.Snackbar
import io.timelimit.android.R import io.timelimit.android.R
import io.timelimit.android.data.model.Device import io.timelimit.android.data.model.Device
import io.timelimit.android.data.model.HadManipulationFlag import io.timelimit.android.data.model.HadManipulationFlag
import io.timelimit.android.data.model.ManipulationFlag
import io.timelimit.android.databinding.ManageDeviceManipulationViewBinding import io.timelimit.android.databinding.ManageDeviceManipulationViewBinding
import io.timelimit.android.livedata.map import io.timelimit.android.livedata.map
import io.timelimit.android.sync.actions.IgnoreManipulationAction import io.timelimit.android.sync.actions.IgnoreManipulationAction
@ -62,7 +63,7 @@ object ManageDeviceManipulation {
entries.forEach { warning -> entries.forEach { warning ->
container.addView( container.addView(
createCheckbox().apply { createCheckbox().apply {
setText(ManipulationWarningTypeLabel.getLabel(warning)) setText(warning.labelResourceId)
isChecked = selection.contains(warning) isChecked = selection.contains(warning)
setOnCheckedChangeListener { _, newIsChecked -> setOnCheckedChangeListener { _, newIsChecked ->
@ -128,66 +129,80 @@ data class ManipulationWarnings(val current: List<ManipulationWarningType>, val
fun isFlagSet(flag: Long) = (manipulationFlags and flag) == flag fun isFlagSet(flag: Long) = (manipulationFlags and flag) == flag
if (device.manipulationTriedDisablingDeviceAdmin) { if (device.manipulationTriedDisablingDeviceAdmin) {
current.add(ManipulationWarningType.TriedDisablingDeviceAdmin) current.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.TriedDisablingDeviceAdmin))
} }
if (device.manipulationOfAppVersion) { if (device.manipulationOfAppVersion) {
current.add(ManipulationWarningType.AppDowngrade) current.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.AppDowngrade))
} }
if (isFlagSet(HadManipulationFlag.APP_VERSION)) { if (isFlagSet(HadManipulationFlag.APP_VERSION)) {
past.add(ManipulationWarningType.AppDowngrade) past.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.AppDowngrade))
} }
if (device.manipulationOfProtectionLevel) { if (device.manipulationOfProtectionLevel) {
current.add(ManipulationWarningType.DeviceAdmin) current.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.DeviceAdmin))
} }
if (isFlagSet(HadManipulationFlag.PROTECTION_LEVEL)) { if (isFlagSet(HadManipulationFlag.PROTECTION_LEVEL)) {
past.add(ManipulationWarningType.DeviceAdmin) past.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.DeviceAdmin))
} }
if (device.manipulationOfUsageStats) { if (device.manipulationOfUsageStats) {
current.add(ManipulationWarningType.UsageStats) current.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.UsageStats))
} }
if (isFlagSet(HadManipulationFlag.USAGE_STATS_ACCESS)) { if (isFlagSet(HadManipulationFlag.USAGE_STATS_ACCESS)) {
past.add(ManipulationWarningType.UsageStats) past.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.UsageStats))
} }
if (device.manipulationOfNotificationAccess) { if (device.manipulationOfNotificationAccess) {
current.add(ManipulationWarningType.NotificationAccess) current.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.NotificationAccess))
} }
if (isFlagSet(HadManipulationFlag.NOTIFICATION_ACCESS)) { if (isFlagSet(HadManipulationFlag.NOTIFICATION_ACCESS)) {
past.add(ManipulationWarningType.NotificationAccess) past.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.NotificationAccess))
} }
if (device.manipulationOfOverlayPermission) { if (device.manipulationOfOverlayPermission) {
current.add(ManipulationWarningType.OverlayPermission) current.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.OverlayPermission))
} }
if (isFlagSet(HadManipulationFlag.OVERLAY_PERMISSION)) { if (isFlagSet(HadManipulationFlag.OVERLAY_PERMISSION)) {
past.add(ManipulationWarningType.OverlayPermission) past.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.OverlayPermission))
} }
if (device.wasAccessibilityServiceEnabled) { if (device.wasAccessibilityServiceEnabled) {
if (!device.accessibilityServiceEnabled) { if (!device.accessibilityServiceEnabled) {
current.add(ManipulationWarningType.AccessibilityService) current.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.AccessibilityService))
} }
} }
if (isFlagSet(HadManipulationFlag.ACCESSIBILITY_SERVICE)) { if (isFlagSet(HadManipulationFlag.ACCESSIBILITY_SERVICE)) {
past.add(ManipulationWarningType.AccessibilityService) past.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.AccessibilityService))
} }
if (device.manipulationDidReboot) { if (device.manipulationDidReboot) {
current.add(ManipulationWarningType.DidReboot) current.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.DidReboot))
} }
if (device.hadManipulation) { if (device.hadManipulation) {
if (past.isEmpty()) { if (past.isEmpty()) {
past.add(ManipulationWarningType.Unknown) past.add(ManipulationWarningType.Classic(ClassicManipulationWarningType.Unknown))
}
}
if (device.manipulationFlags != 0L) {
var remainingFlags = device.manipulationFlags
if (remainingFlags and ManipulationFlag.USED_FGS_KILLER == ManipulationFlag.USED_FGS_KILLER) {
past.add(ManipulationWarningType.Flag(ManipulationFlag.USED_FGS_KILLER, R.string.manage_device_manipulation_fgs_killer))
remainingFlags = remainingFlags.and(ManipulationFlag.USED_FGS_KILLER.inv())
}
if (remainingFlags != 0L) {
past.add(ManipulationWarningType.Flag(remainingFlags, R.string.manage_device_manipulation_unknown))
} }
} }
return ManipulationWarnings( return ManipulationWarnings(
current = current, current = current,
past = past past = past
) )
} }
} }
@ -197,38 +212,69 @@ data class ManipulationWarnings(val current: List<ManipulationWarningType>, val
fun buildIgnoreAction(deviceId: String): IgnoreManipulationAction { fun buildIgnoreAction(deviceId: String): IgnoreManipulationAction {
var ignoreHadManipulationFlags = 0L var ignoreHadManipulationFlags = 0L
var ignoreManipulationFlags = 0L
past.forEach { type -> past.forEach { type ->
ignoreHadManipulationFlags = ignoreHadManipulationFlags or when(type) { when (type) {
ManipulationWarningType.TriedDisablingDeviceAdmin -> throw IllegalArgumentException() is ManipulationWarningType.Classic -> {
ManipulationWarningType.AppDowngrade -> HadManipulationFlag.APP_VERSION ignoreHadManipulationFlags = ignoreHadManipulationFlags or when(type.type) {
ManipulationWarningType.DeviceAdmin -> HadManipulationFlag.PROTECTION_LEVEL ClassicManipulationWarningType.TriedDisablingDeviceAdmin -> throw IllegalArgumentException()
ManipulationWarningType.UsageStats -> HadManipulationFlag.USAGE_STATS_ACCESS ClassicManipulationWarningType.AppDowngrade -> HadManipulationFlag.APP_VERSION
ManipulationWarningType.NotificationAccess -> HadManipulationFlag.NOTIFICATION_ACCESS ClassicManipulationWarningType.DeviceAdmin -> HadManipulationFlag.PROTECTION_LEVEL
ManipulationWarningType.OverlayPermission -> HadManipulationFlag.OVERLAY_PERMISSION ClassicManipulationWarningType.UsageStats -> HadManipulationFlag.USAGE_STATS_ACCESS
ManipulationWarningType.AccessibilityService -> HadManipulationFlag.ACCESSIBILITY_SERVICE ClassicManipulationWarningType.NotificationAccess -> HadManipulationFlag.NOTIFICATION_ACCESS
ManipulationWarningType.DidReboot -> throw IllegalArgumentException() ClassicManipulationWarningType.OverlayPermission -> HadManipulationFlag.OVERLAY_PERMISSION
ManipulationWarningType.Unknown -> 0L // handled at an other location ClassicManipulationWarningType.AccessibilityService -> HadManipulationFlag.ACCESSIBILITY_SERVICE
} ClassicManipulationWarningType.DidReboot -> throw IllegalArgumentException()
ClassicManipulationWarningType.Unknown -> 0L // handled at an other location
}
}
is ManipulationWarningType.Flag -> {
ignoreManipulationFlags = ignoreManipulationFlags or type.mask
}
}.let {/* require handling all cases */}
} }
val currentClassic = current.filterIsInstance<ManipulationWarningType.Classic>().map { it.type }
return IgnoreManipulationAction( return IgnoreManipulationAction(
deviceId = deviceId, deviceId = deviceId,
ignoreUsageStatsAccessManipulation = current.contains(ManipulationWarningType.UsageStats), ignoreUsageStatsAccessManipulation = currentClassic.contains(ClassicManipulationWarningType.UsageStats),
ignoreNotificationAccessManipulation = current.contains(ManipulationWarningType.NotificationAccess), ignoreNotificationAccessManipulation = currentClassic.contains(ClassicManipulationWarningType.NotificationAccess),
ignoreDeviceAdminManipulationAttempt = current.contains(ManipulationWarningType.TriedDisablingDeviceAdmin), ignoreDeviceAdminManipulationAttempt = currentClassic.contains(ClassicManipulationWarningType.TriedDisablingDeviceAdmin),
ignoreDeviceAdminManipulation = current.contains(ManipulationWarningType.DeviceAdmin), ignoreDeviceAdminManipulation = currentClassic.contains(ClassicManipulationWarningType.DeviceAdmin),
ignoreOverlayPermissionManipulation = current.contains(ManipulationWarningType.OverlayPermission), ignoreOverlayPermissionManipulation = currentClassic.contains(ClassicManipulationWarningType.OverlayPermission),
ignoreAccessibilityServiceManipulation = current.contains(ManipulationWarningType.AccessibilityService), ignoreAccessibilityServiceManipulation = currentClassic.contains(ClassicManipulationWarningType.AccessibilityService),
ignoreAppDowngrade = current.contains(ManipulationWarningType.AppDowngrade), ignoreAppDowngrade = currentClassic.contains(ClassicManipulationWarningType.AppDowngrade),
ignoreReboot = current.contains(ManipulationWarningType.DidReboot), ignoreReboot = currentClassic.contains(ClassicManipulationWarningType.DidReboot),
ignoreHadManipulation = past.contains(ManipulationWarningType.Unknown), ignoreHadManipulation = currentClassic.contains(ClassicManipulationWarningType.Unknown),
ignoreHadManipulationFlags = ignoreHadManipulationFlags ignoreHadManipulationFlags = ignoreHadManipulationFlags,
ignoreManipulationFlags = ignoreManipulationFlags
) )
} }
} }
enum class ManipulationWarningType { sealed class ManipulationWarningType {
abstract val labelResourceId: Int
data class Classic(val type: ClassicManipulationWarningType): ManipulationWarningType() {
override val labelResourceId = when (type) {
ClassicManipulationWarningType.TriedDisablingDeviceAdmin -> R.string.manage_device_manipulation_device_admin_disable_attempt
ClassicManipulationWarningType.AppDowngrade -> R.string.manage_device_manipulation_app_version
ClassicManipulationWarningType.DeviceAdmin -> R.string.manage_device_manipulation_device_admin_disabled
ClassicManipulationWarningType.UsageStats -> R.string.manage_device_manipulation_usage_stats_access
ClassicManipulationWarningType.NotificationAccess -> R.string.manage_device_manipulation_notification_access
ClassicManipulationWarningType.OverlayPermission -> R.string.manage_device_manipulation_overlay_permission
ClassicManipulationWarningType.AccessibilityService -> R.string.manage_device_manipulation_accessibility_service
ClassicManipulationWarningType.DidReboot -> R.string.manage_device_manipulation_reboot
ClassicManipulationWarningType.Unknown -> R.string.manage_device_manipulation_existed
}
}
data class Flag(val mask: Long, override val labelResourceId: Int): ManipulationWarningType()
}
enum class ClassicManipulationWarningType {
TriedDisablingDeviceAdmin, TriedDisablingDeviceAdmin,
AppDowngrade, AppDowngrade,
DeviceAdmin, DeviceAdmin,
@ -240,20 +286,6 @@ enum class ManipulationWarningType {
Unknown Unknown
} }
object ManipulationWarningTypeLabel {
fun getLabel(type: ManipulationWarningType) = when (type) {
ManipulationWarningType.TriedDisablingDeviceAdmin -> R.string.manage_device_manipulation_device_admin_disable_attempt
ManipulationWarningType.AppDowngrade -> R.string.manage_device_manipulation_app_version
ManipulationWarningType.DeviceAdmin -> R.string.manage_device_manipulation_device_admin_disabled
ManipulationWarningType.UsageStats -> R.string.manage_device_manipulation_usage_stats_access
ManipulationWarningType.NotificationAccess -> R.string.manage_device_manipulation_notification_access
ManipulationWarningType.OverlayPermission -> R.string.manage_device_manipulation_overlay_permission
ManipulationWarningType.AccessibilityService -> R.string.manage_device_manipulation_accessibility_service
ManipulationWarningType.DidReboot -> R.string.manage_device_manipulation_reboot
ManipulationWarningType.Unknown -> R.string.manage_device_manipulation_existed
}
}
class ManageDeviceManipulationStatus { class ManageDeviceManipulationStatus {
val selectedCurrent = mutableListOf<ManipulationWarningType>() val selectedCurrent = mutableListOf<ManipulationWarningType>()
val selectedPast = mutableListOf<ManipulationWarningType>() val selectedPast = mutableListOf<ManipulationWarningType>()

View file

@ -35,7 +35,6 @@ import io.timelimit.android.ui.backdoor.BackdoorDialogFragment
import io.timelimit.android.ui.login.NewLoginFragment import io.timelimit.android.ui.login.NewLoginFragment
import io.timelimit.android.ui.main.ActivityViewModel import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.main.ActivityViewModelHolder import io.timelimit.android.ui.main.ActivityViewModelHolder
import io.timelimit.android.ui.manage.device.manage.ManipulationWarningTypeLabel
import io.timelimit.android.ui.manage.device.manage.ManipulationWarnings import io.timelimit.android.ui.manage.device.manage.ManipulationWarnings
import io.timelimit.android.util.TimeTextUtil import io.timelimit.android.util.TimeTextUtil
@ -95,7 +94,7 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder {
logic.deviceEntry.map { logic.deviceEntry.map {
val reasonItems = (it?.let { ManipulationWarnings.getFromDevice(it) } ?: ManipulationWarnings.empty) val reasonItems = (it?.let { ManipulationWarnings.getFromDevice(it) } ?: ManipulationWarnings.empty)
.current .current
.map { getString(ManipulationWarningTypeLabel.getLabel(it)) } .map { getString(it.labelResourceId) }
if (reasonItems.isEmpty()) { if (reasonItems.isEmpty()) {
null null

View file

@ -862,6 +862,8 @@
<string name="manage_device_manipulation_app_version">ältere App-Version installiert</string> <string name="manage_device_manipulation_app_version">ältere App-Version installiert</string>
<string name="manage_device_manipulation_reboot">Gerät wurde neu gestartet</string> <string name="manage_device_manipulation_reboot">Gerät wurde neu gestartet</string>
<string name="manage_device_manipulation_existed">Es gab eine Manipulation, die wieder beendet wurde</string> <string name="manage_device_manipulation_existed">Es gab eine Manipulation, die wieder beendet wurde</string>
<string name="manage_device_manipulation_unknown">unbekannte Manipulation</string>
<string name="manage_device_manipulation_fgs_killer">mittels Task-Manager beendet</string>
<string name="manage_device_manipulation_btn_ignore">Warnungen bestätigen und ausblenden</string> <string name="manage_device_manipulation_btn_ignore">Warnungen bestätigen und ausblenden</string>
<string name="manage_device_manipulation_toast_nothing_selected">Sie müssen zuerst Warnungen wählen, die Sie ignorieren möchten</string> <string name="manage_device_manipulation_toast_nothing_selected">Sie müssen zuerst Warnungen wählen, die Sie ignorieren möchten</string>

View file

@ -907,6 +907,8 @@
<string name="manage_device_manipulation_app_version">older App version installed</string> <string name="manage_device_manipulation_app_version">older App version installed</string>
<string name="manage_device_manipulation_reboot">device was rebooted</string> <string name="manage_device_manipulation_reboot">device was rebooted</string>
<string name="manage_device_manipulation_existed">there was a manipulation which was stopped again</string> <string name="manage_device_manipulation_existed">there was a manipulation which was stopped again</string>
<string name="manage_device_manipulation_unknown">unknown Manipulation</string>
<string name="manage_device_manipulation_fgs_killer">killed using task manager</string>
<string name="manage_device_manipulation_btn_ignore">Confirm and hide warnings</string> <string name="manage_device_manipulation_btn_ignore">Confirm and hide warnings</string>
<string name="manage_device_manipulation_toast_nothing_selected">You have to select warnings to ignore first</string> <string name="manage_device_manipulation_toast_nothing_selected">You have to select warnings to ignore first</string>