diff --git a/app/build.gradle b/app/build.gradle
index 3c839f6..4de830e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,7 +27,7 @@ android {
defaultConfig {
applicationId "io.timelimit.android"
minSdkVersion 21
- targetSdkVersion 33
+ targetSdkVersion 34
versionCode 205
versionName "6.14.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bc3219f..77bb3c4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -30,6 +30,7 @@
tools:ignore="ProtectedPermissions" />
+
@@ -136,6 +137,7 @@
diff --git a/app/src/main/java/io/timelimit/android/data/model/Device.kt b/app/src/main/java/io/timelimit/android/data/model/Device.kt
index 2fcc822..a11a0d5 100644
--- a/app/src/main/java/io/timelimit/android/data/model/Device.kt
+++ b/app/src/main/java/io/timelimit/android/data/model/Device.kt
@@ -361,7 +361,13 @@ data class Device(
(currentProtectionLevel != ProtectionLevel.DeviceOwner)
@Transient
- val isImportant = hasAnyManipulation || missingPermissionAtQOrLater || didReportUninstall
+ val missingDeviceAdminPermission =
+ platformType == DevicePlatform.ANDROID &&
+ platformLevel >= 2 &&
+ currentProtectionLevel == ProtectionLevel.None
+
+ @Transient
+ val isImportant = hasAnyManipulation || missingPermissionAtQOrLater || missingDeviceAdminPermission || didReportUninstall
}
enum class NetworkTime {
diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundService.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundService.kt
index 63b6d8d..8e5c4e4 100644
--- a/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundService.kt
+++ b/app/src/main/java/io/timelimit/android/integration/platform/android/BackgroundService.kt
@@ -1,5 +1,5 @@
/*
- * TimeLimit Copyright 2019 - 2022 Jonas Lochmann
+ * TimeLimit Copyright 2019 - 2023 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
@@ -18,12 +18,17 @@ package io.timelimit.android.integration.platform.android
import android.app.ActivityManager
import android.app.NotificationManager
import android.app.Service
+import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
+import android.os.Build.VERSION
+import android.os.Build.VERSION_CODES
import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
+import androidx.core.content.getSystemService
import io.timelimit.android.R
import io.timelimit.android.integration.platform.AppStatusMessage
import io.timelimit.android.logic.DefaultAppLogic
@@ -35,9 +40,10 @@ class BackgroundService: Service() {
fun setStatusMessage(status: AppStatusMessage?, context: Context) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val intent = Intent(context, BackgroundService::class.java)
+ val isRestricted = isBackgroundActivityRestricted(context)
if (status != null) {
- if (isBackgroundActivityRestricted(context)) {
+ if (isRestricted) {
val notification = buildNotification(status, context)
notificationManager.notify(NotificationIds.APP_STATUS, notification)
@@ -50,7 +56,7 @@ class BackgroundService: Service() {
} else {
context.stopService(intent)
- if (isBackgroundActivityRestricted(context)) {
+ if (isRestricted) {
notificationManager.cancel(NotificationIds.APP_STATUS)
}
}
@@ -59,11 +65,19 @@ class BackgroundService: Service() {
fun isBackgroundActivityRestricted(context: Context): Boolean {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- return activityManager.isBackgroundRestricted
- } else {
- return false
+ if (VERSION.SDK_INT >= VERSION_CODES.P) {
+ if (activityManager.isBackgroundRestricted) {
+ return true
+ }
}
+
+ if (VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ if (!context.getSystemService()!!.isAdminActive(ComponentName(context, AdminReceiver::class.java))) {
+ return true
+ }
+ }
+
+ return false
}
private fun buildNotification(appStatusMessage: AppStatusMessage, context: Context) = NotificationCompat.Builder(context, NotificationChannels.APP_STATUS)
diff --git a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt
index 5c3238c..e209331 100644
--- a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt
+++ b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt
@@ -37,6 +37,7 @@ import io.timelimit.android.extensions.MinuteOfDay
import io.timelimit.android.extensions.nextBlockedMinuteOfWeek
import io.timelimit.android.integration.platform.*
import io.timelimit.android.integration.platform.android.AccessibilityService
+import io.timelimit.android.integration.platform.android.BackgroundService
import io.timelimit.android.livedata.*
import io.timelimit.android.logic.blockingreason.AppBaseHandling
import io.timelimit.android.logic.blockingreason.CategoryHandlingCache
@@ -178,6 +179,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
private suspend fun backgroundServiceLoop() {
val realTime = RealTime.newInstance()
+ var skipBackgroundRestrictionChecks = 0
while (true) {
val backgroundServiceInterval = when (slowMainLoop) {
@@ -244,6 +246,24 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
// loop logic
try {
+ if (skipBackgroundRestrictionChecks <= 0) {
+ if (BackgroundService.isBackgroundActivityRestricted(appLogic.context)) {
+ commitUsedTimeUpdaters()
+ undisturbedCategoryUsageCounter.reset()
+
+ appLogic.platformIntegration.setShowBlockingOverlay(false)
+ appLogic.platformIntegration.setAppStatusMessage(
+ AppStatusMessage(
+ appLogic.context.getString(R.string.background_logic_error),
+ appLogic.context.getString(R.string.background_logic_error_permission)
+ )
+ )
+
+ appLogic.timeApi.sleep(BACKGROUND_SERVICE_INTERVAL_LONG)
+ continue
+ } else skipBackgroundRestrictionChecks = 128
+ } else skipBackgroundRestrictionChecks--
+
// get the current time
appLogic.realTimeLogic.getRealTime(realTime)
diff --git a/app/src/main/java/io/timelimit/android/u2f/nfc/NFCU2FManager.kt b/app/src/main/java/io/timelimit/android/u2f/nfc/NFCU2FManager.kt
index 7e454fd..3081e02 100644
--- a/app/src/main/java/io/timelimit/android/u2f/nfc/NFCU2FManager.kt
+++ b/app/src/main/java/io/timelimit/android/u2f/nfc/NFCU2FManager.kt
@@ -69,7 +69,7 @@ class NFCU2FManager (val parent: U2fManager, context: Context) {
private val nfcReceiverIntent = PendingIntent.getBroadcast(
context,
PendingIntentIds.U2F_NFC_DISCOVERY,
- Intent(nfcReceiverAction),
+ Intent(nfcReceiverAction).setPackage(context.packageName),
PendingIntentIds.PENDING_INTENT_FLAGS_ALLOW_MUTATION
)
diff --git a/app/src/main/java/io/timelimit/android/u2f/usb/UsbU2FManager.kt b/app/src/main/java/io/timelimit/android/u2f/usb/UsbU2FManager.kt
index 7592185..ae0b5b0 100644
--- a/app/src/main/java/io/timelimit/android/u2f/usb/UsbU2FManager.kt
+++ b/app/src/main/java/io/timelimit/android/u2f/usb/UsbU2FManager.kt
@@ -77,7 +77,7 @@ class UsbU2FManager (val parent: U2fManager, context: Context) {
private val permissionResponseIntent = PendingIntent.getBroadcast(
context,
PendingIntentIds.U2F_USB_RESPONSE,
- Intent(permissionResponseAction),
+ Intent(permissionResponseAction).setPackage(context.packageName),
PendingIntentIds.PENDING_INTENT_FLAGS_ALLOW_MUTATION
)
diff --git a/app/src/main/java/io/timelimit/android/ui/manage/device/manage/permission/PermissionGoals.kt b/app/src/main/java/io/timelimit/android/ui/manage/device/manage/permission/PermissionGoals.kt
index 3595951..155b7fc 100644
--- a/app/src/main/java/io/timelimit/android/ui/manage/device/manage/permission/PermissionGoals.kt
+++ b/app/src/main/java/io/timelimit/android/ui/manage/device/manage/permission/PermissionGoals.kt
@@ -38,7 +38,8 @@ fun PermissionGoals(status: PermissionScreenContent.Status) {
PermissionGoal(
stringResource(R.string.manage_device_permission_goal_limit_title),
status.usageStats != RuntimePermissionStatus.NotGranted &&
- (!status.isQOrLater || status.overlay == RuntimePermissionStatus.Granted || status.accessibility),
+ (!status.isQOrLater || status.overlay == RuntimePermissionStatus.Granted || status.accessibility) &&
+ (status.androidPlatformLevel < 2 || status.protectionLevel != ProtectionLevel.None)
) {
if (status.usageStats != RuntimePermissionStatus.NotRequired) FlowRow {
Text(stringResource(R.string.manage_device_permission_goal_needs))
@@ -52,8 +53,13 @@ fun PermissionGoals(status: PermissionScreenContent.Status) {
PermissionIcon(SystemPermission.AccessibilityService, status.accessibility)
}
- if (status.usageStats == RuntimePermissionStatus.NotRequired && !status.isQOrLater)
- Text(stringResource(R.string.manage_device_permission_goal_reached_by_old_android))
+ FlowRow {
+ Text(stringResource(
+ if (status.androidPlatformLevel >= 2) R.string.manage_device_permission_goal_needs
+ else R.string.manage_device_permission_goal_eventually_needs_future
+ ))
+ PermissionIcon(SystemPermission.DeviceAdmin, status.protectionLevel != ProtectionLevel.None)
+ }
}
PermissionGoal(
diff --git a/app/src/main/java/io/timelimit/android/ui/manage/device/manage/permission/PermissionScreenContent.kt b/app/src/main/java/io/timelimit/android/ui/manage/device/manage/permission/PermissionScreenContent.kt
index ee518b0..16c63f5 100644
--- a/app/src/main/java/io/timelimit/android/ui/manage/device/manage/permission/PermissionScreenContent.kt
+++ b/app/src/main/java/io/timelimit/android/ui/manage/device/manage/permission/PermissionScreenContent.kt
@@ -32,7 +32,8 @@ data class PermissionScreenContent(
val usageStats: RuntimePermissionStatus,
val overlay: RuntimePermissionStatus,
val accessibility: Boolean,
- val isQOrLater: Boolean
+ val isQOrLater: Boolean,
+ val androidPlatformLevel: Int
)
data class Dialog(
diff --git a/app/src/main/java/io/timelimit/android/ui/model/main/OverviewHandling.kt b/app/src/main/java/io/timelimit/android/ui/model/main/OverviewHandling.kt
index 1028329..6818135 100644
--- a/app/src/main/java/io/timelimit/android/ui/model/main/OverviewHandling.kt
+++ b/app/src/main/java/io/timelimit/android/ui/model/main/OverviewHandling.kt
@@ -412,7 +412,9 @@ object OverviewHandling {
data class UserList(val list: List, val canAdd: Boolean, val canShowMore: Boolean)
data class DeviceItem(val device: Device, val userName: String?, val userType: UserType?, val isCurrentDevice: Boolean, val isConnected: Boolean) {
val isMissingRequiredPermission = userType == UserType.Child && (
- device.currentUsageStatsPermission == RuntimePermissionStatus.NotGranted || device.missingPermissionAtQOrLater)
+ device.currentUsageStatsPermission == RuntimePermissionStatus.NotGranted ||
+ device.missingPermissionAtQOrLater ||
+ device.missingDeviceAdminPermission)
}
data class DeviceList(val list: List, val canAdd: Boolean, val canShowMore: OverviewState.DeviceList?)
}
\ No newline at end of file
diff --git a/app/src/main/java/io/timelimit/android/ui/model/managedevice/ManageDevicePermissions.kt b/app/src/main/java/io/timelimit/android/ui/model/managedevice/ManageDevicePermissions.kt
index 52a96ea..9718948 100644
--- a/app/src/main/java/io/timelimit/android/ui/model/managedevice/ManageDevicePermissions.kt
+++ b/app/src/main/java/io/timelimit/android/ui/model/managedevice/ManageDevicePermissions.kt
@@ -16,6 +16,7 @@
package io.timelimit.android.ui.model.managedevice
import io.timelimit.android.data.model.Device
+import io.timelimit.android.data.model.DevicePlatform
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.ui.manage.device.manage.permission.PermissionScreenContent
import io.timelimit.android.ui.model.ActivityCommand
@@ -82,7 +83,8 @@ object ManageDevicePermissions {
usageStats = device.currentUsageStatsPermission,
overlay = device.currentOverlayPermission,
accessibility = device.accessibilityServiceEnabled,
- isQOrLater = device.qOrLater
+ isQOrLater = device.qOrLater,
+ androidPlatformLevel = if (device.platformType == DevicePlatform.ANDROID) device.platformLevel else 0
)
}.distinctUntilChanged()
}
\ No newline at end of file
diff --git a/app/src/main/java/io/timelimit/android/ui/model/setup/SetupLocalModePermissions.kt b/app/src/main/java/io/timelimit/android/ui/model/setup/SetupLocalModePermissions.kt
index 0a23bd0..400de7b 100644
--- a/app/src/main/java/io/timelimit/android/ui/model/setup/SetupLocalModePermissions.kt
+++ b/app/src/main/java/io/timelimit/android/ui/model/setup/SetupLocalModePermissions.kt
@@ -108,7 +108,8 @@ object SetupLocalModePermissions {
usageStats = platformIntegration.getForegroundAppPermissionStatus(),
overlay = platformIntegration.getDrawOverOtherAppsPermissionStatus(true),
accessibility = platformIntegration.isAccessibilityServiceEnabled(),
- isQOrLater = AndroidVersion.qOrLater
+ isQOrLater = AndroidVersion.qOrLater,
+ androidPlatformLevel = AndroidVersion.platformLevel
)
)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index bbd8214..22ea0f9 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -1033,6 +1033,7 @@
in der aktuellen Umgebung nicht verfügbar
öffnen Sie diese Ansicht am entsprechenden Gerät
benötigt evtl.
+ benötigt zukünftig
benötigt
oder
benötigt wegen alter Android-Version Nichts weiter
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 803cb62..35ba748 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1082,6 +1082,7 @@
unavailable in the current environment
open this screen at the device
eventually needs
+ needs in the future
needs
or
needs nothing due to old Android version