From 03bd3a510032096a1bba4b4376f308fd5c08d772 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 19 Aug 2019 00:00:00 +0000 Subject: [PATCH] Add option to block Apps at system level --- .../timelimit/android/data/dao/ConfigDao.kt | 11 +- .../android/data/model/ConfigurationItem.kt | 1 + .../integration/platform/android/Apps.kt | 6 + .../io/timelimit/android/logic/AppLogic.kt | 1 + .../android/logic/BackgroundTaskLogic.kt | 4 +- .../timelimit/android/logic/BlockingReason.kt | 4 +- .../logic/CategoriesBlockingReasons.kt | 221 ++++++++++++++++++ .../android/logic/SuspendAppsLogic.kt | 163 +++++++++++++ .../android/sync/ApplyServerDataStatus.kt | 8 +- .../android/sync/actions/apply/ApplyAction.kt | 17 +- .../DiagnoseExperimentalFlagFragment.kt | 7 +- .../main/res/values-de/strings-diagnose.xml | 1 + app/src/main/res/values/strings-diagnose.xml | 1 + 13 files changed, 427 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/io/timelimit/android/logic/CategoriesBlockingReasons.kt create mode 100644 app/src/main/java/io/timelimit/android/logic/SuspendAppsLogic.kt diff --git a/app/src/main/java/io/timelimit/android/data/dao/ConfigDao.kt b/app/src/main/java/io/timelimit/android/data/dao/ConfigDao.kt index 4c6576b..2fa9489 100644 --- a/app/src/main/java/io/timelimit/android/data/dao/ConfigDao.kt +++ b/app/src/main/java/io/timelimit/android/data/dao/ConfigDao.kt @@ -223,7 +223,8 @@ abstract class ConfigDao { fun getEnableAlternativeDurationSelectionAsync() = getValueOfKeyAsync(ConfigurationItemType.EnableAlternativeDurationSelection).map { it == "1" } fun setEnableAlternativeDurationSelectionSync(enable: Boolean) = updateValueSync(ConfigurationItemType.EnableAlternativeDurationSelection, if (enable) "1" else "0") - fun getExperimentalFlagsLive(): LiveData { + + protected fun getExperimentalFlagsLive(): LiveData { return getValueOfKeyAsync(ConfigurationItemType.ExperimentalFlags).map { if (it == null) { 0 @@ -233,6 +234,8 @@ abstract class ConfigDao { } } + val experimentalFlags: LiveData by lazy { getExperimentalFlagsLive() } + private fun getExperimentalFlagsSync(): Long { val v = getValueOfKeySync(ConfigurationItemType.ExperimentalFlags) @@ -243,7 +246,7 @@ abstract class ConfigDao { } } - fun isExperimentalFlagsSetAsync(flags: Long) = getExperimentalFlagsLive().map { + fun isExperimentalFlagsSetAsync(flags: Long) = experimentalFlags.map { (it and flags) == flags }.ignoreUnchanged() @@ -253,9 +256,9 @@ abstract class ConfigDao { updateValueSync( ConfigurationItemType.ExperimentalFlags, if (enable) - (getShownHintsSync() or flags).toString(16) + (getExperimentalFlagsSync() or flags).toString(16) else - (getShownHintsSync() and (flags.inv())).toString(16) + (getExperimentalFlagsSync() and (flags.inv())).toString(16) ) } } diff --git a/app/src/main/java/io/timelimit/android/data/model/ConfigurationItem.kt b/app/src/main/java/io/timelimit/android/data/model/ConfigurationItem.kt index c6c683c..21b6227 100644 --- a/app/src/main/java/io/timelimit/android/data/model/ConfigurationItem.kt +++ b/app/src/main/java/io/timelimit/android/data/model/ConfigurationItem.kt @@ -191,4 +191,5 @@ object HintsToShow { object ExperimentalFlags { const val DISABLE_BLOCK_ON_MANIPULATION = 1L + const val SYSTEM_LEVEL_BLOCKING = 2L } \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/Apps.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/Apps.kt index 1436b2a..dd7daab 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/android/Apps.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/Apps.kt @@ -52,6 +52,12 @@ object AndroidIntegrationApps { ignoredApps["com.google.android.packageinstaller"] = AndroidIntegrationApps.IgnoredAppHandling.IgnoreOnStoreOtherwiseWhitelistAndDontDisable } + private val ignoredActivities = setOf( + "com.android.settings:com.android.settings.enterprise.ActionDisabledByAdminDialog" + ) + + fun shouldIgnoreActivity(packageName: String, activityName: String) = ignoredActivities.contains("$packageName:$activityName") + fun getLocalApps(deviceId: String, context: Context): Collection { val packageManager = context.packageManager diff --git a/app/src/main/java/io/timelimit/android/logic/AppLogic.kt b/app/src/main/java/io/timelimit/android/logic/AppLogic.kt index 3404884..34e3099 100644 --- a/app/src/main/java/io/timelimit/android/logic/AppLogic.kt +++ b/app/src/main/java/io/timelimit/android/logic/AppLogic.kt @@ -99,6 +99,7 @@ class AppLogic( } val manipulationLogic = ManipulationLogic(this) + val suspendAppsLogic = SuspendAppsLogic(this) fun shutdown() { enable.value = false diff --git a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt index 3a2d1fd..b839ce3 100644 --- a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt +++ b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt @@ -296,7 +296,9 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { AndroidIntegrationApps.IgnoredAppHandling.Ignore -> true AndroidIntegrationApps.IgnoredAppHandling.IgnoreOnStoreOtherwiseWhitelistAndDontDisable -> BuildConfig.storeCompilant } - }) + }) || + (foregroundAppPackageName != null && foregroundAppActivityName != null && + AndroidIntegrationApps.shouldIgnoreActivity(foregroundAppPackageName, foregroundAppActivityName)) ) { usedTimeUpdateHelper?.commit(appLogic) showStatusMessageWithCurrentAppTitle( diff --git a/app/src/main/java/io/timelimit/android/logic/BlockingReason.kt b/app/src/main/java/io/timelimit/android/logic/BlockingReason.kt index a69e76e..1f2dda5 100644 --- a/app/src/main/java/io/timelimit/android/logic/BlockingReason.kt +++ b/app/src/main/java/io/timelimit/android/logic/BlockingReason.kt @@ -357,7 +357,7 @@ class BlockingReasonUtil(private val appLogic: AppLogic) { } } - private fun getTemporarilyTrustedTimeInMillis(): LiveData { + fun getTemporarilyTrustedTimeInMillis(): LiveData { val realTime = RealTime.newInstance() return liveDataFromFunction { @@ -417,7 +417,7 @@ class BlockingReasonUtil(private val appLogic: AppLogic) { }.ignoreUnchanged() } - private fun getTrustedDateLive(timeZone: TimeZone): LiveData { + fun getTrustedDateLive(timeZone: TimeZone): LiveData { val realTime = RealTime.newInstance() return object: LiveData() { diff --git a/app/src/main/java/io/timelimit/android/logic/CategoriesBlockingReasons.kt b/app/src/main/java/io/timelimit/android/logic/CategoriesBlockingReasons.kt new file mode 100644 index 0000000..cb71015 --- /dev/null +++ b/app/src/main/java/io/timelimit/android/logic/CategoriesBlockingReasons.kt @@ -0,0 +1,221 @@ +/* + * TimeLimit Copyright 2019 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 . + */ +package io.timelimit.android.logic + +import android.util.Log +import android.util.SparseLongArray +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import io.timelimit.android.BuildConfig +import io.timelimit.android.data.model.* +import io.timelimit.android.date.DateInTimezone +import io.timelimit.android.livedata.* +import java.util.* + +class CategoriesBlockingReasonUtil(private val appLogic: AppLogic) { + companion object { + private const val LOG_TAG = "CategoryBlockingReason" + } + + private val blockingReason = BlockingReasonUtil(appLogic) + private val temporarilyTrustedTimeInMillis = blockingReason.getTemporarilyTrustedTimeInMillis() + + // NOTE: this ignores the current device rule + fun getCategoryBlockingReasons( + childDisableLimitsUntil: LiveData, + timeZone: LiveData, + categories: List + ): LiveData> { + val result = MediatorLiveData>() + val status = mutableMapOf() + + val reasons = getCategoryBlockingReasonsInternal(childDisableLimitsUntil, timeZone, categories) + var missing = reasons.size + + reasons.entries.forEach { (k, v) -> + var ready = false + + result.addSource(v) { newStatus -> + status[k] = newStatus + + if (!ready) { + ready = true + missing-- + } + + if (missing == 0) { + result.value = status.toMap() + } + } + } + + return result + } + + // NOTE: this ignores the current device rule + private fun getCategoryBlockingReasonsInternal( + childDisableLimitsUntil: LiveData, + timeZone: LiveData, + categories: List + ): Map> { + val result = mutableMapOf>() + val categoryById = categories.associateBy { it.id } + + val areLimitsTemporarilyDisabled = areLimitsDisabled( + temporarilyTrustedTimeInMillis = temporarilyTrustedTimeInMillis, + childDisableLimitsUntil = childDisableLimitsUntil + ) + + val temporarilyTrustedMinuteOfWeek = timeZone.switchMap { timeZone -> + blockingReason.getTrustedMinuteOfWeekLive(timeZone) + } + + val temporarilyTrustedDate = timeZone.switchMap { timeZone -> + blockingReason.getTrustedDateLive(timeZone) + } + + fun handleCategory(categoryId: String) { + categoryById[categoryId]?.let { category -> + result[categoryId] = result[categoryId] ?: getCategoryBlockingReason( + category = liveDataFromValue(category), + temporarilyTrustedMinuteOfWeek = temporarilyTrustedMinuteOfWeek, + temporarilyTrustedDate = temporarilyTrustedDate, + areLimitsTemporarilyDisabled = areLimitsTemporarilyDisabled + ) + } + } + + categoryById.keys.forEach { handleCategory(it) } + + return result + } + + // NOTE: this ignores parent categories (otherwise would check parent category if category has no blocking reason) + // NOTE: this ignores the current device rule + private fun getCategoryBlockingReason( + category: LiveData, + temporarilyTrustedMinuteOfWeek: LiveData, + temporarilyTrustedDate: LiveData, + areLimitsTemporarilyDisabled: LiveData + ): LiveData { + return category.switchMap { category -> + if (category.temporarilyBlocked) { + liveDataFromValue(BlockingReason.TemporarilyBlocked) + } else { + areLimitsTemporarilyDisabled.switchMap { areLimitsTemporarilyDisabled -> + if (areLimitsTemporarilyDisabled) { + liveDataFromValue(BlockingReason.None) + } else { + checkCategoryBlockedTimeAreas( + temporarilyTrustedMinuteOfWeek = temporarilyTrustedMinuteOfWeek, + blockedMinutesInWeek = category.blockedMinutesInWeek.dataNotToModify + ).switchMap { blockedTimeAreasReason -> + if (blockedTimeAreasReason != BlockingReason.None) { + liveDataFromValue(blockedTimeAreasReason) + } else { + checkCategoryTimeLimitRules( + temporarilyTrustedDate = temporarilyTrustedDate, + category = category, + rules = appLogic.database.timeLimitRules().getTimeLimitRulesByCategory(category.id) + ) + } + } + } + } + } + } + } + + private fun areLimitsDisabled( + temporarilyTrustedTimeInMillis: LiveData, + childDisableLimitsUntil: LiveData + ): LiveData = childDisableLimitsUntil.switchMap { childDisableLimitsUntil -> + if (childDisableLimitsUntil == 0L) { + liveDataFromValue(false) + } else { + temporarilyTrustedTimeInMillis.map { + trustedTimeInMillis -> + + trustedTimeInMillis != null && childDisableLimitsUntil > trustedTimeInMillis + }.ignoreUnchanged() + } + } + + private fun checkCategoryBlockedTimeAreas(blockedMinutesInWeek: BitSet, temporarilyTrustedMinuteOfWeek: LiveData): LiveData { + if (blockedMinutesInWeek.isEmpty) { + return liveDataFromValue(BlockingReason.None) + } else { + return temporarilyTrustedMinuteOfWeek.map { temporarilyTrustedMinuteOfWeek -> + if (temporarilyTrustedMinuteOfWeek == null) { + BlockingReason.MissingNetworkTime + } else if (blockedMinutesInWeek[temporarilyTrustedMinuteOfWeek]) { + BlockingReason.BlockedAtThisTime + } else { + BlockingReason.None + } + }.ignoreUnchanged() + } + } + + private fun checkCategoryTimeLimitRules( + temporarilyTrustedDate: LiveData, + rules: LiveData>, + category: Category + ): LiveData = rules.switchMap { rules -> + if (rules.isEmpty()) { + liveDataFromValue(BlockingReason.None) + } else { + temporarilyTrustedDate.switchMap { temporarilyTrustedDate -> + if (temporarilyTrustedDate == null) { + liveDataFromValue(BlockingReason.MissingNetworkTime) + } else { + getBlockingReasonStep7( + category = category, + nowTrustedDate = temporarilyTrustedDate, + rules = rules + ) + } + } + } + } + + private fun getBlockingReasonStep7(category: Category, nowTrustedDate: DateInTimezone, rules: List): LiveData { + if (BuildConfig.DEBUG) { + Log.d(LOG_TAG, "step 7") + } + + return appLogic.database.usedTimes().getUsedTimesOfWeek(category.id, nowTrustedDate.dayOfEpoch - nowTrustedDate.dayOfWeek).map { usedTimes -> + val usedTimesSparseArray = SparseLongArray() + + for (i in 0..6) { + val usedTimesItem = usedTimes[i]?.usedMillis + usedTimesSparseArray.put(i, (if (usedTimesItem != null) usedTimesItem else 0)) + } + + val remaining = RemainingTime.getRemainingTime(nowTrustedDate.dayOfWeek, usedTimesSparseArray, rules, category.extraTimeInMillis) + + if (remaining == null || remaining.includingExtraTime > 0) { + BlockingReason.None + } else { + if (category.extraTimeInMillis > 0) { + BlockingReason.TimeOverExtraTimeCanBeUsedLater + } else { + BlockingReason.TimeOver + } + } + }.ignoreUnchanged() + } +} diff --git a/app/src/main/java/io/timelimit/android/logic/SuspendAppsLogic.kt b/app/src/main/java/io/timelimit/android/logic/SuspendAppsLogic.kt new file mode 100644 index 0000000..c5db48d --- /dev/null +++ b/app/src/main/java/io/timelimit/android/logic/SuspendAppsLogic.kt @@ -0,0 +1,163 @@ +/* + * TimeLimit Copyright 2019 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 . + */ +package io.timelimit.android.logic + +import androidx.lifecycle.LiveData +import io.timelimit.android.data.model.Category +import io.timelimit.android.data.model.CategoryApp +import io.timelimit.android.data.model.ExperimentalFlags +import io.timelimit.android.data.model.UserType +import io.timelimit.android.livedata.ignoreUnchanged +import io.timelimit.android.livedata.liveDataFromValue +import io.timelimit.android.livedata.map +import io.timelimit.android.livedata.switchMap +import java.util.* + +class SuspendAppsLogic(private val appLogic: AppLogic) { + private val blockingAtActivityLevel = appLogic.deviceEntry.map { it?.enableActivityLevelBlocking ?: false } + private val blockingReasonUtil = CategoriesBlockingReasonUtil(appLogic) + + private val knownInstalledApps = appLogic.deviceId.switchMap { deviceId -> + if (deviceId.isNullOrEmpty()) { + liveDataFromValue(emptyList()) + } else { + appLogic.database.app().getAppsByDeviceIdAsync(deviceId).map { apps -> + apps.map { it.packageName } + } + } + }.ignoreUnchanged() + + private val categoryData = appLogic.deviceUserEntry.switchMap { deviceUser -> + if (deviceUser?.type == UserType.Child) { + appLogic.database.category().getCategoriesByChildId(deviceUser.id).switchMap { categories -> + appLogic.database.categoryApp().getCategoryApps(categories.map { it.id }).map { categoryApps -> + RealCategoryData( + categoryForUnassignedApps = deviceUser.categoryForNotAssignedApps, + categories = categories, + categoryApps = categoryApps + ) as CategoryData + } + } + } else { + liveDataFromValue(NoChildUser as CategoryData) + } + }.ignoreUnchanged() + + private val categoryBlockingReasons: LiveData?> = appLogic.deviceUserEntry.switchMap { deviceUser -> + if (deviceUser?.type == UserType.Child) { + appLogic.database.category().getCategoriesByChildId(deviceUser.id).switchMap { categories -> + blockingReasonUtil.getCategoryBlockingReasons( + childDisableLimitsUntil = liveDataFromValue(deviceUser.disableLimitsUntil), + timeZone = liveDataFromValue(TimeZone.getTimeZone(deviceUser.timeZone)), + categories = categories + ) as LiveData?> + } + } else { + liveDataFromValue(null as Map?) + } + }.ignoreUnchanged() + + private val appsToBlock = categoryBlockingReasons.switchMap { blockingReasons -> + if (blockingReasons == null) { + liveDataFromValue(emptyList()) + } else { + categoryData.switchMap { categories -> + when (categories) { + is NoChildUser -> liveDataFromValue(emptyList()) + is RealCategoryData -> { + knownInstalledApps.switchMap { installedApps -> + blockingAtActivityLevel.map { blockingAtActivityLevel -> + val prepared = getAppsWithCategories(installedApps, categories, blockingAtActivityLevel) + val result = mutableListOf() + + installedApps.forEach { packageName -> + val appCategories = prepared[packageName] ?: emptySet() + + if (appCategories.find { categoryId -> (blockingReasons[categoryId] ?: BlockingReason.None) == BlockingReason.None } == null) { + result.add(packageName) + } + } + + result + } + } + } + } as LiveData> + } + } + } + + private val realAppsToBlock = appLogic.database.config().isExperimentalFlagsSetAsync(ExperimentalFlags.SYSTEM_LEVEL_BLOCKING).switchMap { systemLevelBlocking -> + if (systemLevelBlocking) { + appsToBlock + } else { + liveDataFromValue(emptyList()) + } + } + + private fun getAppsWithCategories(packageNames: List, data: RealCategoryData, blockingAtActivityLevel: Boolean): Map> { + val categoryForUnassignedApps = if (data.categories.find { it.id == data.categoryForUnassignedApps } != null) data.categoryForUnassignedApps else null + + if (blockingAtActivityLevel) { + val categoriesByPackageName = data.categoryApps.groupBy { it.packageNameWithoutActivityName } + + val result = mutableMapOf>() + + packageNames.forEach { packageName -> + val categoriesItems = categoriesByPackageName[packageName] + val categories = (categoriesItems?.map { it.categoryId }?.toSet() ?: emptySet()).toMutableSet() + val isMainAppIncluded = categoriesItems?.find { !it.specifiesActivity } != null + + if (!isMainAppIncluded) { + if (categoryForUnassignedApps != null) { + categories.add(categoryForUnassignedApps) + } + } + + result[packageName] = categories + } + + return result + } else { + val categoryByPackageName = data.categoryApps.associateBy { it.packageName } + + val result = mutableMapOf>() + + packageNames.forEach { packageName -> + val category = categoryByPackageName[packageName]?.categoryId ?: categoryForUnassignedApps + + result[packageName] = if (category != null) setOf(category) else emptySet() + } + + return result + } + } + + init { + realAppsToBlock.observeForever { appsToBlock -> + appLogic.platformIntegration.stopSuspendingForAllApps() + appLogic.platformIntegration.setSuspendedApps(appsToBlock, true) + } + } +} + +internal sealed class CategoryData +internal object NoChildUser: CategoryData() +internal class RealCategoryData( + val categoryForUnassignedApps: String, + val categories: List, + val categoryApps: List +): CategoryData() \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt b/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt index 536db54..4683ddc 100644 --- a/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt +++ b/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt @@ -391,9 +391,11 @@ object ApplyServerDataStatus { assignedAppsVersion = item.version ) - if (thisDeviceUserCategoryIds.contains(item.categoryId)) { - runAsync { - platformIntegration.setSuspendedApps(item.assignedApps, false) + if (!database.config().isExperimentalFlagsSetSync(ExperimentalFlags.SYSTEM_LEVEL_BLOCKING)) { + if (thisDeviceUserCategoryIds.contains(item.categoryId)) { + runAsync { + platformIntegration.setSuspendedApps(item.assignedApps, false) + } } } } diff --git a/app/src/main/java/io/timelimit/android/sync/actions/apply/ApplyAction.kt b/app/src/main/java/io/timelimit/android/sync/actions/apply/ApplyAction.kt index a757180..845e4fd 100644 --- a/app/src/main/java/io/timelimit/android/sync/actions/apply/ApplyAction.kt +++ b/app/src/main/java/io/timelimit/android/sync/actions/apply/ApplyAction.kt @@ -22,6 +22,7 @@ import io.timelimit.android.async.Threads import io.timelimit.android.coroutines.executeAndWait import io.timelimit.android.crypto.Sha512 import io.timelimit.android.data.Database +import io.timelimit.android.data.model.ExperimentalFlags import io.timelimit.android.data.model.PendingSyncAction import io.timelimit.android.data.model.PendingSyncActionType import io.timelimit.android.data.model.UserType @@ -164,15 +165,17 @@ object ApplyActionUtil { LocalDatabaseParentActionDispatcher.dispatchParentActionSync(action, database) // disable suspending the assigned app - if (action is AddCategoryAppsAction) { - val thisDeviceId = database.config().getOwnDeviceIdSync()!! - val thisDeviceEntry = database.device().getDeviceByIdSync(thisDeviceId)!! + if (!database.config().isExperimentalFlagsSetSync(ExperimentalFlags.SYSTEM_LEVEL_BLOCKING)) { + if (action is AddCategoryAppsAction) { + val thisDeviceId = database.config().getOwnDeviceIdSync()!! + val thisDeviceEntry = database.device().getDeviceByIdSync(thisDeviceId)!! - if (thisDeviceEntry.currentUserId != "") { - val userCategories = database.category().getCategoriesByChildIdSync(thisDeviceEntry.currentUserId) + if (thisDeviceEntry.currentUserId != "") { + val userCategories = database.category().getCategoriesByChildIdSync(thisDeviceEntry.currentUserId) - if (userCategories.find { category -> category.id == action.categoryId } != null) { - platformIntegration.setSuspendedApps(action.packageNames, false) + if (userCategories.find { category -> category.id == action.categoryId } != null) { + platformIntegration.setSuspendedApps(action.packageNames, false) + } } } } diff --git a/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExperimentalFlagFragment.kt b/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExperimentalFlagFragment.kt index b1dffe1..ddc292a 100644 --- a/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExperimentalFlagFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExperimentalFlagFragment.kt @@ -60,7 +60,7 @@ class DiagnoseExperimentalFlagFragment : Fragment() { checkboxes.forEach { binding.container.addView(it) } - database.config().getExperimentalFlagsLive().observe(this, Observer { setFlags -> + database.config().experimentalFlags.observe(this, Observer { setFlags -> flags.forEachIndexed { index, flag -> val checkbox = checkboxes[index] val isFlagSet = (setFlags and flag.flag) == flag.flag @@ -96,6 +96,11 @@ data class DiagnoseExperimentalFlagItem( label = R.string.diagnose_exf_lom, flag = ExperimentalFlags.DISABLE_BLOCK_ON_MANIPULATION, enable = !BuildConfig.storeCompilant + ), + DiagnoseExperimentalFlagItem( + label = R.string.diagnose_exf_slb, + flag = ExperimentalFlags.SYSTEM_LEVEL_BLOCKING, + enable = !BuildConfig.storeCompilant ) ) } diff --git a/app/src/main/res/values-de/strings-diagnose.xml b/app/src/main/res/values-de/strings-diagnose.xml index 7436722..20eda08 100644 --- a/app/src/main/res/values-de/strings-diagnose.xml +++ b/app/src/main/res/values-de/strings-diagnose.xml @@ -54,4 +54,5 @@ Experimentelle Parameter Sperrung nach Manipulationen deaktivieren + Apps auf Systemebene sperren diff --git a/app/src/main/res/values/strings-diagnose.xml b/app/src/main/res/values/strings-diagnose.xml index f0da369..b1a0e14 100644 --- a/app/src/main/res/values/strings-diagnose.xml +++ b/app/src/main/res/values/strings-diagnose.xml @@ -54,4 +54,5 @@ Experimental flags Disable locking after manipulations + Block Apps at system level