Compare commits

...

23 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
Jonas Lochmann
6ba9614557
Release 7.2.4 2025-06-23 02:00:00 +02:00
Jonas Lochmann
53732e47dd
catch SecurityException caused by adjusting sensor related permissions 2025-06-23 18:29:22 +02:00
Jonas Lochmann
acdd83c39a
Update build tools 2025-06-23 18:23:40 +02:00
Jonas Lochmann
f4c35dec55
Update dependencies 2025-06-16 02:00:00 +02:00
Jonas Lochmann
5f69014850
Release 7.2.3 2025-05-05 02:00:00 +02:00
Jonas Lochmann
d02631d5c1
Update dependencies 2025-05-05 02:00:00 +02:00
Jonas Lochmann
801bf760dc
update buildtools 2025-05-05 02:00:00 +02:00
Jonas Lochmann
daff66b26b
remove generated file 2025-05-05 02:00:00 +02:00
Jonas Lochmann
a36ffa861a
remove obsolete tests 2025-04-28 02:00:00 +02:00
Jonas Lochmann
de0b6d1c8f
Fix parsing u2f counter bytes >= 0x80 2025-04-28 02:00:00 +02:00
Jonas Lochmann
2e68798e2c
Update buildtools and dependencies 2025-04-28 02:00:00 +02:00
Jonas Lochmann
73a82f3cf8
Update buildtools and dependencies 2025-01-13 01:00:00 +01:00
Jonas Lochmann
bd88da38d2
Update copyright year 2025-01-13 01:00:00 +01:00
Jonas Lochmann
e54ff92cbb
Release 7.2.2 2024-10-28 01:00:00 +01:00
Jonas Lochmann
8e02eb3fb3
Move lockscreen tabs into the toolbar 2024-10-28 01:00:00 +01:00
Jonas Lochmann
11a47c5f30
Disable ActionBar in the theme 2024-11-04 20:01:46 +01:00
Jonas Lochmann
15ae018589
Add insets to the add app dialog 2024-11-04 20:01:43 +01:00
34 changed files with 380 additions and 236 deletions

1
.gitignore vendored
View file

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

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
* it under the terms of the GNU General Public License as published by
@ -21,17 +21,18 @@ plugins {
id "androidx.navigation.safeargs.kotlin"
id 'kotlin-kapt'
id 'com.squareup.wire'
id("org.jetbrains.kotlin.plugin.compose") version "2.1.10"
}
android {
namespace 'io.timelimit.android'
compileSdk 35
compileSdk 36
defaultConfig {
applicationId "io.timelimit.android"
minSdkVersion 26
targetSdkVersion 35
versionCode 220
versionName "7.2.1"
targetSdkVersion 36
versionCode 224
versionName "7.3.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
kapt {
arguments {
@ -166,24 +167,24 @@ wire {
dependencies {
def nav_version = "2.5.3"
def room_version = "2.6.1"
def work_version = '2.9.1'
def paging_version = "3.3.2"
def room_version = "2.7.2"
def work_version = '2.10.2'
def paging_version = "3.3.6"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.21"
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.core:core:1.15.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.10"
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'androidx.core:core:1.16.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.gridlayout:gridlayout:1.1.0'
implementation "com.google.android.material:material:1.12.0"
implementation 'androidx.compose.material:material:1.7.5'
implementation 'androidx.activity:activity-compose:1.9.3'
implementation 'androidx.compose.material:material:1.8.3'
implementation 'androidx.activity:activity-compose:1.10.1'
implementation "com.google.accompanist:accompanist-flowlayout:0.30.0"
implementation 'androidx.compose.material:material-icons-extended:1.7.5'
debugImplementation "androidx.compose.ui:ui-tooling:1.7.5"
implementation 'androidx.fragment:fragment-ktx:1.8.5'
implementation 'androidx.fragment:fragment-compose:1.8.5'
implementation 'androidx.compose.material:material-icons-extended:1.7.8'
debugImplementation "androidx.compose.ui:ui-tooling:1.8.3"
implementation 'androidx.fragment:fragment-ktx:1.8.8'
implementation 'androidx.fragment:fragment-compose:1.8.8'
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
@ -200,8 +201,8 @@ dependencies {
implementation "androidx.work:work-runtime-ktx:$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-android:1.7.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.6.2'
@ -217,7 +218,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp-tls: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') {
exclude group: 'org.json', module: 'json'
@ -229,5 +230,5 @@ dependencies {
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
* 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 ...
// 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")
abstract fun getUsedTimeListItemsByCategoryId(categoryId: String): Flow<List<UsedTimeListItem>>
// breaking it into multiple lines causes issues during compilation ...
// 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")
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
* it under the terms of the GNU General Public License as published by
@ -295,4 +295,7 @@ object ExperimentalFlags {
object ConsentFlags {
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
* 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 isConsentFlagSet(flags: Long) = (consentFlags and flags) == flags
}

View file

@ -134,9 +134,19 @@ class AndroidDeviceOwnerApi(
if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) return false
if (!devicePolicyManager.isDeviceOwnerApp(componentName.packageName)) return false
try {
return devicePolicyManager.setPermissionGrantState(
componentName, componentName.packageName, Manifest.permission.ACCESS_FINE_LOCATION,
DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
)
} catch (ex: SecurityException) {
// set to default so that granting this manually is possible
devicePolicyManager.setPermissionGrantState(
componentName, componentName.packageName, Manifest.permission.ACCESS_FINE_LOCATION,
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
)
return false
}
}
}

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
* 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 {
private const val FEATURE_ADB = "adb"
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 apply(feature: String, restriction: String) {
@ -40,6 +44,18 @@ object AndroidFeatures {
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
}
@ -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
}
}

View file

@ -507,7 +507,6 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
if (enableLockdown) {
// disable problematic features
policyManager.addUserRestriction(deviceAdmin, UserManager.DISALLOW_ADD_USER)
policyManager.addUserRestriction(deviceAdmin, UserManager.DISALLOW_FACTORY_RESET)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -519,17 +518,25 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
context.packageName,
Manifest.permission.ACCESS_FINE_LOCATION,
).let {
try {
if (it == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
policyManager.setPermissionGrantState(
deviceAdmin,
context.packageName,
Manifest.permission.ACCESS_FINE_LOCATION,
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
)
DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
else
DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
)
}
} catch (ex: SecurityException) {
// ignore
}
}
policyManager.setPermissionGrantState(
@ -549,7 +556,6 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
}
} else /* disable lockdown */ {
// enable problematic features
policyManager.clearUserRestriction(deviceAdmin, UserManager.DISALLOW_ADD_USER)
policyManager.clearUserRestriction(deviceAdmin, UserManager.DISALLOW_FACTORY_RESET)
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
* 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.config().setCustomServerUrlSync(customServerUrl)
appLogic.database.config().setConsentFlagSync(
ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT,
true
)
}
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
* 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.Table
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.UserType
import io.timelimit.android.data.model.derived.UserRelatedData
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.logic.blockingreason.CategoryHandlingCache
import java.lang.ref.WeakReference
@ -107,12 +109,23 @@ class SuspendAppsLogic(private val appLogic: AppLogic): Observer {
val hasManagedFeatures = featureCategoryApps.isNotEmpty()
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) {
lastDefaultCategory = null
lastAllowedCategoryList = emptySet()
lastCategoryApps = emptyList()
applySuspendedApps(emptyList())
applyBlockedFeatures(emptySet())
applyBlockedFeatures(
featureToAllowDefaults.filter { !it.value }.map { it.key }.toSet()
)
return
}
@ -191,9 +204,15 @@ class SuspendAppsLogic(private val appLogic: AppLogic): Observer {
val deviceSpecificFeatureIdentifiers = deviceSpecificFeatures.map { it.appSpecifierString }.toSet()
val globalFeatures = featureCategoryApps.filter { !deviceSpecificFeatureIdentifiers.contains(it.appSpecifierString) }
val effectiveFeatures = deviceSpecificFeatures + globalFeatures
val featuresToBlock = effectiveFeatures.filter { !categoryIdsToAllow.contains(it.categoryId) }
.map { it.appSpecifierString.substring(DummyApps.FEATURE_APP_PREFIX.length) }
.toSet()
val featuresToAllow = featureToAllowDefaults + effectiveFeatures.associate {
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)
applyBlockedFeatures(featuresToBlock)

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
* it under the terms of the GNU General Public License as published by
@ -64,10 +64,10 @@ object U2FResponse {
val flags = rawResponse.payload[0]
val counter = rawResponse.payload[4].toUInt() or
rawResponse.payload[3].toUInt().shl(8) or
rawResponse.payload[2].toUInt().shl(16) or
rawResponse.payload[1].toUInt().shl(24)
val counter = rawResponse.payload[4].toUByte().toUInt() or
rawResponse.payload[3].toUByte().toUInt().shl(8) or
rawResponse.payload[2].toUByte().toUInt().shl(16) or
rawResponse.payload[1].toUByte().toUInt().shl(24)
val signature = rawResponse.payload.sliceArray(5 until rawResponse.payload.size)

View file

@ -144,8 +144,6 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
)
)
supportActionBar!!.hide()
U2fManager.setupActivity(this)
NotificationChannels.createNotificationChannels(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager, this)

View file

@ -51,6 +51,7 @@ fun ScreenScaffold(
backStack: List<BackStackItem>,
snackbarHostState: SnackbarHostState?,
content: @Composable (PaddingValues) -> Unit,
extraBars: (@Composable () -> Unit)? = null,
executeCommand: (UpdateStateCommand) -> Unit,
showAuthenticationDialog: (() -> Unit)?
) {
@ -58,6 +59,7 @@ fun ScreenScaffold(
Scaffold(
topBar = {
Column {
TopAppBar(
title = {
Column {
@ -121,6 +123,9 @@ fun ScreenScaffold(
modifier = Modifier,
windowInsets = WindowInsets.statusBarsIgnoringVisibility
)
extraBars?.invoke()
}
},
bottomBar = {
val backStackColors = ButtonDefaults.textButtonColors(

View file

@ -120,8 +120,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
)
)
supportActionBar!!.hide()
U2fManager.setupActivity(this)
val subtitleLive = syncModel.statusText.asFlow()
@ -149,8 +147,7 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
subtitle = subtitle,
backStack = emptyList(),
snackbarHostState = null,
content = { padding ->
Column (Modifier.fillMaxSize().padding(padding)) {
extraBars = {
TabRow(
pager.currentPage,
indicator = { tabPositions ->
@ -192,10 +189,11 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
)
}
}
},
content = { padding ->
HorizontalPager(
pager,
Modifier.weight(1.0F, fill = true),
Modifier.fillMaxSize().padding(padding),
pageContent = { index ->
when (index) {
0 -> AndroidFragment<LockReasonFragment>(Modifier.fillMaxSize())
@ -204,7 +202,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
}
}
)
}
},
executeCommand = {},
showAuthenticationDialog =
@ -214,8 +211,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
}
}
syncModel.statusText.observe(this) { supportActionBar?.subtitle = it }
currentInstances.add(this)
model.init(blockedPackageName, blockedActivityName)

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,14 +16,21 @@
package io.timelimit.android.ui.manage.category.apps.add
import android.app.Dialog
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
@ -220,9 +227,29 @@ class AddCategoryAppsFragment : DialogFragment() {
}
}
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updateLayoutParams<MarginLayoutParams> {
topMargin = insets.top
bottomMargin = insets.bottom
leftMargin = insets.left
rightMargin = insets.right
}
WindowInsetsCompat.CONSUMED
}
return AlertDialog.Builder(requireContext(), R.style.AppTheme)
.setView(binding.root)
.create()
.also { dialog ->
if (VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) dialog.setOnShowListener {
WindowInsetsControllerCompat(dialog.window!!, binding.root).run {
isAppearanceLightStatusBars = true
}
}
}
}
fun show(manager: FragmentManager) {

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,9 +16,16 @@
package io.timelimit.android.ui.manage.category.apps.addactivity
import android.app.Dialog
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup.MarginLayoutParams
import androidx.appcompat.app.AlertDialog
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
@ -114,9 +121,30 @@ class AddAppActivitiesDialogFragment: DialogFragment() {
dismissAllowingStateLoss()
}
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updateLayoutParams<MarginLayoutParams> {
topMargin = insets.top
bottomMargin = insets.bottom
leftMargin = insets.left
rightMargin = insets.right
}
WindowInsetsCompat.CONSUMED
}
return AlertDialog.Builder(requireContext(), R.style.AppTheme)
.setView(binding.root)
.create()
.also { dialog ->
if (VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) dialog.setOnShowListener {
WindowInsetsControllerCompat(dialog.window!!, binding.root).run {
isAppearanceLightStatusBars = true
}
}
}
}
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)

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
* it under the terms of the GNU General Public License as published by
@ -49,7 +49,7 @@ fun ManageDeviceUserScreen(
Card(
onClick = { actions.select(item) },
modifier = Modifier
.animateItemPlacement()
.animateItem()
.fillMaxWidth(),
backgroundColor = when (item.selected) {
true -> MaterialTheme.colors.secondary

View file

@ -89,8 +89,6 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D
)
)
supportActionBar!!.hide()
setContent {
Theme {
ScreenScaffold(

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
* 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.data.backup.DatabaseBackup
import io.timelimit.android.data.devicename.DeviceName
import io.timelimit.android.data.model.ConsentFlags
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.sync.ApplyServerDataStatus
import io.timelimit.android.sync.network.NewDeviceInfo
@ -333,6 +334,11 @@ object SetupParentHandling {
database.config().setDeviceAuthTokenSync(result.deviceAuthToken)
database.config().setEnableBackgroundSync(state.backgroundSync)
database.config().setConsentFlagSync(
ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT,
true
)
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
* it under the terms of the GNU General Public License as published by
@ -35,7 +35,7 @@ import io.timelimit.android.ui.model.main.OverviewHandling
@OptIn(ExperimentalFoundationApi::class)
fun LazyListScope.deviceItems(screen: OverviewHandling.OverviewScreen) {
item (key = Pair("devices", "header")) {
ListCommon.SectionHeader(stringResource(R.string.overview_header_devices), Modifier.animateItemPlacement())
ListCommon.SectionHeader(stringResource(R.string.overview_header_devices), Modifier.animateItem())
}
items(screen.devices.list, key = { Pair("device", it.device.id) }) {
@ -48,7 +48,7 @@ fun LazyListScope.deviceItems(screen: OverviewHandling.OverviewScreen) {
icon = Icons.Default.Add,
label = stringResource(R.string.add_device),
action = screen.actions.addDevice,
modifier = Modifier.animateItemPlacement()
modifier = Modifier.animateItem()
)
}
}
@ -56,7 +56,7 @@ fun LazyListScope.deviceItems(screen: OverviewHandling.OverviewScreen) {
if (screen.devices.canShowMore != null) {
item (key = Pair("devices", "more")) {
ListCommon.ShowMoreItem(
modifier = Modifier.animateItemPlacement(),
modifier = Modifier.animateItem(),
action = { screen.actions.showMoreDevices(screen.devices.canShowMore) }
)
}
@ -71,7 +71,7 @@ fun LazyItemScope.DeviceItem(
) {
ListCardCommon.Card(
Modifier
.animateItemPlacement()
.animateItem()
.padding(horizontal = 8.dp)
.clickable(onClick = { openAction(item) })
) {

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
* it under the terms of the GNU General Public License as published by
@ -40,7 +40,7 @@ fun LazyListScope.introItems(
item (key = Pair("intro", "finish setup")) {
ListCardCommon.Card(
modifier = Modifier
.animateItemPlacement()
.animateItem()
.padding(horizontal = 8.dp)
) {
Text(
@ -62,7 +62,7 @@ fun LazyListScope.introItems(
item (key = Pair("intro", "outdated server")) {
ListCardCommon.Card(
modifier = Modifier
.animateItemPlacement()
.animateItem()
.padding(horizontal = 8.dp)
) {
Text(
@ -79,7 +79,7 @@ fun LazyListScope.introItems(
item (key = Pair("intro", "server message")) {
ListCardCommon.Card(
modifier = Modifier
.animateItemPlacement()
.animateItem()
.padding(horizontal = 8.dp)
) {
Text(
@ -108,7 +108,7 @@ fun LazyListScope.introItems(
SwipeToDismiss(
state = state,
background = {},
modifier = Modifier.animateItemPlacement()
modifier = Modifier.animateItem()
) {
ListCardCommon.Card(
modifier = Modifier.padding(horizontal = 8.dp)
@ -133,7 +133,7 @@ fun LazyListScope.introItems(
item (key = Pair("intro", "task review")) {
ListCardCommon.Card(
modifier = Modifier
.animateItemPlacement()
.animateItem()
.padding(horizontal = 8.dp)
) {
Text(

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
* it under the terms of the GNU General Public License as published by
@ -35,7 +35,7 @@ import io.timelimit.android.ui.model.main.OverviewHandling
@OptIn(ExperimentalFoundationApi::class)
fun LazyListScope.userItems(screen: OverviewHandling.OverviewScreen) {
item (key = Pair("users", "header")) {
ListCommon.SectionHeader(stringResource(R.string.overview_header_users), Modifier.animateItemPlacement())
ListCommon.SectionHeader(stringResource(R.string.overview_header_users), Modifier.animateItem())
}
items(screen.users.list, key = { Pair("user", it.id) }) { UserItem(it, screen.actions) }
@ -45,13 +45,13 @@ fun LazyListScope.userItems(screen: OverviewHandling.OverviewScreen) {
icon = Icons.Default.Add,
label = stringResource(R.string.add_user_title),
action = screen.actions.addUser,
modifier = Modifier.animateItemPlacement()
modifier = Modifier.animateItem()
)
}
if (screen.users.canShowMore) item (key = Pair("users", "more")) {
ListCommon.ShowMoreItem (
modifier = Modifier.animateItemPlacement(),
modifier = Modifier.animateItem(),
action = screen.actions.showMoreUsers
)
}
@ -65,7 +65,7 @@ fun LazyItemScope.UserItem(
) {
ListCardCommon.Card(
Modifier
.animateItemPlacement()
.animateItem()
.padding(horizontal = 8.dp)
.clickable(onClick = { actions.openUser(user) })
) {

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
* it under the terms of the GNU General Public License as published by
@ -74,7 +74,12 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
clientMutex.withLock {
if (_billingClient == null) {
_billingClient = BillingClient.newBuilder(getApplication())
.enablePendingPurchases()
.enablePendingPurchases(
PendingPurchasesParams
.newBuilder()
.enableOneTimeProducts()
.build()
)
.setListener(purchaseUpdatedListener)
.build()
}

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
* 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.data.backup.DatabaseBackup
import io.timelimit.android.data.devicename.DeviceName
import io.timelimit.android.data.model.ConsentFlags
import io.timelimit.android.livedata.castDown
import io.timelimit.android.logic.AppLogic
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().setDeviceAuthTokenSync(registerResponse.deviceAuthToken)
logic.database.config().setConsentFlagSync(
ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT,
true
)
ApplyServerDataStatus.applyServerDataStatusSync(clientStatusResponse, logic.database, logic.platformIntegration)
}
}

View file

@ -47,8 +47,6 @@ class UpdateActivity: AppCompatActivity() {
)
)
supportActionBar!!.hide()
setContent {
Theme {
ScreenScaffold(

View file

@ -1,3 +1,2 @@
- Anpassungen für Android 15
- falsch angezeigte Nutzungsdauer bei Regeln die je Tag gelten an Tagen, an denen diese nicht gelten, behoben
- Funktionsumfang bei Verwendung der Geräte-Besitzer-Berechtigung erweitert
- enthaltene Komponenten aktualisiert

View file

@ -1,3 +1,2 @@
- adjustments for Android 15
- fix incorrect used time at rules that apply per day at days where they do not apply
- add more features for users of the device owner permission
- update contained software

View file

@ -1,6 +1,6 @@
<?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
it under the terms of the GNU General Public License as published by
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_feature_adb">Entwickleroptionen</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="notify_permission_title">Benachrichtigungen</string>

View file

@ -1,6 +1,6 @@
<?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
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License.
@ -1649,7 +1649,7 @@
<a href="https://legal.timelimit.io/en/privacy/">https://legal.timelimit.io/en/privacy/</a>
</string>
<string name="terms_gpl" translatable="false">
TimeLimit Copyright &#169; 2019 - 2024 Jonas Lochmann
TimeLimit Copyright &#169; 2019 - 2025 Jonas Lochmann
\nThis 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.
@ -1804,6 +1804,10 @@
<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_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="notify_permission_title">Notifications</string>

View file

@ -1,5 +1,5 @@
<!--
TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
TimeLimit Copyright <C> 2019 - 2024 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.
@ -15,7 +15,7 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorSecondary">@color/colorAccent</item>

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
* it under the terms of the GNU General Public License as published by
@ -42,7 +42,7 @@ object BillingClient {
enum class ProductType { INAPP }
object Builder {
fun enablePendingPurchases() = this
fun enablePendingPurchases(params: PendingPurchasesParams) = this
fun setListener(listener: PurchasesUpdatedListener) = this
fun build() = BillingClient
}
@ -144,3 +144,12 @@ object QueryPurchasesParams {
fun setProductType(type: BillingClient.ProductType) = this
fun build() = this
}
object PendingPurchasesParams {
object Builder {
fun enableOneTimeProducts() = this
fun build() = PendingPurchasesParams
}
fun newBuilder() = Builder
}

View file

@ -1,23 +0,0 @@
package io.timelimit.android.ui.manage.category.blocked_times
import org.junit.Assert.assertEquals
import org.junit.Test
class MinutesOfWeekItemsTest {
@Test
fun canGetAllItems() {
for (i in 0 until MinuteOfWeekItems.itemsPerWeek) {
MinuteOfWeekItems.getItemAtPosition(i)
}
}
@Test
fun reverseLookupReturnsSameItem() {
for (i in 0 until MinuteOfWeekItems.itemsPerWeek) {
val item = MinuteOfWeekItems.getItemAtPosition(i)
val index = MinuteOfWeekItems.getPositionOfItem(item)
assertEquals(item.toString(), i, index)
}
}
}

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
* it under the terms of the GNU General Public License as published by
@ -15,10 +15,10 @@
*/
plugins {
id 'com.android.application' version '8.7.2' apply false
id 'com.android.library' version '8.7.2' apply false
id 'org.jetbrains.kotlin.android' version "1.9.21" apply false
id 'com.android.application' version '8.11.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 'com.google.devtools.ksp' version '1.9.21-1.0.16' 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
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
distributionSha256Sum=258e722ec21e955201e31447b0aed14201765a3bfbae296a46cf60b70e66db70
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78