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 new file mode 100644 index 0000000..c12009a --- /dev/null +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/AppOps.kt @@ -0,0 +1,98 @@ +/* + * TimeLimit Copyright 2019 - 2022 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.annotation.SuppressLint +import android.app.AppOpsManager +import android.content.Context +import android.os.Process +import java.lang.ClassCastException +import java.lang.IllegalArgumentException +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Method + +@SuppressLint("PrivateApi") +object AppOps { + internal class ReflectionData ( + val getOpsForPackage: Method, + val getOps: Method, + val getMode: Method + ) + + private val reflectionData by lazy { + try { + val getOpsForPackage = AppOpsManager::class.java.getMethod("getOpsForPackage", Int::class.java, String::class.java, Array::class.java) + val packageOpsClass = Class.forName("android.app.AppOpsManager\$PackageOps") + val getOps = packageOpsClass.getMethod("getOps") + val opEntryClass = Class.forName("android.app.AppOpsManager\$OpEntry") + val getMode = opEntryClass.getMethod("getMode") + + ReflectionData( + getOpsForPackage = getOpsForPackage, + getOps = getOps, + getMode = getMode + ) + } catch (ex: ReflectiveOperationException) { + null + } + } + + fun getOpMode(op: String, appOpsManager: AppOpsManager, context: Context): Mode { + try { + val reflectionData = reflectionData ?: return Mode.Unknown + + val uid: Int = Process.myUid() + val pkg: String = context.packageName + val ops: Array = arrayOf(op) + + val packageOpsList = + reflectionData.getOpsForPackage.invoke(appOpsManager, uid, pkg, ops) as List<*> + + val packageOpsItem = packageOpsList.singleOrNull() ?: return Mode.Unknown + + val opEntryList = reflectionData.getOps.invoke(packageOpsItem) as List<*> + + val opEntryItem = opEntryList.singleOrNull() ?: return Mode.Unknown + + val mode = reflectionData.getMode.invoke(opEntryItem) as Int + + return when (mode) { + AppOpsManager.MODE_ALLOWED -> Mode.Allowed + AppOpsManager.MODE_DEFAULT -> Mode.Default + AppOpsManager.MODE_IGNORED -> Mode.Ignored + AppOpsManager.MODE_ERRORED -> Mode.Blocked + else -> Mode.Unknown + } + } catch (ex: ReflectiveOperationException) { + return Mode.Unknown + } catch (ex: ClassCastException) { + return Mode.Unknown + } catch (ex: IllegalArgumentException) { + return Mode.Unknown + } catch (ex: InvocationTargetException) { + if (ex.cause is SecurityException) return Mode.Unknown + else throw ex + } + } + + enum class Mode { + Unknown, + Allowed, + Blocked, + Default, + Ignored + } +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/OverlayUtil.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/OverlayUtil.kt index ecb9de5..0cf4d89 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/android/OverlayUtil.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/OverlayUtil.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2021 Jonas Lochmann + * TimeLimit Copyright 2019 - 2022 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 @@ -100,8 +100,14 @@ class OverlayUtil(private var application: Application) { private fun checkAppOp(): Boolean { if (systemOverlayOp == null) return false - val status = appsOpsManager.checkOpNoThrow(systemOverlayOp, Process.myUid(), application.packageName) + val mode1 = AppOps.getOpMode(systemOverlayOp, appsOpsManager, application) - return status == AppOpsManager.MODE_ALLOWED || status == AppOpsManager.MODE_IGNORED + if (mode1 != AppOps.Mode.Unknown) { + return mode1 == AppOps.Mode.Allowed + } + + val mode2 = appsOpsManager.checkOpNoThrow(systemOverlayOp, Process.myUid(), application.packageName) + + return mode2 == AppOpsManager.MODE_ALLOWED || mode2 == AppOpsManager.MODE_IGNORED } } \ No newline at end of file