Add new restrictions that can be set by the device owner

This commit is contained in:
Jonas Lochmann 2025-07-21 02:00:00 +02:00
parent 6ba9614557
commit abe6d47a96
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
10 changed files with 100 additions and 14 deletions

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2025 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
@ -295,4 +295,7 @@ object ExperimentalFlags {
object ConsentFlags { object ConsentFlags {
const val APP_LIST_SYNC = 1L const val APP_LIST_SYNC = 1L
// this is used internally
const val BLOCK_USER_SWITCH_BY_DEFAULT = 2L
} }

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2025 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
@ -73,4 +73,5 @@ data class DeviceRelatedData (
} }
fun isExperimentalFlagSetSync(flags: Long) = (experimentalFlags and flags) == flags fun isExperimentalFlagSetSync(flags: Long) = (experimentalFlags and flags) == flags
fun isConsentFlagSet(flags: Long) = (consentFlags and flags) == flags
} }

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2025 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
@ -27,6 +27,10 @@ import io.timelimit.android.integration.platform.PlatformFeature
object AndroidFeatures { object AndroidFeatures {
private const val FEATURE_ADB = "adb" private const val FEATURE_ADB = "adb"
private const val FEATURE_CONFIG_PRIVATE_DNS = "dns" private const val FEATURE_CONFIG_PRIVATE_DNS = "dns"
const val FEATURE_ADD_USER = "add_user"
const val FEATURE_USER_SWITCH = "user_switch"
private const val FEATURE_VPN = "vpn"
private const val FEATURE_UNKNOWN_SOURCES = "unknown_sources"
fun applyBlockedFeatures(features: Set<String>, policyManager: DevicePolicyManager, admin: ComponentName): Boolean { fun applyBlockedFeatures(features: Set<String>, policyManager: DevicePolicyManager, admin: ComponentName): Boolean {
fun apply(feature: String, restriction: String) { fun apply(feature: String, restriction: String) {
@ -40,6 +44,18 @@ object AndroidFeatures {
apply(FEATURE_CONFIG_PRIVATE_DNS, UserManager.DISALLOW_CONFIG_PRIVATE_DNS) apply(FEATURE_CONFIG_PRIVATE_DNS, UserManager.DISALLOW_CONFIG_PRIVATE_DNS)
} }
apply(FEATURE_ADD_USER, UserManager.DISALLOW_ADD_USER)
if (VERSION.SDK_INT >= VERSION_CODES.P) {
apply(FEATURE_USER_SWITCH, UserManager.DISALLOW_USER_SWITCH)
}
apply(FEATURE_VPN, UserManager.DISALLOW_CONFIG_VPN)
if (VERSION.SDK_INT >= VERSION_CODES.Q) {
apply(FEATURE_UNKNOWN_SOURCES, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY)
}
return true return true
} }
@ -60,6 +76,30 @@ object AndroidFeatures {
) )
} }
result.add(PlatformFeature(
id = FEATURE_ADD_USER,
title = context.getString(R.string.dummy_app_feature_add_user)
))
if (VERSION.SDK_INT >= VERSION_CODES.P) {
result.add(PlatformFeature(
id = FEATURE_USER_SWITCH,
title = context.getString(R.string.dummy_app_feature_switch_user)
))
}
result.add(PlatformFeature(
id = FEATURE_VPN,
title = context.getString(R.string.dummy_app_feature_vpn)
))
if (VERSION.SDK_INT >= VERSION_CODES.Q) {
result.add(PlatformFeature(
id = FEATURE_UNKNOWN_SOURCES,
title = context.getString(R.string.dummy_app_feature_unknown_sources)
))
}
return result return result
} }
} }

View file

@ -507,7 +507,6 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
if (enableLockdown) { if (enableLockdown) {
// disable problematic features // disable problematic features
policyManager.addUserRestriction(deviceAdmin, UserManager.DISALLOW_ADD_USER)
policyManager.addUserRestriction(deviceAdmin, UserManager.DISALLOW_FACTORY_RESET) policyManager.addUserRestriction(deviceAdmin, UserManager.DISALLOW_FACTORY_RESET)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -557,7 +556,6 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
} }
} else /* disable lockdown */ { } else /* disable lockdown */ {
// enable problematic features // enable problematic features
policyManager.clearUserRestriction(deviceAdmin, UserManager.DISALLOW_ADD_USER)
policyManager.clearUserRestriction(deviceAdmin, UserManager.DISALLOW_FACTORY_RESET) policyManager.clearUserRestriction(deviceAdmin, UserManager.DISALLOW_FACTORY_RESET)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2025 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
@ -65,6 +65,11 @@ class AppSetupLogic(private val appLogic: AppLogic) {
appLogic.database.deleteAllData() appLogic.database.deleteAllData()
appLogic.database.config().setCustomServerUrlSync(customServerUrl) appLogic.database.config().setCustomServerUrlSync(customServerUrl)
appLogic.database.config().setConsentFlagSync(
ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT,
true
)
} }
run { run {

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2025 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
@ -19,10 +19,12 @@ import io.timelimit.android.async.Threads
import io.timelimit.android.data.invalidation.Observer import io.timelimit.android.data.invalidation.Observer
import io.timelimit.android.data.invalidation.Table import io.timelimit.android.data.invalidation.Table
import io.timelimit.android.data.model.CategoryApp import io.timelimit.android.data.model.CategoryApp
import io.timelimit.android.data.model.ConsentFlags
import io.timelimit.android.data.model.ExperimentalFlags import io.timelimit.android.data.model.ExperimentalFlags
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.integration.platform.ProtectionLevel import io.timelimit.android.integration.platform.ProtectionLevel
import io.timelimit.android.integration.platform.android.AndroidFeatures
import io.timelimit.android.integration.platform.android.AndroidIntegrationApps import io.timelimit.android.integration.platform.android.AndroidIntegrationApps
import io.timelimit.android.logic.blockingreason.CategoryHandlingCache import io.timelimit.android.logic.blockingreason.CategoryHandlingCache
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -107,12 +109,23 @@ class SuspendAppsLogic(private val appLogic: AppLogic): Observer {
val hasManagedFeatures = featureCategoryApps.isNotEmpty() val hasManagedFeatures = featureCategoryApps.isNotEmpty()
val enableBlocking = isRestrictedUser && (enableBlockingAtSystemLevel || hasManagedFeatures) val enableBlocking = isRestrictedUser && (enableBlockingAtSystemLevel || hasManagedFeatures)
val blockUserSwitchByDefault =
userAndDeviceRelatedData?.deviceRelatedData?.isConsentFlagSet(ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT) == true
&& userAndDeviceRelatedData.userRelatedData?.user?.type == UserType.Child
val featureToAllowDefaults = mapOf(
AndroidFeatures.FEATURE_ADD_USER to false,
AndroidFeatures.FEATURE_USER_SWITCH to !blockUserSwitchByDefault
)
if (!enableBlocking) { if (!enableBlocking) {
lastDefaultCategory = null lastDefaultCategory = null
lastAllowedCategoryList = emptySet() lastAllowedCategoryList = emptySet()
lastCategoryApps = emptyList() lastCategoryApps = emptyList()
applySuspendedApps(emptyList()) applySuspendedApps(emptyList())
applyBlockedFeatures(emptySet()) applyBlockedFeatures(
featureToAllowDefaults.filter { !it.value }.map { it.key }.toSet()
)
return return
} }
@ -191,9 +204,15 @@ class SuspendAppsLogic(private val appLogic: AppLogic): Observer {
val deviceSpecificFeatureIdentifiers = deviceSpecificFeatures.map { it.appSpecifierString }.toSet() val deviceSpecificFeatureIdentifiers = deviceSpecificFeatures.map { it.appSpecifierString }.toSet()
val globalFeatures = featureCategoryApps.filter { !deviceSpecificFeatureIdentifiers.contains(it.appSpecifierString) } val globalFeatures = featureCategoryApps.filter { !deviceSpecificFeatureIdentifiers.contains(it.appSpecifierString) }
val effectiveFeatures = deviceSpecificFeatures + globalFeatures val effectiveFeatures = deviceSpecificFeatures + globalFeatures
val featuresToBlock = effectiveFeatures.filter { !categoryIdsToAllow.contains(it.categoryId) }
.map { it.appSpecifierString.substring(DummyApps.FEATURE_APP_PREFIX.length) } val featuresToAllow = featureToAllowDefaults + effectiveFeatures.associate {
.toSet() Pair(
it.appSpecifierString.substring(DummyApps.FEATURE_APP_PREFIX.length),
categoryIdsToAllow.contains(it.categoryId)
)
}
val featuresToBlock = featuresToAllow.filter { !it.value }.map { it.key }.toSet()
applySuspendedApps(appsToBlock) applySuspendedApps(appsToBlock)
applyBlockedFeatures(featuresToBlock) applyBlockedFeatures(featuresToBlock)

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2025 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
@ -23,6 +23,7 @@ import io.timelimit.android.async.Threads
import io.timelimit.android.coroutines.executeAndWait import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.data.backup.DatabaseBackup import io.timelimit.android.data.backup.DatabaseBackup
import io.timelimit.android.data.devicename.DeviceName import io.timelimit.android.data.devicename.DeviceName
import io.timelimit.android.data.model.ConsentFlags
import io.timelimit.android.logic.AppLogic import io.timelimit.android.logic.AppLogic
import io.timelimit.android.sync.ApplyServerDataStatus import io.timelimit.android.sync.ApplyServerDataStatus
import io.timelimit.android.sync.network.NewDeviceInfo import io.timelimit.android.sync.network.NewDeviceInfo
@ -333,6 +334,11 @@ object SetupParentHandling {
database.config().setDeviceAuthTokenSync(result.deviceAuthToken) database.config().setDeviceAuthTokenSync(result.deviceAuthToken)
database.config().setEnableBackgroundSync(state.backgroundSync) database.config().setEnableBackgroundSync(state.backgroundSync)
database.config().setConsentFlagSync(
ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT,
true
)
ApplyServerDataStatus.applyServerDataStatusSync(result.serverDataStatus, logic.database, logic.platformIntegration) ApplyServerDataStatus.applyServerDataStatusSync(result.serverDataStatus, logic.database, logic.platformIntegration)
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2025 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
@ -24,6 +24,7 @@ import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.coroutines.runAsync import io.timelimit.android.coroutines.runAsync
import io.timelimit.android.data.backup.DatabaseBackup import io.timelimit.android.data.backup.DatabaseBackup
import io.timelimit.android.data.devicename.DeviceName import io.timelimit.android.data.devicename.DeviceName
import io.timelimit.android.data.model.ConsentFlags
import io.timelimit.android.livedata.castDown import io.timelimit.android.livedata.castDown
import io.timelimit.android.logic.AppLogic import io.timelimit.android.logic.AppLogic
import io.timelimit.android.logic.DefaultAppLogic import io.timelimit.android.logic.DefaultAppLogic
@ -70,6 +71,11 @@ class SetupRemoteChildViewModel(application: Application): AndroidViewModel(appl
logic.database.config().setOwnDeviceIdSync(registerResponse.ownDeviceId) logic.database.config().setOwnDeviceIdSync(registerResponse.ownDeviceId)
logic.database.config().setDeviceAuthTokenSync(registerResponse.deviceAuthToken) logic.database.config().setDeviceAuthTokenSync(registerResponse.deviceAuthToken)
logic.database.config().setConsentFlagSync(
ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT,
true
)
ApplyServerDataStatus.applyServerDataStatusSync(clientStatusResponse, logic.database, logic.platformIntegration) ApplyServerDataStatus.applyServerDataStatusSync(clientStatusResponse, logic.database, logic.platformIntegration)
} }
} }

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann TimeLimit Copyright <C> 2019 - 2025 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
the Free Software Foundation version 3 of the License. the Free Software Foundation version 3 of the License.
@ -1752,6 +1752,10 @@
<string name="dummy_app_unassigned_system_image_app">nicht zugeordnete Apps von der Systempartition</string> <string name="dummy_app_unassigned_system_image_app">nicht zugeordnete Apps von der Systempartition</string>
<string name="dummy_app_feature_adb">Entwickleroptionen</string> <string name="dummy_app_feature_adb">Entwickleroptionen</string>
<string name="dummy_app_feature_dns">DNS-Einstellungen</string> <string name="dummy_app_feature_dns">DNS-Einstellungen</string>
<string name="dummy_app_feature_add_user">Systembenutzer erstellen</string>
<string name="dummy_app_feature_switch_user">Systembenutzer wechseln</string>
<string name="dummy_app_feature_vpn">VPN konfigurieren</string>
<string name="dummy_app_feature_unknown_sources">Apps aus unbekannten Quellen installieren</string>
<string name="dummy_app_activity_audio">Hintergrundmusikwiedergabe</string> <string name="dummy_app_activity_audio">Hintergrundmusikwiedergabe</string>
<string name="notify_permission_title">Benachrichtigungen</string> <string name="notify_permission_title">Benachrichtigungen</string>

View file

@ -1804,6 +1804,10 @@
<string name="dummy_app_unassigned_system_image_app">not assigned Apps from the system image</string> <string name="dummy_app_unassigned_system_image_app">not assigned Apps from the system image</string>
<string name="dummy_app_feature_adb">Developer Options</string> <string name="dummy_app_feature_adb">Developer Options</string>
<string name="dummy_app_feature_dns">DNS Settings</string> <string name="dummy_app_feature_dns">DNS Settings</string>
<string name="dummy_app_feature_add_user">Create System User</string>
<string name="dummy_app_feature_switch_user">Switch System User</string>
<string name="dummy_app_feature_vpn">Configure VPN</string>
<string name="dummy_app_feature_unknown_sources">Install Apps from unknown sources</string>
<string name="dummy_app_activity_audio">Background Audio Playback</string> <string name="dummy_app_activity_audio">Background Audio Playback</string>
<string name="notify_permission_title">Notifications</string> <string name="notify_permission_title">Notifications</string>