Compare commits

...

6 commits

Author SHA1 Message Date
Jonas Lochmann
6c02c4e4c7
Release 7.3.0 2025-07-21 02:00:00 +02:00
Jonas Lochmann
c746770e89
Increase target sdk to 36 2025-07-21 02:00:00 +02:00
Jonas Lochmann
0505fa0f8b
Extend .gitignore 2025-07-21 02:00:00 +02:00
Jonas Lochmann
4498aa191f
Update Google Play Billing Library 2025-07-21 02:00:00 +02:00
Jonas Lochmann
3d49bd8229
Update buildtools and dependencies 2025-07-21 02:00:00 +02:00
Jonas Lochmann
abe6d47a96
Add new restrictions that can be set by the device owner 2025-07-21 02:00:00 +02:00
19 changed files with 141 additions and 40 deletions

1
.gitignore vendored
View file

@ -10,3 +10,4 @@
/captures /captures
.externalNativeBuild .externalNativeBuild
.idea .idea
.kotlin

View file

@ -21,18 +21,18 @@ plugins {
id "androidx.navigation.safeargs.kotlin" id "androidx.navigation.safeargs.kotlin"
id 'kotlin-kapt' id 'kotlin-kapt'
id 'com.squareup.wire' id 'com.squareup.wire'
id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" id("org.jetbrains.kotlin.plugin.compose") version "2.1.10"
} }
android { android {
namespace 'io.timelimit.android' namespace 'io.timelimit.android'
compileSdk 35 compileSdk 36
defaultConfig { defaultConfig {
applicationId "io.timelimit.android" applicationId "io.timelimit.android"
minSdkVersion 26 minSdkVersion 26
targetSdkVersion 35 targetSdkVersion 36
versionCode 223 versionCode 224
versionName "7.2.4" versionName "7.3.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
kapt { kapt {
arguments { arguments {
@ -172,7 +172,7 @@ dependencies {
def paging_version = "3.3.6" def paging_version = "3.3.6"
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.10"
implementation 'androidx.appcompat:appcompat:1.7.1' implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'androidx.core:core:1.16.0' implementation 'androidx.core:core:1.16.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
@ -201,8 +201,8 @@ dependencies {
implementation "androidx.work:work-runtime-ktx:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version"
// androidTestImplementation "android.arch.work:work-testing:$work_version" // androidTestImplementation "android.arch.work:work-testing:$work_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.6.2' androidTestImplementation 'androidx.test:runner:1.6.2'
@ -218,7 +218,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp-tls:4.9.3' implementation 'com.squareup.okhttp3:okhttp-tls:4.9.3'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3' implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
googleApiImplementation "com.android.billingclient:billing-ktx:7.1.1" googleApiImplementation "com.android.billingclient:billing-ktx:8.0.0"
implementation('io.socket:socket.io-client:2.0.0') { implementation('io.socket:socket.io-client:2.0.0') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
@ -230,5 +230,5 @@ dependencies {
implementation 'com.google.zxing:core:3.3.3' implementation 'com.google.zxing:core:3.3.3'
api "com.squareup.wire:wire-runtime:4.4.3" api "com.squareup.wire:wire-runtime:5.3.5"
} }

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
@ -69,13 +69,13 @@ abstract class UsedTimeDao {
// breaking it into multiple lines causes issues during compilation ... // breaking it into multiple lines causes issues during compilation ...
// this warns about an unused column, but this column is used in the ORDER BY // this warns about an unused column, but this column is used in the ORDER BY
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @SuppressWarnings(RoomWarnings.QUERY_MISMATCH)
@Query("SELECT 2 AS type, start_time_of_day AS startMinuteOfDay, end_time_of_day AS endMinuteOfDay, used_time AS duration, day_of_epoch AS day, NULL AS lastUsage, NULL AS maxSessionDuration, NULL AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM used_time JOIN category ON (used_time.category_id = category.id) WHERE category.id = :categoryId UNION ALL SELECT 1 AS type, start_minute_of_day AS startMinuteOfDay, end_minute_of_day AS endMinuteOfDay, last_session_duration AS duration, NULL AS day, last_usage AS lastUsage, max_session_duration AS maxSessionDuration, session_pause_duration AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM session_duration JOIN category ON (session_duration.category_id = category.id) WHERE category.id = :categoryId ORDER BY type, day DESC, lastUsage DESC, startMinuteOfDay, endMinuteOfDay, categoryId") @Query("SELECT 2 AS type, start_time_of_day AS startMinuteOfDay, end_time_of_day AS endMinuteOfDay, used_time AS duration, day_of_epoch AS day, NULL AS lastUsage, NULL AS maxSessionDuration, NULL AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM used_time JOIN category ON (used_time.category_id = category.id) WHERE category.id = :categoryId UNION ALL SELECT 1 AS type, start_minute_of_day AS startMinuteOfDay, end_minute_of_day AS endMinuteOfDay, last_session_duration AS duration, NULL AS day, last_usage AS lastUsage, max_session_duration AS maxSessionDuration, session_pause_duration AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM session_duration JOIN category ON (session_duration.category_id = category.id) WHERE category.id = :categoryId ORDER BY type, day DESC, lastUsage DESC, startMinuteOfDay, endMinuteOfDay, categoryId")
abstract fun getUsedTimeListItemsByCategoryId(categoryId: String): Flow<List<UsedTimeListItem>> abstract fun getUsedTimeListItemsByCategoryId(categoryId: String): Flow<List<UsedTimeListItem>>
// breaking it into multiple lines causes issues during compilation ... // breaking it into multiple lines causes issues during compilation ...
// this warns about an unused column, but this column is used in the ORDER BY // this warns about an unused column, but this column is used in the ORDER BY
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @SuppressWarnings(RoomWarnings.QUERY_MISMATCH)
@Query("SELECT 2 AS type, start_time_of_day AS startMinuteOfDay, end_time_of_day AS endMinuteOfDay, used_time AS duration, day_of_epoch AS day, NULL AS lastUsage, NULL AS maxSessionDuration, NULL AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM used_time JOIN category ON (used_time.category_id = category.id) WHERE category.child_id = :userId UNION ALL SELECT 1 AS type, start_minute_of_day AS startMinuteOfDay, end_minute_of_day AS endMinuteOfDay, last_session_duration AS duration, NULL AS day, last_usage AS lastUsage, max_session_duration AS maxSessionDuration, session_pause_duration AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM session_duration JOIN category ON (session_duration.category_id = category.id) WHERE category.child_id = :userId ORDER BY type, day DESC, lastUsage DESC, startMinuteOfDay, endMinuteOfDay, categoryId") @Query("SELECT 2 AS type, start_time_of_day AS startMinuteOfDay, end_time_of_day AS endMinuteOfDay, used_time AS duration, day_of_epoch AS day, NULL AS lastUsage, NULL AS maxSessionDuration, NULL AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM used_time JOIN category ON (used_time.category_id = category.id) WHERE category.child_id = :userId UNION ALL SELECT 1 AS type, start_minute_of_day AS startMinuteOfDay, end_minute_of_day AS endMinuteOfDay, last_session_duration AS duration, NULL AS day, last_usage AS lastUsage, max_session_duration AS maxSessionDuration, session_pause_duration AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM session_duration JOIN category ON (session_duration.category_id = category.id) WHERE category.child_id = :userId ORDER BY type, day DESC, lastUsage DESC, startMinuteOfDay, endMinuteOfDay, categoryId")
abstract fun getUsedTimeListItemsByUserId(userId: String): Flow<List<UsedTimeListItem>> abstract fun getUsedTimeListItemsByUserId(userId: String): Flow<List<UsedTimeListItem>>
} }

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
@ -74,9 +74,14 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
clientMutex.withLock { clientMutex.withLock {
if (_billingClient == null) { if (_billingClient == null) {
_billingClient = BillingClient.newBuilder(getApplication()) _billingClient = BillingClient.newBuilder(getApplication())
.enablePendingPurchases() .enablePendingPurchases(
.setListener(purchaseUpdatedListener) PendingPurchasesParams
.build() .newBuilder()
.enableOneTimeProducts()
.build()
)
.setListener(purchaseUpdatedListener)
.build()
} }
val initBillingClient = _billingClient!! val initBillingClient = _billingClient!!

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,2 +1,2 @@
- Abstürze in bestimmten Fällen behoben - Funktionsumfang bei Verwendung der Geräte-Besitzer-Berechtigung erweitert
- enthaltene Komponenten aktualisiert - enthaltene Komponenten aktualisiert

View file

@ -1,2 +1,2 @@
- fix crashes in specific scenarios - add more features for users of the device owner permission
- update contained software - update contained software

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>

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
@ -42,7 +42,7 @@ object BillingClient {
enum class ProductType { INAPP } enum class ProductType { INAPP }
object Builder { object Builder {
fun enablePendingPurchases() = this fun enablePendingPurchases(params: PendingPurchasesParams) = this
fun setListener(listener: PurchasesUpdatedListener) = this fun setListener(listener: PurchasesUpdatedListener) = this
fun build() = BillingClient fun build() = BillingClient
} }
@ -144,3 +144,12 @@ object QueryPurchasesParams {
fun setProductType(type: BillingClient.ProductType) = this fun setProductType(type: BillingClient.ProductType) = this
fun build() = this fun build() = this
} }
object PendingPurchasesParams {
object Builder {
fun enableOneTimeProducts() = this
fun build() = PendingPurchasesParams
}
fun newBuilder() = Builder
}

View file

@ -15,10 +15,10 @@
*/ */
plugins { plugins {
id 'com.android.application' version '8.10.1' apply false id 'com.android.application' version '8.11.1' apply false
id 'com.android.library' version '8.10.1' apply false id 'com.android.library' version '8.11.1' apply false
id 'org.jetbrains.kotlin.android' version "2.0.21" apply false id 'org.jetbrains.kotlin.android' version "2.0.21" apply false
id 'com.google.devtools.ksp' version '1.9.21-1.0.16' apply false id 'com.google.devtools.ksp' version '1.9.21-1.0.16' apply false
id 'androidx.navigation.safeargs' version '2.6.0' apply false id 'androidx.navigation.safeargs' version '2.6.0' apply false
id 'com.squareup.wire' version '4.4.3' apply false id 'com.squareup.wire' version '5.3.5' apply false
} }

View file

@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionSha256Sum=89d4e70e4e84e2d2dfbb63e4daa53e21b25017cc70c37e4eea31ee51fb15098a distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78