Add new experimental app detection method

This commit is contained in:
Jonas Lochmann 2022-03-07 01:00:00 +01:00
parent d38724c42a
commit 1a0e56ef37
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
18 changed files with 519 additions and 43 deletions

View file

@ -243,4 +243,5 @@ object ExperimentalFlags {
const val HIDE_MANIPULATION_WARNING = 8192L
const val ENABLE_SOFT_BLOCKING = 16384L
const val SYNC_RELATED_NOTIFICATIONS = 32768L
const val INSTANCE_ID_FG_APP_DETECTION = 65536L
}

View file

@ -49,7 +49,7 @@ abstract class PlatformIntegration(
abstract suspend fun muteAudioIfPossible(packageName: String): Boolean
abstract fun setShowBlockingOverlay(show: Boolean, blockedElement: String? = null)
// this should throw an SecurityException if the permission is missing
abstract suspend fun getForegroundApps(queryInterval: Long, enableMultiAppDetection: Boolean): Set<ForegroundApp>
abstract suspend fun getForegroundApps(queryInterval: Long, experimentalFlags: Long): Set<ForegroundApp>
abstract fun getMusicPlaybackPackage(): String?
abstract fun setAppStatusMessage(message: AppStatusMessage?)
abstract fun isScreenOn(): Boolean

View file

@ -150,7 +150,7 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
return AdminStatus.getAdminStatus(context, policyManager)
}
override suspend fun getForegroundApps(queryInterval: Long, enableMultiAppDetection: Boolean): Set<ForegroundApp> = foregroundAppHelper.getForegroundApps(queryInterval, enableMultiAppDetection)
override suspend fun getForegroundApps(queryInterval: Long, experimentalFlags: Long): Set<ForegroundApp> = foregroundAppHelper.getForegroundApps(queryInterval, experimentalFlags)
override fun getForegroundAppPermissionStatus(): RuntimePermissionStatus {
return foregroundAppHelper.getPermissionStatus()

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
*
* 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
@ -29,7 +29,7 @@ class CompatForegroundAppHelper(context: Context) : ForegroundAppHelper() {
private var lastForegroundAppList: Set<ForegroundApp> = emptySet()
private val mutex = Mutex()
override suspend fun getForegroundApps(queryInterval: Long, enableMultiAppDetection: Boolean): Set<ForegroundApp> {
override suspend fun getForegroundApps(queryInterval: Long, experimentalFlags: Long): Set<ForegroundApp> {
mutex.withLock {
try {
val activity = activityManager.getRunningTasks(1)[0].topActivity!!

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
*
* 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
@ -21,7 +21,7 @@ import io.timelimit.android.integration.platform.ForegroundApp
import io.timelimit.android.integration.platform.RuntimePermissionStatus
abstract class ForegroundAppHelper {
abstract suspend fun getForegroundApps(queryInterval: Long, enableMultiAppDetection: Boolean): Set<ForegroundApp>
abstract suspend fun getForegroundApps(queryInterval: Long, experimentalFlags: Long): Set<ForegroundApp>
abstract fun getPermissionStatus(): RuntimePermissionStatus
companion object {
@ -32,7 +32,9 @@ abstract class ForegroundAppHelper {
if (instance == null) {
synchronized(lock) {
if (instance == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
instance = QForegroundAppHelper(context.applicationContext)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
instance = LollipopForegroundAppHelper(context.applicationContext)
} else {
instance = CompatForegroundAppHelper(context.applicationContext)

View file

@ -0,0 +1,110 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
*
* 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
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.timelimit.android.integration.platform.android.foregroundapp
import android.annotation.TargetApi
import android.content.Context
import android.os.Build
import android.util.SparseArray
import androidx.core.util.size
import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.integration.platform.ForegroundApp
import io.timelimit.android.integration.platform.RuntimePermissionStatus
@TargetApi(Build.VERSION_CODES.Q)
class InstanceIdForegroundAppHelper(context: Context): UsageStatsForegroundAppHelper(context) {
companion object {
private const val START_QUERY_INTERVAL = 1000 * 60 * 60 * 24 * 3 // 3 days
private const val TOLERANCE = 3000L
}
private var lastQueryTime = 0L
private var lastEventTimestamp = 0L
private val apps = SparseArray<ForegroundApp>()
override suspend fun getForegroundApps(
queryInterval: Long,
experimentalFlags: Long
): Set<ForegroundApp> {
if (Build.VERSION.SDK_INT > 32) {
throw UntestedSystemVersionException()
}
if (getPermissionStatus() != RuntimePermissionStatus.Granted) {
throw SecurityException()
}
val result = backgroundThread.executeAndWait {
val now = System.currentTimeMillis()
val didTimeWentBackwards = lastQueryTime > now
val didNeverQuery = lastQueryTime == 0L
val shouldDoFullQuery = didTimeWentBackwards || didNeverQuery
if (shouldDoFullQuery) {
apps.clear()
}
val minQueryStartTime = (now - START_QUERY_INTERVAL).coerceAtLeast(1)
val queryStartTimeByLastEvent = lastEventTimestamp - TOLERANCE
val queryStartTime = if (shouldDoFullQuery) {
minQueryStartTime
} else {
queryStartTimeByLastEvent
.coerceAtLeast(minQueryStartTime)
.coerceAtMost(now - TOLERANCE)
}
val queryEndTime = now + TOLERANCE
usageStatsManager.queryEvents(queryStartTime, queryEndTime)?.let { nativeEvents ->
val events = TlUsageEvents.fromUsageEvents(nativeEvents)
while (events.readNextItem()) {
lastEventTimestamp = events.timestamp
if (events.eventType == TlUsageEvents.DEVICE_STARTUP) {
apps.clear()
} else if (events.eventType == TlUsageEvents.MOVE_TO_FOREGROUND) {
val app = ForegroundApp(events.packageName, events.className)
apps.put(events.instanceId, app)
} else if (
events.eventType == TlUsageEvents.MOVE_TO_BACKGROUND ||
events.eventType == TlUsageEvents.ACTIVITY_STOPPED
) {
apps.remove(events.instanceId)
}
}
}
lastQueryTime = now
val appsSet = mutableSetOf<ForegroundApp>()
for (index in 0 until apps.size) {
appsSet.add(apps.valueAt(index))
}
appsSet
}
return result
}
class UntestedSystemVersionException: RuntimeException()
}

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
*
* 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
@ -16,9 +16,7 @@
package io.timelimit.android.integration.platform.android.foregroundapp
import android.annotation.TargetApi
import android.app.AppOpsManager
import android.app.usage.UsageEvents
import android.app.usage.UsageStatsManager
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
@ -27,13 +25,14 @@ import android.util.Log
import android.util.SparseIntArray
import io.timelimit.android.BuildConfig
import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.data.model.ExperimentalFlags
import io.timelimit.android.integration.platform.ForegroundApp
import io.timelimit.android.integration.platform.RuntimePermissionStatus
import java.util.concurrent.Executor
import java.util.concurrent.Executors
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class LollipopForegroundAppHelper(private val context: Context) : ForegroundAppHelper() {
class LollipopForegroundAppHelper(context: Context) : UsageStatsForegroundAppHelper(context) {
companion object {
private const val LOG_TAG = "LollipopForegroundApp"
@ -51,10 +50,6 @@ class LollipopForegroundAppHelper(private val context: Context) : ForegroundAppH
}
}
private val usageStatsManager = context.getSystemService(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) Context.USAGE_STATS_SERVICE else "usagestats") as UsageStatsManager
private val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
private val packageManager = context.packageManager
private var lastQueryTime: Long = 0
private val currentForegroundApps = mutableMapOf<ForegroundApp, Int>()
private val expectedStopEvents = mutableSetOf<ForegroundApp>()
@ -66,11 +61,12 @@ class LollipopForegroundAppHelper(private val context: Context) : ForegroundAppH
private var lastEnableMultiAppDetection = false
@Throws(SecurityException::class)
override suspend fun getForegroundApps(queryInterval: Long, enableMultiAppDetection: Boolean): Set<ForegroundApp> {
override suspend fun getForegroundApps(queryInterval: Long, experimentalFlags: Long): Set<ForegroundApp> {
if (getPermissionStatus() == RuntimePermissionStatus.NotGranted) {
throw SecurityException()
}
val enableMultiAppDetection = experimentalFlags and ExperimentalFlags.MULTI_APP_DETECTION == ExperimentalFlags.MULTI_APP_DETECTION
val effectiveEnableMultiAppDetection = enableMultiAppDetection && enableMultiAppDetectionGeneral
foregroundAppThread.executeAndWait {
@ -249,20 +245,6 @@ class LollipopForegroundAppHelper(private val context: Context) : ForegroundAppH
return currentForegroundAppsSnapshot
}
override fun getPermissionStatus(): RuntimePermissionStatus {
val appOpsStatus = appOpsManager.checkOpNoThrow("android:get_usage_stats", android.os.Process.myUid(), context.packageName)
val packageManagerStatus = packageManager.checkPermission("android.permission.PACKAGE_USAGE_STATS", BuildConfig.APPLICATION_ID)
val allowedUsingSystemSettings = appOpsStatus == AppOpsManager.MODE_ALLOWED
val allowedUsingAdb = appOpsStatus == AppOpsManager.MODE_DEFAULT && packageManagerStatus == PackageManager.PERMISSION_GRANTED
if(allowedUsingSystemSettings || allowedUsingAdb) {
return RuntimePermissionStatus.Granted
} else {
return RuntimePermissionStatus.NotGranted
}
}
// Android 9 (and maybe older versions too) do not report pausing Apps if they are disabled while running
private fun doesActivityExist(app: ForegroundApp) = doesActivityExistSimple(app) || doesActivityExistAsAlias(app)

View file

@ -0,0 +1,88 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
*
* 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
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.timelimit.android.integration.platform.android.foregroundapp
import android.os.Parcel
import java.io.FileInputStream
/**
* This class implements a clone of Parcel.readBlob() as this is not a public
* interface in all Android versions that support it.
*
* The wire format is (both as integer): length, useSharedMemory
*
* useSharedMemory can be 0 or 1
*/
object ParcelBlob {
fun readBlob(parcel: Parcel): ByteArray {
val length = parcel.readInt()
val useSharedMemory = when (parcel.readInt()) {
0 -> false
1 -> true
else -> throw InvalidBooleanException()
}
if (useSharedMemory) {
val fd = parcel.readFileDescriptor()
val result = ByteArray(length)
try {
FileInputStream(fd.fileDescriptor).use { stream ->
var cursor = 0
while (cursor < length) {
val bytesRead = stream.read(result, cursor, length - cursor)
if (bytesRead == -1) {
throw UnexpectedEndOfSharedMemory()
} else {
cursor += bytesRead
}
}
}
} finally {
fd.close()
}
return result
} else {
val tempParcel = Parcel.obtain()
val oldPosition = parcel.dataPosition()
try {
tempParcel.appendFrom(parcel, oldPosition, length)
parcel.setDataPosition(oldPosition + length)
val result = tempParcel.marshall()
if (result.size != length) {
throw WrongReturnedDataSize()
}
return result
} finally {
tempParcel.recycle()
}
}
}
open class ParcelBlobException: RuntimeException()
class InvalidBooleanException: ParcelBlobException()
class UnexpectedEndOfSharedMemory: ParcelBlobException()
class WrongReturnedDataSize: ParcelBlobException()
}

View file

@ -0,0 +1,61 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
*
* 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
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.timelimit.android.integration.platform.android.foregroundapp
import android.content.Context
import android.util.Log
import io.timelimit.android.BuildConfig
import io.timelimit.android.data.model.ExperimentalFlags
import io.timelimit.android.integration.platform.ForegroundApp
class QForegroundAppHelper(context: Context): UsageStatsForegroundAppHelper(context) {
companion object {
private const val LOG_TAG = "QForegroundAppHelper"
}
private val legacy = LollipopForegroundAppHelper(context)
private val modern = InstanceIdForegroundAppHelper(context)
private var fallbackCounter = 0
override suspend fun getForegroundApps(
queryInterval: Long,
experimentalFlags: Long
): Set<ForegroundApp> {
val useInstanceIdForegroundAppDetection = experimentalFlags and ExperimentalFlags.INSTANCE_ID_FG_APP_DETECTION == ExperimentalFlags.INSTANCE_ID_FG_APP_DETECTION
val result = if (useInstanceIdForegroundAppDetection && fallbackCounter == 0) {
try {
modern.getForegroundApps(queryInterval, experimentalFlags)
} catch (ex: Exception) {
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "falling back to the legacy implementation", ex)
}
fallbackCounter = 100
legacy.getForegroundApps(queryInterval, experimentalFlags)
}
} else {
legacy.getForegroundApps(queryInterval, experimentalFlags)
}
if (fallbackCounter > 0) {
fallbackCounter -= 1
}
return result
}
}

View file

@ -0,0 +1,180 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
*
* 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
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.timelimit.android.integration.platform.android.foregroundapp
import android.app.usage.UsageEvents
import android.content.res.Configuration
import android.os.Parcel
/**
* This class is/should be a Parcel compatible clone of the UsageEvents
* system class. This allows reading fields which the public API does not provide.
*/
class TlUsageEvents (private val content: Parcel) {
companion object {
const val NONE = 0
const val MOVE_TO_FOREGROUND = 1
const val MOVE_TO_BACKGROUND = 2
// const val END_OF_DAY = 3
// const val CONTINUE_PREVIOUS_DAY = 4
const val CONFIGURATION_CHANGE = 5
// const val SYSTEM_INTERACTION = 6
// const val USER_INTERACTION = 7
const val SHORTCUT_INVOCATION = 8
const val CHOOSER_ACTION = 9
// const val NOTIFICATION_SEEN = 10
const val STANDBY_BUCKET_CHANGED = 11
const val NOTIFICATION_INTERRUPTION = 12
// const val SLICE_PINNED_PRIV = 13
// const val SLICE_PINNED = 14
// const val SCREEN_INTERACTIVE = 15
// const val SCREEN_NON_INTERACTIVE = 16
// const val KEYGUARD_SHOWN = 17
// const val KEYGUARD_HIDDEN = 18
// const val FOREGROUND_SERVICE_START = 19
// const val FOREGROUND_SERVICE_STOP = 20
// const val CONTINUING_FOREGROUND_SERVICE = 21
// const val ROLLOVER_FOREGROUND_SERVICE = 22
const val ACTIVITY_STOPPED = 23
// const val ACTIVITY_DESTROYED = 24
// const val FLUSH_TO_DISK = 25
// const val DEVICE_SHUTDOWN = 26
const val DEVICE_STARTUP = 27
// const val USER_UNLOCKED = 28
// const val USER_STOPPED = 29
const val LOCUS_ID_SET = 30
// const val APP_COMPONENT_USED = 31
const val MAX_EVENT_TYPE = 31
const val DUMMY_STRING = "null"
fun fromUsageEvents(input: UsageEvents): TlUsageEvents {
val outerParcel = Parcel.obtain()
val blob = try {
input.writeToParcel(outerParcel, 0)
outerParcel.setDataPosition(0)
ParcelBlob.readBlob(outerParcel)
} finally {
outerParcel.recycle()
}
val innerParcel = Parcel.obtain()
try {
innerParcel.unmarshall(blob, 0, blob.size)
innerParcel.setDataPosition(0)
return TlUsageEvents(innerParcel)
} catch (ex: Exception) {
innerParcel.recycle()
throw ex
}
}
}
private var free = false
private val length = content.readInt()
private var index = content.readInt()
private val strings = if (length > 0) content.createStringArray() else null
init {
if (length > 0) {
val listByteLength = content.readInt()
val positionInParcel = content.readInt()
content.setDataPosition(content.dataPosition() + positionInParcel)
content.setDataSize(content.dataPosition() + listByteLength)
} else {
free()
}
}
private var outputTimestamp = 0L
private var outputEventType = 0
private var outputInstanceId = 0
private var outputPackageName = DUMMY_STRING
private var outputClassName = DUMMY_STRING
val timestamp get() = outputTimestamp
val eventType get() = outputEventType
val instanceId get() = outputInstanceId
val packageName get() = outputPackageName
val className get() = outputClassName
fun readNextItem(): Boolean {
if (free) return false
if (strings == null) throw IllegalStateException()
val packageIndex = content.readInt()
val classIndex = content.readInt()
val instanceId = content.readInt()
val taskRootPackageIndex = content.readInt()
val taskRootClassIndex = content.readInt()
val eventType = content.readInt()
val timestamp = content.readLong()
if (eventType < NONE || eventType > MAX_EVENT_TYPE) {
throw UnknownEventTypeException()
}
when (eventType) {
CONFIGURATION_CHANGE -> {
val newConfiguration = Configuration.CREATOR.createFromParcel(content)
}
SHORTCUT_INVOCATION -> {
val shortcutId = content.readString()
}
CHOOSER_ACTION -> {
val action = content.readString()
val contentType = content.readString()
val contentAnnotations = content.createStringArray()
}
STANDBY_BUCKET_CHANGED -> {
val bucketAndReason = content.readInt()
}
NOTIFICATION_INTERRUPTION -> {
val notificationChannelId = content.readString()
}
LOCUS_ID_SET -> {
val locusId = content.readString()
}
}
val flags = content.readInt()
outputTimestamp = timestamp
outputEventType = eventType
outputInstanceId = instanceId
outputPackageName = if (packageIndex == -1) DUMMY_STRING else strings[packageIndex]
outputClassName = if(classIndex == -1) DUMMY_STRING else strings[classIndex]
index++; if (index == length) free()
return true
}
fun free() {
if (!free) {
content.recycle()
free = true
}
}
open class UsageException: RuntimeException()
class UnknownEventTypeException: UsageException()
}

View file

@ -0,0 +1,50 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
*
* 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
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.timelimit.android.integration.platform.android.foregroundapp
import android.annotation.TargetApi
import android.app.AppOpsManager
import android.app.usage.UsageStatsManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import io.timelimit.android.BuildConfig
import io.timelimit.android.integration.platform.RuntimePermissionStatus
import java.util.concurrent.Executor
import java.util.concurrent.Executors
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
abstract class UsageStatsForegroundAppHelper (context: Context): ForegroundAppHelper() {
protected val usageStatsManager = context.getSystemService(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) Context.USAGE_STATS_SERVICE else "usagestats") as UsageStatsManager
protected val packageManager: PackageManager = context.packageManager
protected val backgroundThread: Executor by lazy { Executors.newSingleThreadExecutor() }
private val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
private val packageName = context.packageName
override fun getPermissionStatus(): RuntimePermissionStatus {
val appOpsStatus = appOpsManager.checkOpNoThrow("android:get_usage_stats", android.os.Process.myUid(), packageName)
val packageManagerStatus = packageManager.checkPermission("android.permission.PACKAGE_USAGE_STATS", BuildConfig.APPLICATION_ID)
val allowedUsingSystemSettings = appOpsStatus == AppOpsManager.MODE_ALLOWED
val allowedUsingAdb = appOpsStatus == AppOpsManager.MODE_DEFAULT && packageManagerStatus == PackageManager.PERMISSION_GRANTED
if(allowedUsingSystemSettings || allowedUsingAdb) {
return RuntimePermissionStatus.Granted
} else {
return RuntimePermissionStatus.NotGranted
}
}
}

View file

@ -111,7 +111,7 @@ class DummyIntegration(
}
}
override suspend fun getForegroundApps(queryInterval: Long, enableMultiAppDetection: Boolean): Set<ForegroundApp> {
override suspend fun getForegroundApps(queryInterval: Long, experimentalFlags: Long): Set<ForegroundApp> {
if (foregroundAppPermission == RuntimePermissionStatus.NotGranted) {
throw SecurityException()
}

View file

@ -42,7 +42,7 @@ object AppAffectedByPrimaryDeviceUtil {
val currentApps = try {
logic.platformIntegration.getForegroundApps(
logic.getForegroundAppQueryInterval(),
logic.getEnableMultiAppDetection()
deviceAndUserRelatedData.deviceRelatedData.experimentalFlags
)
} catch (ex: SecurityException) {
emptySet<ForegroundApp>()

View file

@ -73,12 +73,8 @@ class AppLogic(
}.ignoreUnchanged()
private val foregroundAppQueryInterval = database.config().getForegroundAppQueryIntervalAsync().apply { observeForever { } }
private val enableMultiAppDetection = database.config().experimentalFlags
.map { it and ExperimentalFlags.MULTI_APP_DETECTION == ExperimentalFlags.MULTI_APP_DETECTION }.ignoreUnchanged()
.apply {observeForever { } }
fun getForegroundAppQueryInterval() = foregroundAppQueryInterval.value ?: 0L
fun getEnableMultiAppDetection() = enableMultiAppDetection.value ?: false
val serverLogic = ServerLogic(this)
val defaultUserLogic = DefaultUserLogic(this)

View file

@ -159,8 +159,6 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
if (blockedAppPackageName != appLogic.platformIntegration.getLauncherAppPackageName()) {
AccessibilityService.instance?.showHomescreen()
delay(100)
AccessibilityService.instance?.showHomescreen()
delay(100)
}
}
@ -296,7 +294,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
val foregroundApps = appLogic.platformIntegration.getForegroundApps(
appLogic.getForegroundAppQueryInterval(),
appLogic.getEnableMultiAppDetection()
deviceRelatedData.experimentalFlags
)
val audioPlaybackPackageName = appLogic.platformIntegration.getMusicPlaybackPackage()

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
*
* 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
@ -195,6 +195,12 @@ data class DiagnoseExperimentalFlagItem(
enableFlags = ExperimentalFlags.SYNC_RELATED_NOTIFICATIONS,
disableFlags = ExperimentalFlags.SYNC_RELATED_NOTIFICATIONS,
enable = { true }
),
DiagnoseExperimentalFlagItem(
label = R.string.diagnose_exf_ifd,
enableFlags = ExperimentalFlags.INSTANCE_ID_FG_APP_DETECTION,
disableFlags = ExperimentalFlags.INSTANCE_ID_FG_APP_DETECTION,
enable = { true }
)
)
}

View file

@ -522,6 +522,7 @@
<string name="diagnose_exf_hmw">Manipulationswarnung in der Kategorienliste ausblenden</string>
<string name="diagnose_exf_esb">Overlay und Home-Button nicht zum Sperren verwenden</string>
<string name="diagnose_exf_srn">Toasts zur Synchronisation anzeigen</string>
<string name="diagnose_exf_ifd">neue App-Erkennungs-Methode verwenden</string>
<string name="diagnose_bg_task_loop_ex">Hintergrundaufgabenschleifenfehler</string>

View file

@ -575,6 +575,7 @@
<string name="diagnose_exf_hmw">Hide manipulation warning in the category list</string>
<string name="diagnose_exf_esb">Do not use a overlay or the home button for blocking</string>
<string name="diagnose_exf_srn">Show sync related toasts</string>
<string name="diagnose_exf_ifd">Use new App detection method</string>
<string name="diagnose_bg_task_loop_ex">Background task loop exception</string>