mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-05 10:49:26 +02:00
Add option to block Apps at system level
This commit is contained in:
parent
3fbf598eb5
commit
03bd3a5100
13 changed files with 427 additions and 18 deletions
|
@ -223,7 +223,8 @@ abstract class ConfigDao {
|
||||||
|
|
||||||
fun getEnableAlternativeDurationSelectionAsync() = getValueOfKeyAsync(ConfigurationItemType.EnableAlternativeDurationSelection).map { it == "1" }
|
fun getEnableAlternativeDurationSelectionAsync() = getValueOfKeyAsync(ConfigurationItemType.EnableAlternativeDurationSelection).map { it == "1" }
|
||||||
fun setEnableAlternativeDurationSelectionSync(enable: Boolean) = updateValueSync(ConfigurationItemType.EnableAlternativeDurationSelection, if (enable) "1" else "0")
|
fun setEnableAlternativeDurationSelectionSync(enable: Boolean) = updateValueSync(ConfigurationItemType.EnableAlternativeDurationSelection, if (enable) "1" else "0")
|
||||||
fun getExperimentalFlagsLive(): LiveData<Long> {
|
|
||||||
|
protected fun getExperimentalFlagsLive(): LiveData<Long> {
|
||||||
return getValueOfKeyAsync(ConfigurationItemType.ExperimentalFlags).map {
|
return getValueOfKeyAsync(ConfigurationItemType.ExperimentalFlags).map {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
0
|
0
|
||||||
|
@ -233,6 +234,8 @@ abstract class ConfigDao {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val experimentalFlags: LiveData<Long> by lazy { getExperimentalFlagsLive() }
|
||||||
|
|
||||||
private fun getExperimentalFlagsSync(): Long {
|
private fun getExperimentalFlagsSync(): Long {
|
||||||
val v = getValueOfKeySync(ConfigurationItemType.ExperimentalFlags)
|
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
|
(it and flags) == flags
|
||||||
}.ignoreUnchanged()
|
}.ignoreUnchanged()
|
||||||
|
|
||||||
|
@ -253,9 +256,9 @@ abstract class ConfigDao {
|
||||||
updateValueSync(
|
updateValueSync(
|
||||||
ConfigurationItemType.ExperimentalFlags,
|
ConfigurationItemType.ExperimentalFlags,
|
||||||
if (enable)
|
if (enable)
|
||||||
(getShownHintsSync() or flags).toString(16)
|
(getExperimentalFlagsSync() or flags).toString(16)
|
||||||
else
|
else
|
||||||
(getShownHintsSync() and (flags.inv())).toString(16)
|
(getExperimentalFlagsSync() and (flags.inv())).toString(16)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,4 +191,5 @@ object HintsToShow {
|
||||||
|
|
||||||
object ExperimentalFlags {
|
object ExperimentalFlags {
|
||||||
const val DISABLE_BLOCK_ON_MANIPULATION = 1L
|
const val DISABLE_BLOCK_ON_MANIPULATION = 1L
|
||||||
|
const val SYSTEM_LEVEL_BLOCKING = 2L
|
||||||
}
|
}
|
|
@ -52,6 +52,12 @@ object AndroidIntegrationApps {
|
||||||
ignoredApps["com.google.android.packageinstaller"] = AndroidIntegrationApps.IgnoredAppHandling.IgnoreOnStoreOtherwiseWhitelistAndDontDisable
|
ignoredApps["com.google.android.packageinstaller"] = AndroidIntegrationApps.IgnoredAppHandling.IgnoreOnStoreOtherwiseWhitelistAndDontDisable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val ignoredActivities = setOf<String>(
|
||||||
|
"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<App> {
|
fun getLocalApps(deviceId: String, context: Context): Collection<App> {
|
||||||
val packageManager = context.packageManager
|
val packageManager = context.packageManager
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,7 @@ class AppLogic(
|
||||||
}
|
}
|
||||||
|
|
||||||
val manipulationLogic = ManipulationLogic(this)
|
val manipulationLogic = ManipulationLogic(this)
|
||||||
|
val suspendAppsLogic = SuspendAppsLogic(this)
|
||||||
|
|
||||||
fun shutdown() {
|
fun shutdown() {
|
||||||
enable.value = false
|
enable.value = false
|
||||||
|
|
|
@ -296,7 +296,9 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
||||||
AndroidIntegrationApps.IgnoredAppHandling.Ignore -> true
|
AndroidIntegrationApps.IgnoredAppHandling.Ignore -> true
|
||||||
AndroidIntegrationApps.IgnoredAppHandling.IgnoreOnStoreOtherwiseWhitelistAndDontDisable -> BuildConfig.storeCompilant
|
AndroidIntegrationApps.IgnoredAppHandling.IgnoreOnStoreOtherwiseWhitelistAndDontDisable -> BuildConfig.storeCompilant
|
||||||
}
|
}
|
||||||
})
|
}) ||
|
||||||
|
(foregroundAppPackageName != null && foregroundAppActivityName != null &&
|
||||||
|
AndroidIntegrationApps.shouldIgnoreActivity(foregroundAppPackageName, foregroundAppActivityName))
|
||||||
) {
|
) {
|
||||||
usedTimeUpdateHelper?.commit(appLogic)
|
usedTimeUpdateHelper?.commit(appLogic)
|
||||||
showStatusMessageWithCurrentAppTitle(
|
showStatusMessageWithCurrentAppTitle(
|
||||||
|
|
|
@ -357,7 +357,7 @@ class BlockingReasonUtil(private val appLogic: AppLogic) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTemporarilyTrustedTimeInMillis(): LiveData<Long?> {
|
fun getTemporarilyTrustedTimeInMillis(): LiveData<Long?> {
|
||||||
val realTime = RealTime.newInstance()
|
val realTime = RealTime.newInstance()
|
||||||
|
|
||||||
return liveDataFromFunction {
|
return liveDataFromFunction {
|
||||||
|
@ -417,7 +417,7 @@ class BlockingReasonUtil(private val appLogic: AppLogic) {
|
||||||
}.ignoreUnchanged()
|
}.ignoreUnchanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTrustedDateLive(timeZone: TimeZone): LiveData<DateInTimezone?> {
|
fun getTrustedDateLive(timeZone: TimeZone): LiveData<DateInTimezone?> {
|
||||||
val realTime = RealTime.newInstance()
|
val realTime = RealTime.newInstance()
|
||||||
|
|
||||||
return object: LiveData<DateInTimezone?>() {
|
return object: LiveData<DateInTimezone?>() {
|
||||||
|
|
|
@ -0,0 +1,221 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<Long>,
|
||||||
|
timeZone: LiveData<TimeZone>,
|
||||||
|
categories: List<Category>
|
||||||
|
): LiveData<Map<String, BlockingReason>> {
|
||||||
|
val result = MediatorLiveData<Map<String, BlockingReason>>()
|
||||||
|
val status = mutableMapOf<String, BlockingReason>()
|
||||||
|
|
||||||
|
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<Long>,
|
||||||
|
timeZone: LiveData<TimeZone>,
|
||||||
|
categories: List<Category>
|
||||||
|
): Map<String, LiveData<BlockingReason>> {
|
||||||
|
val result = mutableMapOf<String, LiveData<BlockingReason>>()
|
||||||
|
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<Category>,
|
||||||
|
temporarilyTrustedMinuteOfWeek: LiveData<Int?>,
|
||||||
|
temporarilyTrustedDate: LiveData<DateInTimezone?>,
|
||||||
|
areLimitsTemporarilyDisabled: LiveData<Boolean>
|
||||||
|
): LiveData<BlockingReason> {
|
||||||
|
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<Long?>,
|
||||||
|
childDisableLimitsUntil: LiveData<Long>
|
||||||
|
): LiveData<Boolean> = childDisableLimitsUntil.switchMap { childDisableLimitsUntil ->
|
||||||
|
if (childDisableLimitsUntil == 0L) {
|
||||||
|
liveDataFromValue(false)
|
||||||
|
} else {
|
||||||
|
temporarilyTrustedTimeInMillis.map {
|
||||||
|
trustedTimeInMillis ->
|
||||||
|
|
||||||
|
trustedTimeInMillis != null && childDisableLimitsUntil > trustedTimeInMillis
|
||||||
|
}.ignoreUnchanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkCategoryBlockedTimeAreas(blockedMinutesInWeek: BitSet, temporarilyTrustedMinuteOfWeek: LiveData<Int?>): LiveData<BlockingReason> {
|
||||||
|
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<DateInTimezone?>,
|
||||||
|
rules: LiveData<List<TimeLimitRule>>,
|
||||||
|
category: Category
|
||||||
|
): LiveData<BlockingReason> = 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<TimeLimitRule>): LiveData<BlockingReason> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
163
app/src/main/java/io/timelimit/android/logic/SuspendAppsLogic.kt
Normal file
163
app/src/main/java/io/timelimit/android/logic/SuspendAppsLogic.kt
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<Map<String, BlockingReason>?> = 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<Map<String, BlockingReason>?>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
liveDataFromValue(null as Map<String, BlockingReason>?)
|
||||||
|
}
|
||||||
|
}.ignoreUnchanged()
|
||||||
|
|
||||||
|
private val appsToBlock = categoryBlockingReasons.switchMap { blockingReasons ->
|
||||||
|
if (blockingReasons == null) {
|
||||||
|
liveDataFromValue(emptyList<String>())
|
||||||
|
} else {
|
||||||
|
categoryData.switchMap { categories ->
|
||||||
|
when (categories) {
|
||||||
|
is NoChildUser -> liveDataFromValue(emptyList<String>())
|
||||||
|
is RealCategoryData -> {
|
||||||
|
knownInstalledApps.switchMap { installedApps ->
|
||||||
|
blockingAtActivityLevel.map { blockingAtActivityLevel ->
|
||||||
|
val prepared = getAppsWithCategories(installedApps, categories, blockingAtActivityLevel)
|
||||||
|
val result = mutableListOf<String>()
|
||||||
|
|
||||||
|
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<List<String>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val realAppsToBlock = appLogic.database.config().isExperimentalFlagsSetAsync(ExperimentalFlags.SYSTEM_LEVEL_BLOCKING).switchMap { systemLevelBlocking ->
|
||||||
|
if (systemLevelBlocking) {
|
||||||
|
appsToBlock
|
||||||
|
} else {
|
||||||
|
liveDataFromValue(emptyList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAppsWithCategories(packageNames: List<String>, data: RealCategoryData, blockingAtActivityLevel: Boolean): Map<String, Set<String>> {
|
||||||
|
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<String, Set<String>>()
|
||||||
|
|
||||||
|
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<String, Set<String>>()
|
||||||
|
|
||||||
|
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<Category>,
|
||||||
|
val categoryApps: List<CategoryApp>
|
||||||
|
): CategoryData()
|
|
@ -391,9 +391,11 @@ object ApplyServerDataStatus {
|
||||||
assignedAppsVersion = item.version
|
assignedAppsVersion = item.version
|
||||||
)
|
)
|
||||||
|
|
||||||
if (thisDeviceUserCategoryIds.contains(item.categoryId)) {
|
if (!database.config().isExperimentalFlagsSetSync(ExperimentalFlags.SYSTEM_LEVEL_BLOCKING)) {
|
||||||
runAsync {
|
if (thisDeviceUserCategoryIds.contains(item.categoryId)) {
|
||||||
platformIntegration.setSuspendedApps(item.assignedApps, false)
|
runAsync {
|
||||||
|
platformIntegration.setSuspendedApps(item.assignedApps, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import io.timelimit.android.async.Threads
|
||||||
import io.timelimit.android.coroutines.executeAndWait
|
import io.timelimit.android.coroutines.executeAndWait
|
||||||
import io.timelimit.android.crypto.Sha512
|
import io.timelimit.android.crypto.Sha512
|
||||||
import io.timelimit.android.data.Database
|
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.PendingSyncAction
|
||||||
import io.timelimit.android.data.model.PendingSyncActionType
|
import io.timelimit.android.data.model.PendingSyncActionType
|
||||||
import io.timelimit.android.data.model.UserType
|
import io.timelimit.android.data.model.UserType
|
||||||
|
@ -164,15 +165,17 @@ object ApplyActionUtil {
|
||||||
LocalDatabaseParentActionDispatcher.dispatchParentActionSync(action, database)
|
LocalDatabaseParentActionDispatcher.dispatchParentActionSync(action, database)
|
||||||
|
|
||||||
// disable suspending the assigned app
|
// disable suspending the assigned app
|
||||||
if (action is AddCategoryAppsAction) {
|
if (!database.config().isExperimentalFlagsSetSync(ExperimentalFlags.SYSTEM_LEVEL_BLOCKING)) {
|
||||||
val thisDeviceId = database.config().getOwnDeviceIdSync()!!
|
if (action is AddCategoryAppsAction) {
|
||||||
val thisDeviceEntry = database.device().getDeviceByIdSync(thisDeviceId)!!
|
val thisDeviceId = database.config().getOwnDeviceIdSync()!!
|
||||||
|
val thisDeviceEntry = database.device().getDeviceByIdSync(thisDeviceId)!!
|
||||||
|
|
||||||
if (thisDeviceEntry.currentUserId != "") {
|
if (thisDeviceEntry.currentUserId != "") {
|
||||||
val userCategories = database.category().getCategoriesByChildIdSync(thisDeviceEntry.currentUserId)
|
val userCategories = database.category().getCategoriesByChildIdSync(thisDeviceEntry.currentUserId)
|
||||||
|
|
||||||
if (userCategories.find { category -> category.id == action.categoryId } != null) {
|
if (userCategories.find { category -> category.id == action.categoryId } != null) {
|
||||||
platformIntegration.setSuspendedApps(action.packageNames, false)
|
platformIntegration.setSuspendedApps(action.packageNames, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ class DiagnoseExperimentalFlagFragment : Fragment() {
|
||||||
|
|
||||||
checkboxes.forEach { binding.container.addView(it) }
|
checkboxes.forEach { binding.container.addView(it) }
|
||||||
|
|
||||||
database.config().getExperimentalFlagsLive().observe(this, Observer { setFlags ->
|
database.config().experimentalFlags.observe(this, Observer { setFlags ->
|
||||||
flags.forEachIndexed { index, flag ->
|
flags.forEachIndexed { index, flag ->
|
||||||
val checkbox = checkboxes[index]
|
val checkbox = checkboxes[index]
|
||||||
val isFlagSet = (setFlags and flag.flag) == flag.flag
|
val isFlagSet = (setFlags and flag.flag) == flag.flag
|
||||||
|
@ -96,6 +96,11 @@ data class DiagnoseExperimentalFlagItem(
|
||||||
label = R.string.diagnose_exf_lom,
|
label = R.string.diagnose_exf_lom,
|
||||||
flag = ExperimentalFlags.DISABLE_BLOCK_ON_MANIPULATION,
|
flag = ExperimentalFlags.DISABLE_BLOCK_ON_MANIPULATION,
|
||||||
enable = !BuildConfig.storeCompilant
|
enable = !BuildConfig.storeCompilant
|
||||||
|
),
|
||||||
|
DiagnoseExperimentalFlagItem(
|
||||||
|
label = R.string.diagnose_exf_slb,
|
||||||
|
flag = ExperimentalFlags.SYSTEM_LEVEL_BLOCKING,
|
||||||
|
enable = !BuildConfig.storeCompilant
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,4 +54,5 @@
|
||||||
|
|
||||||
<string name="diagnose_exf_title">Experimentelle Parameter</string>
|
<string name="diagnose_exf_title">Experimentelle Parameter</string>
|
||||||
<string name="diagnose_exf_lom">Sperrung nach Manipulationen deaktivieren</string>
|
<string name="diagnose_exf_lom">Sperrung nach Manipulationen deaktivieren</string>
|
||||||
|
<string name="diagnose_exf_slb">Apps auf Systemebene sperren</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -54,4 +54,5 @@
|
||||||
|
|
||||||
<string name="diagnose_exf_title">Experimental flags</string>
|
<string name="diagnose_exf_title">Experimental flags</string>
|
||||||
<string name="diagnose_exf_lom">Disable locking after manipulations</string>
|
<string name="diagnose_exf_lom">Disable locking after manipulations</string>
|
||||||
|
<string name="diagnose_exf_slb">Block Apps at system level</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue