diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/AppOps.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/AppOps.kt index c12009a..4c1a24a 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/android/AppOps.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/AppOps.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * TimeLimit Copyright 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 @@ -50,6 +50,12 @@ object AppOps { } } + private val setMode by lazy { try { + AppOpsManager::class.java.getMethod("setMode", String::class.java, Int::class.java, String::class.java, Int::class.java) + } catch (ex: ReflectiveOperationException) { + null + } } + fun getOpMode(op: String, appOpsManager: AppOpsManager, context: Context): Mode { try { val reflectionData = reflectionData ?: return Mode.Unknown @@ -88,6 +94,28 @@ object AppOps { } } + fun setMode(op: String, appOpsManager: AppOpsManager, context: Context, mode: Mode) { + val setMode = setMode + + if (setMode == null) throw SecurityException("blocked by the OS") + + val realMode = when (mode) { + Mode.Allowed -> AppOpsManager.MODE_ALLOWED + Mode.Default -> AppOpsManager.MODE_DEFAULT + Mode.Ignored -> AppOpsManager.MODE_IGNORED + Mode.Blocked -> AppOpsManager.MODE_ERRORED + else -> return + } + + try { + setMode.invoke(appOpsManager, op, Process.myUid(), context.packageName, realMode) + } catch (ex: InvocationTargetException) { + ex.cause?.let { throw it } + + throw ex + } + } + enum class Mode { Unknown, Allowed, 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 8e5c4e4..f9d5188 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 - 2023 Jonas Lochmann + * TimeLimit Copyright 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 @@ -22,7 +22,6 @@ 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 @@ -67,7 +66,9 @@ class BackgroundService: Service() { if (VERSION.SDK_INT >= VERSION_CODES.P) { if (activityManager.isBackgroundRestricted) { - return true + if (RunInBackgroundPermission.trySelfGrant(context)) { + if (activityManager.isBackgroundRestricted) return true + } else return true } } diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/RunInBackgroundPermission.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/RunInBackgroundPermission.kt new file mode 100644 index 0000000..741051f --- /dev/null +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/RunInBackgroundPermission.kt @@ -0,0 +1,44 @@ +/* + * TimeLimit Copyright 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.timelimit.android.integration.platform.android + +import android.app.AppOpsManager +import android.app.admin.DevicePolicyManager +import android.content.Context +import androidx.core.content.getSystemService +import io.timelimit.android.BuildConfig + +object RunInBackgroundPermission { + private const val OP = "android:run_any_in_background" + + fun trySelfGrant(context: Context): Boolean { + if (BuildConfig.storeCompilant) return false + + val devicePolicyManager = context.getSystemService() ?: return false + + if (!devicePolicyManager.isDeviceOwnerApp(context.packageName)) return false + + val appOpsService = context.getSystemService() ?: return false + + return try { + AppOps.setMode(OP, appOpsService, context, AppOps.Mode.Allowed) + + true + } catch (ex: SecurityException) { + false + } + } +} \ No newline at end of file