diff --git a/app/schemas/io.timelimit.android.data.RoomDatabase/14.json b/app/schemas/io.timelimit.android.data.RoomDatabase/14.json new file mode 100644 index 0000000..eadf6db --- /dev/null +++ b/app/schemas/io.timelimit.android.data.RoomDatabase/14.json @@ -0,0 +1,662 @@ +{ + "formatVersion": 1, + "database": { + "version": 14, + "identityHash": "d340606d3cf1eaf15eaf78b3e448a0d7", + "entities": [ + { + "tableName": "user", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `password` TEXT NOT NULL, `second_password_salt` TEXT NOT NULL, `type` TEXT NOT NULL, `timezone` TEXT NOT NULL, `disable_limits_until` INTEGER NOT NULL, `mail` TEXT NOT NULL, `current_device` TEXT NOT NULL, `category_for_not_assigned_apps` TEXT NOT NULL, `relax_primary_device` INTEGER NOT NULL, `mail_notification_flags` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondPasswordSalt", + "columnName": "second_password_salt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeZone", + "columnName": "timezone", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "disableLimitsUntil", + "columnName": "disable_limits_until", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mail", + "columnName": "mail", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentDevice", + "columnName": "current_device", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryForNotAssignedApps", + "columnName": "category_for_not_assigned_apps", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "relaxPrimaryDevice", + "columnName": "relax_primary_device", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mailNotificationFlags", + "columnName": "mail_notification_flags", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "device", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `model` TEXT NOT NULL, `added_at` INTEGER NOT NULL, `current_user_id` TEXT NOT NULL, `apps_version` TEXT NOT NULL, `network_time` TEXT NOT NULL, `current_protection_level` TEXT NOT NULL, `highest_permission_level` TEXT NOT NULL, `current_usage_stats_permission` TEXT NOT NULL, `highest_usage_stats_permission` TEXT NOT NULL, `current_notification_access_permission` TEXT NOT NULL, `highest_notification_access_permission` TEXT NOT NULL, `current_app_version` INTEGER NOT NULL, `highest_app_version` INTEGER NOT NULL, `tried_disabling_device_admin` INTEGER NOT NULL, `did_reboot` INTEGER NOT NULL, `had_manipulation` INTEGER NOT NULL, `did_report_uninstall` INTEGER NOT NULL, `is_user_kept_signed_in` INTEGER NOT NULL, `show_device_connected` INTEGER NOT NULL, `default_user` TEXT NOT NULL, `default_user_timeout` INTEGER NOT NULL, `consider_reboot_manipulation` INTEGER NOT NULL, `current_overlay_permission` TEXT NOT NULL, `highest_overlay_permission` TEXT NOT NULL, `current_accessibility_service_permission` INTEGER NOT NULL, `was_accessibility_service_permission` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "model", + "columnName": "model", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "addedAt", + "columnName": "added_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currentUserId", + "columnName": "current_user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "installedAppsVersion", + "columnName": "apps_version", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "networkTime", + "columnName": "network_time", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentProtectionLevel", + "columnName": "current_protection_level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "highestProtectionLevel", + "columnName": "highest_permission_level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentUsageStatsPermission", + "columnName": "current_usage_stats_permission", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "highestUsageStatsPermission", + "columnName": "highest_usage_stats_permission", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentNotificationAccessPermission", + "columnName": "current_notification_access_permission", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "highestNotificationAccessPermission", + "columnName": "highest_notification_access_permission", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentAppVersion", + "columnName": "current_app_version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "highestAppVersion", + "columnName": "highest_app_version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "manipulationTriedDisablingDeviceAdmin", + "columnName": "tried_disabling_device_admin", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "manipulationDidReboot", + "columnName": "did_reboot", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hadManipulation", + "columnName": "had_manipulation", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "didReportUninstall", + "columnName": "did_report_uninstall", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isUserKeptSignedIn", + "columnName": "is_user_kept_signed_in", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "showDeviceConnected", + "columnName": "show_device_connected", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultUser", + "columnName": "default_user", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "defaultUserTimeout", + "columnName": "default_user_timeout", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "considerRebootManipulation", + "columnName": "consider_reboot_manipulation", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currentOverlayPermission", + "columnName": "current_overlay_permission", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "highestOverlayPermission", + "columnName": "highest_overlay_permission", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accessibilityServiceEnabled", + "columnName": "current_accessibility_service_permission", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "wasAccessibilityServiceEnabled", + "columnName": "was_accessibility_service_permission", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "app", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`device_id` TEXT NOT NULL, `package_name` TEXT NOT NULL, `title` TEXT NOT NULL, `launchable` INTEGER NOT NULL, `recommendation` TEXT NOT NULL, PRIMARY KEY(`device_id`, `package_name`))", + "fields": [ + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "package_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isLaunchable", + "columnName": "launchable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recommendation", + "columnName": "recommendation", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "device_id", + "package_name" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_app_device_id", + "unique": false, + "columnNames": [ + "device_id" + ], + "createSql": "CREATE INDEX `index_app_device_id` ON `${TABLE_NAME}` (`device_id`)" + }, + { + "name": "index_app_package_name", + "unique": false, + "columnNames": [ + "package_name" + ], + "createSql": "CREATE INDEX `index_app_package_name` ON `${TABLE_NAME}` (`package_name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "category_app", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` TEXT NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`category_id`, `package_name`))", + "fields": [ + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "package_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "category_id", + "package_name" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_category_app_category_id", + "unique": false, + "columnNames": [ + "category_id" + ], + "createSql": "CREATE INDEX `index_category_app_category_id` ON `${TABLE_NAME}` (`category_id`)" + }, + { + "name": "index_category_app_package_name", + "unique": false, + "columnNames": [ + "package_name" + ], + "createSql": "CREATE INDEX `index_category_app_package_name` ON `${TABLE_NAME}` (`package_name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "category", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `child_id` TEXT NOT NULL, `title` TEXT NOT NULL, `blocked_times` TEXT NOT NULL, `extra_time` INTEGER NOT NULL, `temporarily_blocked` INTEGER NOT NULL, `base_version` TEXT NOT NULL, `apps_version` TEXT NOT NULL, `rules_version` TEXT NOT NULL, `usedtimes_version` TEXT NOT NULL, `parent_category_id` TEXT NOT NULL, `block_all_notifications` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "childId", + "columnName": "child_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockedMinutesInWeek", + "columnName": "blocked_times", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "extraTimeInMillis", + "columnName": "extra_time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "temporarilyBlocked", + "columnName": "temporarily_blocked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "baseVersion", + "columnName": "base_version", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "assignedAppsVersion", + "columnName": "apps_version", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeLimitRulesVersion", + "columnName": "rules_version", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "usedTimesVersion", + "columnName": "usedtimes_version", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentCategoryId", + "columnName": "parent_category_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockAllNotifications", + "columnName": "block_all_notifications", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "used_time", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`day_of_epoch` INTEGER NOT NULL, `used_time` INTEGER NOT NULL, `category_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `day_of_epoch`))", + "fields": [ + { + "fieldPath": "dayOfEpoch", + "columnName": "day_of_epoch", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usedMillis", + "columnName": "used_time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "category_id", + "day_of_epoch" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "time_limit_rule", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `category_id` TEXT NOT NULL, `apply_to_extra_time_usage` INTEGER NOT NULL, `day_mask` INTEGER NOT NULL, `max_time` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "applyToExtraTimeUsage", + "columnName": "apply_to_extra_time_usage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dayMask", + "columnName": "day_mask", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "maximumTimeInMillis", + "columnName": "max_time", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "config", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "temporarily_allowed_app", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`device_id` TEXT NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`device_id`, `package_name`))", + "fields": [ + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "package_name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "device_id", + "package_name" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "pending_sync_action", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sequence_number` INTEGER NOT NULL, `action` TEXT NOT NULL, `integrity` TEXT NOT NULL, `scheduled_for_upload` INTEGER NOT NULL, `type` TEXT NOT NULL, `user_id` TEXT NOT NULL, PRIMARY KEY(`sequence_number`))", + "fields": [ + { + "fieldPath": "sequenceNumber", + "columnName": "sequence_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "encodedAction", + "columnName": "action", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "integrity", + "columnName": "integrity", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "scheduledForUpload", + "columnName": "scheduled_for_upload", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "sequence_number" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_pending_sync_action_scheduled_for_upload", + "unique": false, + "columnNames": [ + "scheduled_for_upload" + ], + "createSql": "CREATE INDEX `index_pending_sync_action_scheduled_for_upload` ON `${TABLE_NAME}` (`scheduled_for_upload`)" + } + ], + "foreignKeys": [] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"d340606d3cf1eaf15eaf78b3e448a0d7\")" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2f215ac..ef106c3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -115,6 +115,18 @@ + + + + + + + + + + diff --git a/app/src/main/java/io/timelimit/android/data/Migrations.kt b/app/src/main/java/io/timelimit/android/data/Migrations.kt index 3e518ce..e17e3c5 100644 --- a/app/src/main/java/io/timelimit/android/data/Migrations.kt +++ b/app/src/main/java/io/timelimit/android/data/Migrations.kt @@ -98,4 +98,11 @@ object DatabaseMigrations { database.execSQL("ALTER TABLE `device` ADD COLUMN `highest_overlay_permission` TEXT NOT NULL DEFAULT \"not granted\"") } } + + val MIGRATE_TO_V14 = object: Migration(13, 14) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE `device` ADD COLUMN `current_accessibility_service_permission` INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE `device` ADD COLUMN `was_accessibility_service_permission` INTEGER NOT NULL DEFAULT 0") + } + } } diff --git a/app/src/main/java/io/timelimit/android/data/RoomDatabase.kt b/app/src/main/java/io/timelimit/android/data/RoomDatabase.kt index b4ca418..f4265e1 100644 --- a/app/src/main/java/io/timelimit/android/data/RoomDatabase.kt +++ b/app/src/main/java/io/timelimit/android/data/RoomDatabase.kt @@ -32,7 +32,7 @@ import io.timelimit.android.data.model.* ConfigurationItem::class, TemporarilyAllowedApp::class, PendingSyncAction::class -], version = 13) +], version = 14) abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database { companion object { private val lock = Object() @@ -79,7 +79,8 @@ abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database DatabaseMigrations.MIGRATE_TO_V10, DatabaseMigrations.MIGRATE_TO_V11, DatabaseMigrations.MIGRATE_TO_V12, - DatabaseMigrations.MIGRATE_TO_V13 + DatabaseMigrations.MIGRATE_TO_V13, + DatabaseMigrations.MIGRATE_TO_V14 ) .build() } 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 76c80dc..aedc248 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 @@ -82,7 +82,11 @@ data class Device( @ColumnInfo(name = "current_overlay_permission") val currentOverlayPermission: RuntimePermissionStatus, @ColumnInfo(name = "highest_overlay_permission") - val highestOverlayPermission: RuntimePermissionStatus + val highestOverlayPermission: RuntimePermissionStatus, + @ColumnInfo(name = "current_accessibility_service_permission") + val accessibilityServiceEnabled: Boolean, + @ColumnInfo(name = "was_accessibility_service_permission") + val wasAccessibilityServiceEnabled: Boolean ): JsonSerializable { companion object { private const val ID = "id" @@ -111,6 +115,8 @@ data class Device( private const val CONSIDER_REBOOT_A_MANIPULATION = "cram" private const val CURRENT_OVERLAY_PERMISSION = "cop" private const val HIGHEST_OVERLAY_PERMISSION = "hop" + private const val ACCESSIBILITY_SERVICE_ENABLED = "ase" + private const val WAS_ACCESSIBILITY_SERVICE_ENABLED = "wase" fun parse(reader: JsonReader): Device { var id: String? = null @@ -139,6 +145,8 @@ data class Device( var considerRebootManipulation = false var currentOverlayPermission = RuntimePermissionStatus.NotGranted var highestOverlayPermission = RuntimePermissionStatus.NotGranted + var accessibilityServiceEnabled = false + var wasAccessibilityServiceEnabled = false reader.beginObject() @@ -170,6 +178,8 @@ data class Device( CONSIDER_REBOOT_A_MANIPULATION -> considerRebootManipulation = reader.nextBoolean() CURRENT_OVERLAY_PERMISSION -> currentOverlayPermission = RuntimePermissionStatusUtil.parse(reader.nextString()) HIGHEST_OVERLAY_PERMISSION -> highestOverlayPermission = RuntimePermissionStatusUtil.parse(reader.nextString()) + ACCESSIBILITY_SERVICE_ENABLED -> accessibilityServiceEnabled = reader.nextBoolean() + WAS_ACCESSIBILITY_SERVICE_ENABLED -> wasAccessibilityServiceEnabled = reader.nextBoolean() else -> reader.skipValue() } } @@ -202,7 +212,9 @@ data class Device( defaultUserTimeout = defaultUserTimeout, considerRebootManipulation = considerRebootManipulation, currentOverlayPermission = currentOverlayPermission, - highestOverlayPermission = highestOverlayPermission + highestOverlayPermission = highestOverlayPermission, + accessibilityServiceEnabled = accessibilityServiceEnabled, + wasAccessibilityServiceEnabled = wasAccessibilityServiceEnabled ) } } @@ -268,6 +280,8 @@ data class Device( writer.name(CONSIDER_REBOOT_A_MANIPULATION).value(considerRebootManipulation) writer.name(CURRENT_OVERLAY_PERMISSION).value(RuntimePermissionStatusUtil.serialize(currentOverlayPermission)) writer.name(HIGHEST_OVERLAY_PERMISSION).value(RuntimePermissionStatusUtil.serialize(highestOverlayPermission)) + writer.name(ACCESSIBILITY_SERVICE_ENABLED).value(accessibilityServiceEnabled) + writer.name(WAS_ACCESSIBILITY_SERVICE_ENABLED).value(wasAccessibilityServiceEnabled) writer.endObject() } @@ -282,6 +296,8 @@ data class Device( val manipulationOfAppVersion = currentAppVersion != highestAppVersion @Transient val manipulationOfOverlayPermission = currentOverlayPermission != highestOverlayPermission + @Transient + val manipulationOfAccessibilityService = accessibilityServiceEnabled != wasAccessibilityServiceEnabled @Transient val hasActiveManipulationWarning = manipulationOfProtectionLevel || @@ -290,7 +306,8 @@ data class Device( manipulationOfAppVersion || manipulationTriedDisablingDeviceAdmin || manipulationDidReboot || - manipulationOfOverlayPermission + manipulationOfOverlayPermission || + manipulationOfAccessibilityService @Transient val hasAnyManipulation = hasActiveManipulationWarning || hadManipulation diff --git a/app/src/main/java/io/timelimit/android/integration/platform/PlatformIntegration.kt b/app/src/main/java/io/timelimit/android/integration/platform/PlatformIntegration.kt index 103131a..3b65090 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/PlatformIntegration.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/PlatformIntegration.kt @@ -32,6 +32,7 @@ abstract class PlatformIntegration( abstract fun getDrawOverOtherAppsPermissionStatus(): RuntimePermissionStatus abstract fun getNotificationAccessPermissionStatus(): NewPermissionStatus abstract fun getOverlayPermissionStatus(): RuntimePermissionStatus + abstract fun isAccessibilityServiceEnabled(): Boolean abstract fun disableDeviceAdmin() abstract fun trySetLockScreenPassword(password: String): Boolean // this must have a fallback if the permission is not granted diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/AccessibilityService.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/AccessibilityService.kt new file mode 100644 index 0000000..c82792d --- /dev/null +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/AccessibilityService.kt @@ -0,0 +1,51 @@ +/* + * TimeLimit Copyright 2019 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.accessibilityservice.AccessibilityService +import android.view.accessibility.AccessibilityEvent +import io.timelimit.android.logic.DefaultAppLogic + +class AccessibilityService: AccessibilityService() { + companion object { + var instance: io.timelimit.android.integration.platform.android.AccessibilityService? = null + } + + override fun onServiceConnected() { + super.onServiceConnected() + + instance = this + DefaultAppLogic.with(this) // init + } + + override fun onDestroy() { + super.onDestroy() + + instance = null + } + + override fun onAccessibilityEvent(event: AccessibilityEvent?) { + // ignore + } + + override fun onInterrupt() { + // ignore + } + + fun showHomescreen() { + performGlobalAction(GLOBAL_ACTION_HOME) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/AndroidIntegration.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/AndroidIntegration.kt index c49b994..9e7ecc3 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/android/AndroidIntegration.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/AndroidIntegration.kt @@ -140,6 +140,28 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio override fun getOverlayPermissionStatus(): RuntimePermissionStatus = overlay.getOverlayPermissionStatus() + override fun isAccessibilityServiceEnabled(): Boolean { + val service = context.packageName + "/" + AccessibilityService::class.java.canonicalName + + val accessibilityEnabled = try { + Settings.Secure.getInt(context.contentResolver, Settings.Secure.ACCESSIBILITY_ENABLED) + } catch (ex: Settings.SettingNotFoundException) { + 0 + } + + if (accessibilityEnabled == 1) { + val enabledServicesString = Settings.Secure.getString(context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) + + if (!enabledServicesString.isNullOrEmpty()) { + if (enabledServicesString.split(":").contains(service)) { + return true + } + } + } + + return false + } + override fun trySetLockScreenPassword(password: String): Boolean { if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "set password") diff --git a/app/src/main/java/io/timelimit/android/integration/platform/dummy/DummyIntegration.kt b/app/src/main/java/io/timelimit/android/integration/platform/dummy/DummyIntegration.kt index 979d615..9acf03b 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/dummy/DummyIntegration.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/dummy/DummyIntegration.kt @@ -65,6 +65,10 @@ class DummyIntegration( return RuntimePermissionStatus.NotRequired } + override fun isAccessibilityServiceEnabled(): Boolean { + return false + } + override fun trySetLockScreenPassword(password: String): Boolean { return false // it failed } diff --git a/app/src/main/java/io/timelimit/android/logic/AppSetupLogic.kt b/app/src/main/java/io/timelimit/android/logic/AppSetupLogic.kt index b4b3f6b..3e241da 100644 --- a/app/src/main/java/io/timelimit/android/logic/AppSetupLogic.kt +++ b/app/src/main/java/io/timelimit/android/logic/AppSetupLogic.kt @@ -97,7 +97,9 @@ class AppSetupLogic(private val appLogic: AppLogic) { defaultUserTimeout = 0, considerRebootManipulation = false, currentOverlayPermission = RuntimePermissionStatus.NotGranted, - highestOverlayPermission = RuntimePermissionStatus.NotGranted + highestOverlayPermission = RuntimePermissionStatus.NotGranted, + accessibilityServiceEnabled = false, + wasAccessibilityServiceEnabled = false ) appLogic.database.device().addDeviceSync(device) 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 0ed98e7..381b7dd 100644 --- a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt +++ b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt @@ -31,11 +31,13 @@ import io.timelimit.android.date.DateInTimezone import io.timelimit.android.date.getMinuteOfWeek import io.timelimit.android.integration.platform.AppStatusMessage import io.timelimit.android.integration.platform.ProtectionLevel +import io.timelimit.android.integration.platform.android.AccessibilityService import io.timelimit.android.integration.platform.android.AndroidIntegrationApps import io.timelimit.android.livedata.* import io.timelimit.android.sync.actions.UpdateDeviceStatusAction import io.timelimit.android.sync.actions.apply.ApplyActionUtil import io.timelimit.android.util.TimeTextUtil +import kotlinx.coroutines.delay import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.util.* @@ -134,6 +136,22 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { private val appTitleCache = QueryAppTitleCache(appLogic.platformIntegration) + private suspend fun openLockscreen(blockedAppPackageName: String) { + appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage( + title = appTitleCache.query(blockedAppPackageName), + text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) + )) + + appLogic.platformIntegration.setShowBlockingOverlay(true) + + if (appLogic.platformIntegration.isAccessibilityServiceEnabled()) { + AccessibilityService.instance?.showHomescreen() + delay(100) + } + + appLogic.platformIntegration.showAppLockScreen(blockedAppPackageName) + } + private suspend fun backgroundServiceLoop() { val realTime = RealTime.newInstance() @@ -257,22 +275,12 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { if (category == null) { usedTimeUpdateHelper?.commit(appLogic) - appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage( - title = appTitleCache.query(foregroundAppPackageName), - text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) - )) appLogic.platformIntegration.setSuspendedApps(listOf(foregroundAppPackageName), true) - appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) - appLogic.platformIntegration.setShowBlockingOverlay(true) + openLockscreen(foregroundAppPackageName) } else if (category.temporarilyBlocked or (parentCategory?.temporarilyBlocked == true)) { usedTimeUpdateHelper?.commit(appLogic) - appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage( - title = appTitleCache.query(foregroundAppPackageName), - text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) - )) - appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) - appLogic.platformIntegration.setShowBlockingOverlay(true) + openLockscreen(foregroundAppPackageName) } else { // disable time limits temporarily feature if (realTime.shouldTrustTimeTemporarily && nowTimestamp < deviceUserEntry.disableLimitsUntil) { @@ -297,12 +305,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { ) { usedTimeUpdateHelper?.commit(appLogic) - appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage( - title = appTitleCache.query(foregroundAppPackageName), - text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) - )) - appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) - appLogic.platformIntegration.setShowBlockingOverlay(true) + openLockscreen(foregroundAppPackageName) } else { // check time limits val rules = timeLimitRules.get(category.id).waitForNonNullValue() @@ -325,12 +328,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { if (!isCurrentDevice) { usedTimeUpdateHelper?.commit(appLogic) - appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage( - title = appTitleCache.query(foregroundAppPackageName), - text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) - )) - appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) - appLogic.platformIntegration.setShowBlockingOverlay(true) + openLockscreen(foregroundAppPackageName) } else if (realTime.shouldTrustTimeTemporarily) { val usedTimes = usedTimesOfCategoryAndWeekByFirstDayOfWeek.get(Pair(category.id, nowDate.dayOfEpoch - nowDate.dayOfWeek)).waitForNonNullValue() val parentUsedTimes = parentCategory?.let { @@ -439,25 +437,14 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { newUsedTimeItemBatchUpdateHelper.commit(appLogic) - appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage( - title = appTitleCache.query(foregroundAppPackageName), - text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) - )) - appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) - appLogic.platformIntegration.setShowBlockingOverlay(true) - } + openLockscreen(foregroundAppPackageName) } } } else { // if should not trust the time temporarily usedTimeUpdateHelper?.commit(appLogic) - appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage( - title = appTitleCache.query(foregroundAppPackageName), - text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) - )) - appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) - appLogic.platformIntegration.setShowBlockingOverlay(true) + openLockscreen(foregroundAppPackageName) } } } @@ -561,6 +548,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { val usageStatsPermission = appLogic.platformIntegration.getForegroundAppPermissionStatus() val notificationAccess = appLogic.platformIntegration.getNotificationAccessPermissionStatus() val overlayPermission = appLogic.platformIntegration.getOverlayPermissionStatus() + val accessibilityService = appLogic.platformIntegration.isAccessibilityServiceEnabled() var changes = UpdateDeviceStatusAction.empty @@ -592,6 +580,12 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { ) } + if (accessibilityService != deviceEntry.accessibilityServiceEnabled) { + changes = changes.copy( + newAccessibilityServiceEnabled = accessibilityService + ) + } + if (changes != UpdateDeviceStatusAction.empty) { ApplyActionUtil.applyAppLogicAction( action = changes, diff --git a/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt b/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt index 6565d1b..f617b11 100644 --- a/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt +++ b/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt @@ -156,7 +156,9 @@ object ApplyServerDataStatus { defaultUserTimeout = newDevice.defaultUserTimeout, considerRebootManipulation = newDevice.considerRebootManipulation, currentOverlayPermission = newDevice.currentOverlayPermission, - highestOverlayPermission = newDevice.highestOverlayPermission + highestOverlayPermission = newDevice.highestOverlayPermission, + accessibilityServiceEnabled = newDevice.accessibilityServiceEnabled, + wasAccessibilityServiceEnabled = newDevice.wasAccessibilityServiceEnabled )) } else { // eventually update old entry @@ -185,7 +187,9 @@ object ApplyServerDataStatus { defaultUserTimeout = newDevice.defaultUserTimeout, considerRebootManipulation = newDevice.considerRebootManipulation, currentOverlayPermission = newDevice.currentOverlayPermission, - highestOverlayPermission = newDevice.highestOverlayPermission + highestOverlayPermission = newDevice.highestOverlayPermission, + accessibilityServiceEnabled = newDevice.accessibilityServiceEnabled, + wasAccessibilityServiceEnabled = newDevice.wasAccessibilityServiceEnabled ) if (updatedDeviceEntry != oldDeviceEntry) { diff --git a/app/src/main/java/io/timelimit/android/sync/actions/Actions.kt b/app/src/main/java/io/timelimit/android/sync/actions/Actions.kt index 0c5c255..5408df1 100644 --- a/app/src/main/java/io/timelimit/android/sync/actions/Actions.kt +++ b/app/src/main/java/io/timelimit/android/sync/actions/Actions.kt @@ -564,6 +564,7 @@ data class UpdateDeviceStatusAction( val newUsageStatsPermissionStatus: RuntimePermissionStatus?, val newNotificationAccessPermission: NewPermissionStatus?, val newOverlayPermission: RuntimePermissionStatus?, + val newAccessibilityServiceEnabled: Boolean?, val newAppVersion: Int?, val didReboot: Boolean ): AppLogicAction() { @@ -573,6 +574,7 @@ data class UpdateDeviceStatusAction( private const val NEW_USAGE_STATS_PERMISSION_STATUS = "usageStats" private const val NEW_NOTIFICATION_ACCESS_PERMISSION = "notificationAccess" private const val NEW_OVERLAY_PERMISSION = "overlayPermission" + private const val NEW_ACCESSIBILITY_SERVICE_ENABLED = "accessibilityServiceEnabled" private const val NEW_APP_VERSION = "appVersion" private const val DID_REBOOT = "didReboot" @@ -581,6 +583,7 @@ data class UpdateDeviceStatusAction( newUsageStatsPermissionStatus = null, newNotificationAccessPermission = null, newOverlayPermission = null, + newAccessibilityServiceEnabled = null, newAppVersion = null, didReboot = false ) @@ -620,6 +623,12 @@ data class UpdateDeviceStatusAction( .value(RuntimePermissionStatusUtil.serialize(newOverlayPermission)) } + if (newAccessibilityServiceEnabled != null) { + writer + .name(NEW_ACCESSIBILITY_SERVICE_ENABLED) + .value(newAccessibilityServiceEnabled) + } + if (newAppVersion != null) { writer.name(NEW_APP_VERSION) writer.value(newAppVersion) @@ -641,6 +650,7 @@ data class IgnoreManipulationAction( val ignoreNotificationAccessManipulation: Boolean, val ignoreUsageStatsAccessManipulation: Boolean, val ignoreOverlayPermissionManipulation: Boolean, + val ignoreAccessibilityServiceManipulation: Boolean, val ignoreReboot: Boolean, val ignoreHadManipulation: Boolean ): ParentAction() { @@ -653,6 +663,7 @@ data class IgnoreManipulationAction( private const val IGNORE_NOTIFICATION_ACCESS = "notification" private const val IGNORE_USAGE_STATS_ACCESS = "usageStats" private const val IGNORE_OVERLAY_PERMISSION_MANIPULATION = "overlay" + private const val IGNORE_ACCESSIBILITY_SERVICE_MANIPULATION = "accessibilityService" private const val IGNORE_HAD_MANIPULATION = "hadManipulation" private const val IGNORE_REBOOT = "reboot" } @@ -667,6 +678,7 @@ data class IgnoreManipulationAction( (!ignoreNotificationAccessManipulation) && (!ignoreUsageStatsAccessManipulation) && (!ignoreOverlayPermissionManipulation) && + (!ignoreAccessibilityServiceManipulation) && (!ignoreReboot) && (!ignoreHadManipulation) @@ -681,6 +693,7 @@ data class IgnoreManipulationAction( writer.name(IGNORE_NOTIFICATION_ACCESS).value(ignoreNotificationAccessManipulation) writer.name(IGNORE_USAGE_STATS_ACCESS).value(ignoreUsageStatsAccessManipulation) writer.name(IGNORE_OVERLAY_PERMISSION_MANIPULATION).value(ignoreOverlayPermissionManipulation) + writer.name(IGNORE_ACCESSIBILITY_SERVICE_MANIPULATION).value(ignoreAccessibilityServiceManipulation) writer.name(IGNORE_HAD_MANIPULATION).value(ignoreHadManipulation) writer.name(IGNORE_REBOOT).value(ignoreReboot) diff --git a/app/src/main/java/io/timelimit/android/sync/actions/dispatch/AppLogicAction.kt b/app/src/main/java/io/timelimit/android/sync/actions/dispatch/AppLogicAction.kt index f42f909..f3b87aa 100644 --- a/app/src/main/java/io/timelimit/android/sync/actions/dispatch/AppLogicAction.kt +++ b/app/src/main/java/io/timelimit/android/sync/actions/dispatch/AppLogicAction.kt @@ -168,6 +168,24 @@ object LocalDatabaseAppLogicActionDispatcher { } } + if (action.newAccessibilityServiceEnabled != null) { + if (device.accessibilityServiceEnabled != action.newAccessibilityServiceEnabled) { + device = device.copy( + accessibilityServiceEnabled = action.newAccessibilityServiceEnabled + ) + + if (action.newAccessibilityServiceEnabled) { + device = device.copy( + wasAccessibilityServiceEnabled = true + ) + } + + if (device.accessibilityServiceEnabled != device.wasAccessibilityServiceEnabled) { + device = device.copy(hadManipulation = true) + } + } + } + if (action.newAppVersion != null) { if (device.currentAppVersion != action.newAppVersion) { device = device.copy( diff --git a/app/src/main/java/io/timelimit/android/sync/actions/dispatch/ParentAction.kt b/app/src/main/java/io/timelimit/android/sync/actions/dispatch/ParentAction.kt index 5f452f0..432ca12 100644 --- a/app/src/main/java/io/timelimit/android/sync/actions/dispatch/ParentAction.kt +++ b/app/src/main/java/io/timelimit/android/sync/actions/dispatch/ParentAction.kt @@ -294,6 +294,10 @@ object LocalDatabaseParentActionDispatcher { deviceEntry = deviceEntry.copy(highestOverlayPermission = deviceEntry.currentOverlayPermission) } + if (action.ignoreAccessibilityServiceManipulation) { + deviceEntry = deviceEntry.copy(wasAccessibilityServiceEnabled = deviceEntry.accessibilityServiceEnabled) + } + if (action.ignoreReboot) { deviceEntry = deviceEntry.copy(manipulationDidReboot = false) } diff --git a/app/src/main/java/io/timelimit/android/sync/network/ServerDataStatus.kt b/app/src/main/java/io/timelimit/android/sync/network/ServerDataStatus.kt index 17317cf..43d05ae 100644 --- a/app/src/main/java/io/timelimit/android/sync/network/ServerDataStatus.kt +++ b/app/src/main/java/io/timelimit/android/sync/network/ServerDataStatus.kt @@ -180,7 +180,9 @@ data class ServerDeviceData( val defaultUserTimeout: Int, val considerRebootManipulation: Boolean, val currentOverlayPermission: RuntimePermissionStatus, - val highestOverlayPermission: RuntimePermissionStatus + val highestOverlayPermission: RuntimePermissionStatus, + val accessibilityServiceEnabled: Boolean, + val wasAccessibilityServiceEnabled: Boolean ) { companion object { private const val DEVICE_ID = "deviceId" @@ -208,6 +210,8 @@ data class ServerDeviceData( private const val CONSIDER_REBOOT_MANIPULATION = "rebootIsManipulation" private const val CURRENT_OVERLAY_PERMISSION = "cOverlay" private const val HIGHEST_OVERLAY_PERMISSION = "hOverlay" + private const val ACCESSIBILITY_SERVICE_ENABLED = "asEnabled" + private const val WAS_ACCESSIBILITY_SERVICE_ENABLED = "wasAsEnabled" fun parse(reader: JsonReader): ServerDeviceData { var deviceId: String? = null @@ -235,6 +239,8 @@ data class ServerDeviceData( var considerRebootManipulation: Boolean? = null var currentOverlayPermission: RuntimePermissionStatus? = null var highestOverlayPermission: RuntimePermissionStatus? = null + var accessibilityServiceEnabled: Boolean? = null + var wasAccessibilityServiceEnabled: Boolean? = null reader.beginObject() while (reader.hasNext()) { @@ -264,6 +270,8 @@ data class ServerDeviceData( CONSIDER_REBOOT_MANIPULATION -> considerRebootManipulation = reader.nextBoolean() CURRENT_OVERLAY_PERMISSION -> currentOverlayPermission = RuntimePermissionStatusUtil.parse(reader.nextString()) HIGHEST_OVERLAY_PERMISSION -> highestOverlayPermission = RuntimePermissionStatusUtil.parse(reader.nextString()) + ACCESSIBILITY_SERVICE_ENABLED -> accessibilityServiceEnabled = reader.nextBoolean() + WAS_ACCESSIBILITY_SERVICE_ENABLED -> wasAccessibilityServiceEnabled = reader.nextBoolean() else -> reader.skipValue() } } @@ -294,7 +302,9 @@ data class ServerDeviceData( defaultUserTimeout = defaultUserTimeout!!, considerRebootManipulation = considerRebootManipulation!!, currentOverlayPermission = currentOverlayPermission!!, - highestOverlayPermission = highestOverlayPermission!! + highestOverlayPermission = highestOverlayPermission!!, + accessibilityServiceEnabled = accessibilityServiceEnabled!!, + wasAccessibilityServiceEnabled = wasAccessibilityServiceEnabled!! ) } diff --git a/app/src/main/java/io/timelimit/android/ui/manage/device/manage/ManageDeviceFragment.kt b/app/src/main/java/io/timelimit/android/ui/manage/device/manage/ManageDeviceFragment.kt index db26c4f..aeb985b 100644 --- a/app/src/main/java/io/timelimit/android/ui/manage/device/manage/ManageDeviceFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/manage/device/manage/ManageDeviceFragment.kt @@ -195,6 +195,13 @@ class ManageDeviceFragment : Fragment(), FragmentWithCustomTitle { } } + override fun openAccessibilitySettings() { + startActivity( + Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } + override fun manageDeviceAdmin() { if (binding.isThisDevice == true) { val protectionLevel = logic.platformIntegration.getCurrentProtectionLevel() @@ -279,6 +286,7 @@ class ManageDeviceFragment : Fragment(), FragmentWithCustomTitle { binding.notificationAccessPermission = device.currentNotificationAccessPermission binding.protectionLevel = device.currentProtectionLevel binding.overlayPermission = device.currentOverlayPermission + binding.accessibilityServiceEnabled = device.accessibilityServiceEnabled binding.didAppDowngrade = device.currentAppVersion < device.highestAppVersion } }) @@ -365,6 +373,7 @@ interface ManageDeviceFragmentHandlers { fun openUsageStatsSettings() fun openNotificationAccessSettings() fun openDrawOverOtherAppsScreen() + fun openAccessibilitySettings() fun manageDeviceAdmin() fun editDeviceTitle() fun showAuthenticationScreen() diff --git a/app/src/main/java/io/timelimit/android/ui/manage/device/manage/ManageDeviceManipulation.kt b/app/src/main/java/io/timelimit/android/ui/manage/device/manage/ManageDeviceManipulation.kt index 9478685..682957e 100644 --- a/app/src/main/java/io/timelimit/android/ui/manage/device/manage/ManageDeviceManipulation.kt +++ b/app/src/main/java/io/timelimit/android/ui/manage/device/manage/ManageDeviceManipulation.kt @@ -42,6 +42,7 @@ object ManageDeviceManipulation { binding.hasManipulatedUsageStatsAccess = device?.manipulationOfUsageStats ?: false binding.hasManipulatedNotificationAccess = device?.manipulationOfNotificationAccess ?: false binding.hasManipulatedOverlayPermission = device?.manipulationOfOverlayPermission ?: false + binding.hasManipulatedAccessibilityService = device?.manipulationOfAccessibilityService ?: false binding.hasManipulationReboot = device?.manipulationDidReboot ?: false binding.hasHadManipulation = (device?.hadManipulation ?: false) and (! (device?.hasActiveManipulationWarning ?: false)) binding.hasAnyManipulation = device?.hasAnyManipulation ?: false @@ -64,6 +65,7 @@ object ManageDeviceManipulation { binding.usageAccessCheckbox, binding.notificationAccessCheckbox, binding.overlayPermissionCheckbox, + binding.accessibilityServiceCheckbox, binding.rebootCheckbox, binding.hadManipulationCheckbox ) @@ -83,6 +85,7 @@ object ManageDeviceManipulation { ignoreDeviceAdminManipulationAttempt = binding.deviceAdminDisableAttemptCheckbox.isChecked && binding.hasTriedManipulatingDeviceAdmin == true, ignoreDeviceAdminManipulation = binding.deviceAdminDisabledCheckbox.isChecked && binding.hasManipulatedDeviceAdmin == true, ignoreOverlayPermissionManipulation = binding.overlayPermissionCheckbox.isChecked && binding.hasManipulatedOverlayPermission == true, + ignoreAccessibilityServiceManipulation = binding.accessibilityServiceCheckbox.isChecked && binding.hasManipulatedAccessibilityService == true, ignoreAppDowngrade = binding.appVersionCheckbox.isChecked && binding.hasManipulatedAppVersion == true, ignoreReboot = binding.rebootCheckbox.isChecked && binding.hasManipulationReboot == true, ignoreHadManipulation = binding.hadManipulationCheckbox.isChecked || ( diff --git a/app/src/main/java/io/timelimit/android/ui/setup/SetupDevicePermissionsFragment.kt b/app/src/main/java/io/timelimit/android/ui/setup/SetupDevicePermissionsFragment.kt index 219d479..6e36a35 100644 --- a/app/src/main/java/io/timelimit/android/ui/setup/SetupDevicePermissionsFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/setup/SetupDevicePermissionsFragment.kt @@ -99,10 +99,18 @@ class SetupDevicePermissionsFragment : Fragment() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { startActivity( Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context!!.packageName)) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ) } } + override fun openAccessibilitySettings() { + startActivity( + Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } + override fun gotoNextStep() { navigation.safeNavigate( SetupDevicePermissionsFragmentDirections @@ -124,6 +132,7 @@ class SetupDevicePermissionsFragment : Fragment() { binding.protectionLevel = platform.getCurrentProtectionLevel() binding.usageStatsAccess = platform.getForegroundAppPermissionStatus() binding.overlayPermission = platform.getOverlayPermissionStatus() + binding.accessibilityServiceEnabled = platform.isAccessibilityServiceEnabled() } override fun onResume() { @@ -138,5 +147,6 @@ interface SetupDevicePermissionsHandlers { fun openUsageStatsSettings() fun openNotificationAccessSettings() fun openDrawOverOtherAppsScreen() + fun openAccessibilitySettings() fun gotoNextStep() } diff --git a/app/src/main/res/layout/fragment_manage_device.xml b/app/src/main/res/layout/fragment_manage_device.xml index 3e4e55f..d7db9e7 100644 --- a/app/src/main/res/layout/fragment_manage_device.xml +++ b/app/src/main/res/layout/fragment_manage_device.xml @@ -58,6 +58,10 @@ name="overlayPermission" type="RuntimePermissionStatus" /> + + @@ -461,6 +465,65 @@ + + + + + + + + + + + + + + + + + + + + + @@ -293,6 +297,57 @@ + + + + + + + + + + + + + + + + + + @@ -118,6 +122,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + + Nutzungsdatenzugriff Benachrichtigungszugriff Berechtigung zum Anzeigen über anderen Apps + Bedienhilfe-Berechtigung ältere App-Version installiert Gerät wurde neu gestartet Es gab eine Manipulation, die wieder beendet wurde diff --git a/app/src/main/res/values-de/strings-manage-device.xml b/app/src/main/res/values-de/strings-manage-device.xml index 6c9851d..3945d6a 100644 --- a/app/src/main/res/values-de/strings-manage-device.xml +++ b/app/src/main/res/values-de/strings-manage-device.xml @@ -52,6 +52,12 @@ Das Anzeigen über anderen Apps kann das Sperren in einigen Fällen verbessern. + Bedienhilfe + + Damit \"drückt\" TimeLimit den Home-Button, bevor der Sperrbildschirm aufgerufen wird. + Das kann das Sperren in einigen Fällen verbessern. + + Geräte-Administrator Die Geräte-Administrator-Berechtigung wurde nicht gewährt. TimeLimit kann jederzeit über die Systemeinstellungen beendet werden. diff --git a/app/src/main/res/values/strings-manage-device-manipulation.xml b/app/src/main/res/values/strings-manage-device-manipulation.xml index 2b8d368..229afbe 100644 --- a/app/src/main/res/values/strings-manage-device-manipulation.xml +++ b/app/src/main/res/values/strings-manage-device-manipulation.xml @@ -23,6 +23,7 @@ usage stats access notification access draw over other Apps permission + Accessibility service permission older App version installed device was rebooted there was a manipulation which was stopped again diff --git a/app/src/main/res/values/strings-manage-device.xml b/app/src/main/res/values/strings-manage-device.xml index 68568ed..1d98bc5 100644 --- a/app/src/main/res/values/strings-manage-device.xml +++ b/app/src/main/res/values/strings-manage-device.xml @@ -52,6 +52,12 @@ Enabling drawing on top of other Apps can improve blocking in some cases. + Accessibility service + + This is used to \"press\" the home button before showing the lock screen. + This fixes blocking in some cases. + + Device admin The device admin permission was not granted. diff --git a/app/src/main/res/xml/accesibility.xml b/app/src/main/res/xml/accesibility.xml new file mode 100644 index 0000000..6eb27d4 --- /dev/null +++ b/app/src/main/res/xml/accesibility.xml @@ -0,0 +1,22 @@ + + +