diff --git a/app/schemas/io.timelimit.android.data.RoomDatabase/12.json b/app/schemas/io.timelimit.android.data.RoomDatabase/12.json index 238cb09..d38e412 100644 --- a/app/schemas/io.timelimit.android.data.RoomDatabase/12.json +++ b/app/schemas/io.timelimit.android.data.RoomDatabase/12.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 12, - "identityHash": "6634817cfeb16e6c23aced572b2a391e", + "identityHash": "289393ddc0b615d627ac88802fbfb977", "entities": [ { "tableName": "user", @@ -92,7 +92,7 @@ }, { "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, PRIMARY KEY(`id`))", + "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, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -237,6 +237,18 @@ "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 } ], "primaryKey": { @@ -356,7 +368,7 @@ }, { "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, `block_all_notifications` 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, PRIMARY KEY(`id`))", + "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", @@ -394,12 +406,6 @@ "affinity": "INTEGER", "notNull": true }, - { - "fieldPath": "blockAllNotifications", - "columnName": "block_all_notifications", - "affinity": "INTEGER", - "notNull": true - }, { "fieldPath": "baseVersion", "columnName": "base_version", @@ -429,6 +435,12 @@ "columnName": "parent_category_id", "affinity": "TEXT", "notNull": true + }, + { + "fieldPath": "blockAllNotifications", + "columnName": "block_all_notifications", + "affinity": "INTEGER", + "notNull": true } ], "primaryKey": { @@ -632,7 +644,7 @@ ], "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, \"6634817cfeb16e6c23aced572b2a391e\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"289393ddc0b615d627ac88802fbfb977\")" ] } } \ No newline at end of file diff --git a/app/schemas/io.timelimit.android.data.RoomDatabase/13.json b/app/schemas/io.timelimit.android.data.RoomDatabase/13.json new file mode 100644 index 0000000..b6d4a17 --- /dev/null +++ b/app/schemas/io.timelimit.android.data.RoomDatabase/13.json @@ -0,0 +1,650 @@ +{ + "formatVersion": 1, + "database": { + "version": 13, + "identityHash": "289393ddc0b615d627ac88802fbfb977", + "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, 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 + } + ], + "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, \"289393ddc0b615d627ac88802fbfb977\")" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f5e1abf..2f215ac 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,7 +20,6 @@ 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 c30346a..3e518ce 100644 --- a/app/src/main/java/io/timelimit/android/data/Migrations.kt +++ b/app/src/main/java/io/timelimit/android/data/Migrations.kt @@ -91,4 +91,11 @@ object DatabaseMigrations { database.execSQL("ALTER TABLE `category` ADD COLUMN `block_all_notifications` INTEGER NOT NULL DEFAULT 0") } } + + val MIGRATE_TO_V13 = object: Migration(12, 13) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE `device` ADD COLUMN `current_overlay_permission` TEXT NOT NULL DEFAULT \"not granted\"") + database.execSQL("ALTER TABLE `device` ADD COLUMN `highest_overlay_permission` TEXT NOT NULL DEFAULT \"not granted\"") + } + } } 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 1239b61..b4ca418 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 = 12) +], version = 13) abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database { companion object { private val lock = Object() @@ -78,7 +78,8 @@ abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database DatabaseMigrations.MIGRATE_TO_V9, DatabaseMigrations.MIGRATE_TO_V10, DatabaseMigrations.MIGRATE_TO_V11, - DatabaseMigrations.MIGRATE_TO_V12 + DatabaseMigrations.MIGRATE_TO_V12, + DatabaseMigrations.MIGRATE_TO_V13 ) .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 f772894..76c80dc 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 @@ -78,7 +78,11 @@ data class Device( @ColumnInfo(name = "default_user_timeout") val defaultUserTimeout: Int, @ColumnInfo(name = "consider_reboot_manipulation") - val considerRebootManipulation: Boolean + val considerRebootManipulation: Boolean, + @ColumnInfo(name = "current_overlay_permission") + val currentOverlayPermission: RuntimePermissionStatus, + @ColumnInfo(name = "highest_overlay_permission") + val highestOverlayPermission: RuntimePermissionStatus ): JsonSerializable { companion object { private const val ID = "id" @@ -105,6 +109,8 @@ data class Device( private const val DEFAULT_USER = "du" private const val DEFAULT_USER_TIMEOUT = "dut" private const val CONSIDER_REBOOT_A_MANIPULATION = "cram" + private const val CURRENT_OVERLAY_PERMISSION = "cop" + private const val HIGHEST_OVERLAY_PERMISSION = "hop" fun parse(reader: JsonReader): Device { var id: String? = null @@ -131,6 +137,8 @@ data class Device( var defaultUser = "" var defaultUserTimeout = 0 var considerRebootManipulation = false + var currentOverlayPermission = RuntimePermissionStatus.NotGranted + var highestOverlayPermission = RuntimePermissionStatus.NotGranted reader.beginObject() @@ -160,6 +168,8 @@ data class Device( DEFAULT_USER -> defaultUser = reader.nextString() DEFAULT_USER_TIMEOUT -> defaultUserTimeout = reader.nextInt() CONSIDER_REBOOT_A_MANIPULATION -> considerRebootManipulation = reader.nextBoolean() + CURRENT_OVERLAY_PERMISSION -> currentOverlayPermission = RuntimePermissionStatusUtil.parse(reader.nextString()) + HIGHEST_OVERLAY_PERMISSION -> highestOverlayPermission = RuntimePermissionStatusUtil.parse(reader.nextString()) else -> reader.skipValue() } } @@ -190,7 +200,9 @@ data class Device( showDeviceConnected = showDeviceConnected, defaultUser = defaultUser, defaultUserTimeout = defaultUserTimeout, - considerRebootManipulation = considerRebootManipulation + considerRebootManipulation = considerRebootManipulation, + currentOverlayPermission = currentOverlayPermission, + highestOverlayPermission = highestOverlayPermission ) } } @@ -254,6 +266,8 @@ data class Device( writer.name(DEFAULT_USER).value(defaultUser) writer.name(DEFAULT_USER_TIMEOUT).value(defaultUserTimeout) 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.endObject() } @@ -266,6 +280,8 @@ data class Device( val manipulationOfNotificationAccess = currentNotificationAccessPermission != highestNotificationAccessPermission @Transient val manipulationOfAppVersion = currentAppVersion != highestAppVersion + @Transient + val manipulationOfOverlayPermission = currentOverlayPermission != highestOverlayPermission @Transient val hasActiveManipulationWarning = manipulationOfProtectionLevel || @@ -273,7 +289,8 @@ data class Device( manipulationOfNotificationAccess || manipulationOfAppVersion || manipulationTriedDisablingDeviceAdmin || - manipulationDidReboot + manipulationDidReboot || + manipulationOfOverlayPermission @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 fabf82c..103131a 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 @@ -31,12 +31,14 @@ abstract class PlatformIntegration( abstract fun getForegroundAppPermissionStatus(): RuntimePermissionStatus abstract fun getDrawOverOtherAppsPermissionStatus(): RuntimePermissionStatus abstract fun getNotificationAccessPermissionStatus(): NewPermissionStatus + abstract fun getOverlayPermissionStatus(): RuntimePermissionStatus abstract fun disableDeviceAdmin() abstract fun trySetLockScreenPassword(password: String): Boolean // this must have a fallback if the permission is not granted abstract fun showOverlayMessage(text: String) abstract fun showAppLockScreen(currentPackageName: String) + abstract fun setShowBlockingOverlay(show: Boolean) // this should throw an SecurityException if the permission is missing abstract suspend fun getForegroundAppPackageName(): String? abstract fun setAppStatusMessage(message: AppStatusMessage?) 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 6101edf..c49b994 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 @@ -17,6 +17,7 @@ package io.timelimit.android.integration.platform.android import android.annotation.TargetApi import android.app.ActivityManager +import android.app.Application import android.app.NotificationManager import android.app.PendingIntent import android.app.admin.DevicePolicyManager @@ -73,6 +74,7 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio private val activityManager = this.context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager private val notificationManager = this.context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager private val deviceAdmin = ComponentName(context.applicationContext, AdminReceiver::class.java) + private val overlay = OverlayUtil(context as Application) init { AppsChangeListener.registerBroadcastReceiver(this.context, object : BroadcastReceiver() { @@ -136,6 +138,8 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio } } + override fun getOverlayPermissionStatus(): RuntimePermissionStatus = overlay.getOverlayPermissionStatus() + override fun trySetLockScreenPassword(password: String): Boolean { if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "set password") @@ -186,6 +190,14 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio LockActivity.start(context, currentPackageName) } + override fun setShowBlockingOverlay(show: Boolean) { + if (show) { + overlay.show() + } else { + overlay.hide() + } + } + override fun isScreenOn(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { return powerManager.isInteractive 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 new file mode 100644 index 0000000..7c2f707 --- /dev/null +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/OverlayUtil.kt @@ -0,0 +1,81 @@ +/* + * 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.app.Application +import android.content.Context +import android.view.WindowManager +import android.graphics.PixelFormat +import android.os.Build +import android.provider.Settings +import android.view.LayoutInflater +import io.timelimit.android.async.Threads +import io.timelimit.android.databinding.BlockingOverlayBinding +import io.timelimit.android.integration.platform.RuntimePermissionStatus + +class OverlayUtil(private var application: Application) { + private val windowManager = application.getSystemService(Context.WINDOW_SERVICE) as WindowManager + private var currentView: BlockingOverlayBinding? = null + + fun show() { + if (currentView != null) { + return + } + + if (getOverlayPermissionStatus() == RuntimePermissionStatus.NotGranted) { + return + } + + val view = BlockingOverlayBinding.inflate(LayoutInflater.from(application)) + + val params = WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY + else + WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT + ) + + windowManager.addView(view.root, params) + currentView = view + + Threads.mainThreadHandler.postDelayed({ + view.showWarningMessage = true + }, 2000) + } + + fun hide() { + if (currentView == null) { + return + } + + windowManager.removeView(currentView!!.root) + currentView = null + } + + fun isOverlayShown() = currentView?.root?.isShown ?: false + + fun getOverlayPermissionStatus() = /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + if (Settings.canDrawOverlays(application)) + RuntimePermissionStatus.Granted + else + */RuntimePermissionStatus.NotGranted/* + else + RuntimePermissionStatus.NotRequired*/ +} \ No newline at end of file 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 215b1f7..979d615 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 @@ -61,6 +61,10 @@ class DummyIntegration( return notificationAccess } + override fun getOverlayPermissionStatus(): RuntimePermissionStatus { + return RuntimePermissionStatus.NotRequired + } + override fun trySetLockScreenPassword(password: String): Boolean { return false // it failed } @@ -72,6 +76,10 @@ class DummyIntegration( launchLockScreenForPackage = currentPackageName } + override fun setShowBlockingOverlay(show: Boolean) { + // ignore + } + fun getAndResetShowAppLockScreen(): String? { try { return launchLockScreenForPackage 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 7db456d..b4b3f6b 100644 --- a/app/src/main/java/io/timelimit/android/logic/AppSetupLogic.kt +++ b/app/src/main/java/io/timelimit/android/logic/AppSetupLogic.kt @@ -95,7 +95,9 @@ class AppSetupLogic(private val appLogic: AppLogic) { showDeviceConnected = false, defaultUser = "", defaultUserTimeout = 0, - considerRebootManipulation = false + considerRebootManipulation = false, + currentOverlayPermission = RuntimePermissionStatus.NotGranted, + highestOverlayPermission = RuntimePermissionStatus.NotGranted ) 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 ac5dcfb..0ed98e7 100644 --- a/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt +++ b/app/src/main/java/io/timelimit/android/logic/BackgroundTaskLogic.kt @@ -35,7 +35,6 @@ 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.ui.IsAppInForeground import io.timelimit.android.util.TimeTextUtil import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -144,6 +143,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { usedTimeUpdateHelper?.commit(appLogic) liveDataCaches.removeAllItems() appLogic.platformIntegration.setAppStatusMessage(null) + appLogic.platformIntegration.setShowBlockingOverlay(false) appLogic.enable.waitUntilValueMatches { it == true } continue @@ -165,12 +165,14 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { text = appLogic.context.getString(R.string.background_logic_timeout_text) ) ) + appLogic.platformIntegration.setShowBlockingOverlay(false) liveDataCaches.reportLoopDone() appLogic.timeApi.sleep(BACKGROUND_SERVICE_INTERVAL) } else { liveDataCaches.removeAllItems() appLogic.platformIntegration.setAppStatusMessage(null) + appLogic.platformIntegration.setShowBlockingOverlay(false) val isChildSignedIn = deviceUserEntryLive.read().map { it != null && it.type == UserType.Child } @@ -238,12 +240,14 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { appTitleCache.query(foregroundAppPackageName), appLogic.context.getString(R.string.background_logic_whitelisted) )) + appLogic.platformIntegration.setShowBlockingOverlay(false) } else if (foregroundAppPackageName != null && temporarilyAllowedApps.contains(foregroundAppPackageName)) { usedTimeUpdateHelper?.commit(appLogic) appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage( appTitleCache.query(foregroundAppPackageName), appLogic.context.getString(R.string.background_logic_temporarily_allowed) )) + appLogic.platformIntegration.setShowBlockingOverlay(false) } else if (foregroundAppPackageName != null) { val appCategory = appCategories.get(Pair(foregroundAppPackageName, categories.map { it.id })).waitForNullableValue() val category = categories.find { it.id == appCategory?.categoryId } @@ -259,6 +263,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { )) appLogic.platformIntegration.setSuspendedApps(listOf(foregroundAppPackageName), true) appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) + appLogic.platformIntegration.setShowBlockingOverlay(true) } else if (category.temporarilyBlocked or (parentCategory?.temporarilyBlocked == true)) { usedTimeUpdateHelper?.commit(appLogic) @@ -267,6 +272,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) )) appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) + appLogic.platformIntegration.setShowBlockingOverlay(true) } else { // disable time limits temporarily feature if (realTime.shouldTrustTimeTemporarily && nowTimestamp < deviceUserEntry.disableLimitsUntil) { @@ -274,6 +280,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { title = appTitleCache.query(foregroundAppPackageName), text = appLogic.context.getString(R.string.background_logic_limits_disabled) )) + appLogic.platformIntegration.setShowBlockingOverlay(false) } else if ( // check blocked time areas // directly blocked @@ -295,6 +302,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) )) appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) + appLogic.platformIntegration.setShowBlockingOverlay(true) } else { // check time limits val rules = timeLimitRules.get(category.id).waitForNonNullValue() @@ -310,6 +318,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { category.title + " - " + appTitleCache.query(foregroundAppPackageName), appLogic.context.getString(R.string.background_logic_no_timelimit) )) + appLogic.platformIntegration.setShowBlockingOverlay(false) } else { val isCurrentDevice = isThisDeviceTheCurrentDeviceLive.read().waitForNonNullValue() @@ -321,6 +330,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) )) appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) + appLogic.platformIntegration.setShowBlockingOverlay(true) } else if (realTime.shouldTrustTimeTemporarily) { val usedTimes = usedTimesOfCategoryAndWeekByFirstDayOfWeek.get(Pair(category.id, nowDate.dayOfEpoch - nowDate.dayOfWeek)).waitForNonNullValue() val parentUsedTimes = parentCategory?.let { @@ -387,6 +397,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { category.title + " - " + appTitleCache.query(foregroundAppPackageName), appLogic.context.getString(R.string.background_logic_no_timelimit) )) + appLogic.platformIntegration.setShowBlockingOverlay(false) } else { // time limited if (remaining.includingExtraTime > 0) { @@ -397,6 +408,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { category.title + " - " + appTitleCache.query(foregroundAppPackageName), appLogic.context.getString(R.string.background_logic_using_extra_time, TimeTextUtil.remaining(remaining.includingExtraTime.toInt(), appLogic.context)) )) + appLogic.platformIntegration.setShowBlockingOverlay(false) if (isScreenOn) { newUsedTimeItemBatchUpdateHelper.addUsedTime( @@ -412,6 +424,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { category.title + " - " + appTitleCache.query(foregroundAppPackageName), TimeTextUtil.remaining(remaining.default.toInt(), appLogic.context) )) + appLogic.platformIntegration.setShowBlockingOverlay(false) if (isScreenOn) { newUsedTimeItemBatchUpdateHelper.addUsedTime( @@ -431,6 +444,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) )) appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) + appLogic.platformIntegration.setShowBlockingOverlay(true) } } } else { @@ -443,6 +457,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { text = appLogic.context.getString(R.string.background_logic_opening_lockscreen) )) appLogic.platformIntegration.showAppLockScreen(foregroundAppPackageName) + appLogic.platformIntegration.setShowBlockingOverlay(true) } } } @@ -452,6 +467,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { appLogic.context.getString(R.string.background_logic_idle_title), appLogic.context.getString(R.string.background_logic_idle_text) )) + appLogic.platformIntegration.setShowBlockingOverlay(false) } } catch (ex: SecurityException) { // this is handled by an other main loop (with a delay) @@ -460,6 +476,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { appLogic.context.getString(R.string.background_logic_error), appLogic.context.getString(R.string.background_logic_error_permission) )) + appLogic.platformIntegration.setShowBlockingOverlay(false) } catch (ex: Exception) { if (BuildConfig.DEBUG) { Log.w(LOG_TAG, "exception during running main loop", ex) @@ -469,6 +486,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { appLogic.context.getString(R.string.background_logic_error), appLogic.context.getString(R.string.background_logic_error_internal) )) + appLogic.platformIntegration.setShowBlockingOverlay(false) } liveDataCaches.reportLoopDone() @@ -542,6 +560,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { val protectionLevel = appLogic.platformIntegration.getCurrentProtectionLevel() val usageStatsPermission = appLogic.platformIntegration.getForegroundAppPermissionStatus() val notificationAccess = appLogic.platformIntegration.getNotificationAccessPermissionStatus() + val overlayPermission = appLogic.platformIntegration.getOverlayPermissionStatus() var changes = UpdateDeviceStatusAction.empty @@ -567,6 +586,12 @@ class BackgroundTaskLogic(val appLogic: AppLogic) { ) } + if (overlayPermission != deviceEntry.currentOverlayPermission) { + changes = changes.copy( + newOverlayPermission = overlayPermission + ) + } + 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 0f0fcc1..6565d1b 100644 --- a/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt +++ b/app/src/main/java/io/timelimit/android/sync/ApplyServerDataStatus.kt @@ -154,7 +154,9 @@ object ApplyServerDataStatus { showDeviceConnected = newDevice.showDeviceConnected, defaultUser = newDevice.defaultUser, defaultUserTimeout = newDevice.defaultUserTimeout, - considerRebootManipulation = newDevice.considerRebootManipulation + considerRebootManipulation = newDevice.considerRebootManipulation, + currentOverlayPermission = newDevice.currentOverlayPermission, + highestOverlayPermission = newDevice.highestOverlayPermission )) } else { // eventually update old entry @@ -181,7 +183,9 @@ object ApplyServerDataStatus { showDeviceConnected = newDevice.showDeviceConnected, defaultUser = newDevice.defaultUser, defaultUserTimeout = newDevice.defaultUserTimeout, - considerRebootManipulation = newDevice.considerRebootManipulation + considerRebootManipulation = newDevice.considerRebootManipulation, + currentOverlayPermission = newDevice.currentOverlayPermission, + highestOverlayPermission = newDevice.highestOverlayPermission ) 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 57d6639..0c5c255 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 @@ -563,6 +563,7 @@ data class UpdateDeviceStatusAction( val newProtectionLevel: ProtectionLevel?, val newUsageStatsPermissionStatus: RuntimePermissionStatus?, val newNotificationAccessPermission: NewPermissionStatus?, + val newOverlayPermission: RuntimePermissionStatus?, val newAppVersion: Int?, val didReboot: Boolean ): AppLogicAction() { @@ -571,6 +572,7 @@ data class UpdateDeviceStatusAction( private const val NEW_PROTECTION_LEVEL = "protectionLevel" 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_APP_VERSION = "appVersion" private const val DID_REBOOT = "didReboot" @@ -578,6 +580,7 @@ data class UpdateDeviceStatusAction( newProtectionLevel = null, newUsageStatsPermissionStatus = null, newNotificationAccessPermission = null, + newOverlayPermission = null, newAppVersion = null, didReboot = false ) @@ -611,6 +614,12 @@ data class UpdateDeviceStatusAction( .value(NewPermissionStatusUtil.serialize(newNotificationAccessPermission)) } + if (newOverlayPermission != null) { + writer + .name(NEW_OVERLAY_PERMISSION) + .value(RuntimePermissionStatusUtil.serialize(newOverlayPermission)) + } + if (newAppVersion != null) { writer.name(NEW_APP_VERSION) writer.value(newAppVersion) @@ -631,6 +640,7 @@ data class IgnoreManipulationAction( val ignoreAppDowngrade: Boolean, val ignoreNotificationAccessManipulation: Boolean, val ignoreUsageStatsAccessManipulation: Boolean, + val ignoreOverlayPermissionManipulation: Boolean, val ignoreReboot: Boolean, val ignoreHadManipulation: Boolean ): ParentAction() { @@ -642,6 +652,7 @@ data class IgnoreManipulationAction( private const val IGNORE_APP_DOWNGRADE = "downgrade" 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_HAD_MANIPULATION = "hadManipulation" private const val IGNORE_REBOOT = "reboot" } @@ -655,6 +666,7 @@ data class IgnoreManipulationAction( (!ignoreAppDowngrade) && (!ignoreNotificationAccessManipulation) && (!ignoreUsageStatsAccessManipulation) && + (!ignoreOverlayPermissionManipulation) && (!ignoreReboot) && (!ignoreHadManipulation) @@ -668,6 +680,7 @@ data class IgnoreManipulationAction( writer.name(IGNORE_APP_DOWNGRADE).value(ignoreAppDowngrade) 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_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 33d8351..f42f909 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 @@ -150,6 +150,24 @@ object LocalDatabaseAppLogicActionDispatcher { } } + if (action.newOverlayPermission != null) { + if (device.currentOverlayPermission != action.newOverlayPermission) { + device = device.copy( + currentOverlayPermission = action.newOverlayPermission + ) + + if (RuntimePermissionStatusUtil.toInt(action.newOverlayPermission) > RuntimePermissionStatusUtil.toInt(device.highestOverlayPermission)) { + device = device.copy( + highestOverlayPermission = action.newOverlayPermission + ) + } + + if (device.currentOverlayPermission != device.highestOverlayPermission) { + 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 8380de1..5f452f0 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 @@ -290,6 +290,10 @@ object LocalDatabaseParentActionDispatcher { deviceEntry = deviceEntry.copy(highestUsageStatsPermission = deviceEntry.currentUsageStatsPermission) } + if (action.ignoreOverlayPermissionManipulation) { + deviceEntry = deviceEntry.copy(highestOverlayPermission = deviceEntry.currentOverlayPermission) + } + 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 494483a..17317cf 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 @@ -178,7 +178,9 @@ data class ServerDeviceData( val showDeviceConnected: Boolean, val defaultUser: String, val defaultUserTimeout: Int, - val considerRebootManipulation: Boolean + val considerRebootManipulation: Boolean, + val currentOverlayPermission: RuntimePermissionStatus, + val highestOverlayPermission: RuntimePermissionStatus ) { companion object { private const val DEVICE_ID = "deviceId" @@ -204,6 +206,8 @@ data class ServerDeviceData( private const val DEFAULT_USER = "defUser" private const val DEFAULT_USER_TIMEOUT = "defUserTimeout" private const val CONSIDER_REBOOT_MANIPULATION = "rebootIsManipulation" + private const val CURRENT_OVERLAY_PERMISSION = "cOverlay" + private const val HIGHEST_OVERLAY_PERMISSION = "hOverlay" fun parse(reader: JsonReader): ServerDeviceData { var deviceId: String? = null @@ -229,6 +233,8 @@ data class ServerDeviceData( var defaultUser: String? = null var defaultUserTimeout: Int? = null var considerRebootManipulation: Boolean? = null + var currentOverlayPermission: RuntimePermissionStatus? = null + var highestOverlayPermission: RuntimePermissionStatus? = null reader.beginObject() while (reader.hasNext()) { @@ -256,6 +262,8 @@ data class ServerDeviceData( DEFAULT_USER -> defaultUser = reader.nextString() DEFAULT_USER_TIMEOUT -> defaultUserTimeout = reader.nextInt() CONSIDER_REBOOT_MANIPULATION -> considerRebootManipulation = reader.nextBoolean() + CURRENT_OVERLAY_PERMISSION -> currentOverlayPermission = RuntimePermissionStatusUtil.parse(reader.nextString()) + HIGHEST_OVERLAY_PERMISSION -> highestOverlayPermission = RuntimePermissionStatusUtil.parse(reader.nextString()) else -> reader.skipValue() } } @@ -284,7 +292,9 @@ data class ServerDeviceData( showDeviceConnected = showDeviceConnected!!, defaultUser = defaultUser!!, defaultUserTimeout = defaultUserTimeout!!, - considerRebootManipulation = considerRebootManipulation!! + considerRebootManipulation = considerRebootManipulation!!, + currentOverlayPermission = currentOverlayPermission!!, + highestOverlayPermission = highestOverlayPermission!! ) } 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 8ffb3fd..db26c4f 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 @@ -18,6 +18,7 @@ package io.timelimit.android.ui.manage.device.manage import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Intent +import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.Settings @@ -186,6 +187,14 @@ class ManageDeviceFragment : Fragment(), FragmentWithCustomTitle { } } + override fun openDrawOverOtherAppsScreen() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + startActivity( + Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context!!.packageName)) + ) + } + } + override fun manageDeviceAdmin() { if (binding.isThisDevice == true) { val protectionLevel = logic.platformIntegration.getCurrentProtectionLevel() @@ -269,6 +278,7 @@ class ManageDeviceFragment : Fragment(), FragmentWithCustomTitle { binding.usageStatsAccess = device.currentUsageStatsPermission binding.notificationAccessPermission = device.currentNotificationAccessPermission binding.protectionLevel = device.currentProtectionLevel + binding.overlayPermission = device.currentOverlayPermission binding.didAppDowngrade = device.currentAppVersion < device.highestAppVersion } }) @@ -354,6 +364,7 @@ interface ManageDeviceFragmentHandlers { fun changeNetworkTimeVerification(newValue: NetworkTime) fun openUsageStatsSettings() fun openNotificationAccessSettings() + fun openDrawOverOtherAppsScreen() 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 97fafd2..9478685 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 @@ -41,6 +41,7 @@ object ManageDeviceManipulation { binding.hasManipulatedDeviceAdmin = device?.manipulationOfProtectionLevel ?: false binding.hasManipulatedUsageStatsAccess = device?.manipulationOfUsageStats ?: false binding.hasManipulatedNotificationAccess = device?.manipulationOfNotificationAccess ?: false + binding.hasManipulatedOverlayPermission = device?.manipulationOfOverlayPermission ?: false binding.hasManipulationReboot = device?.manipulationDidReboot ?: false binding.hasHadManipulation = (device?.hadManipulation ?: false) and (! (device?.hasActiveManipulationWarning ?: false)) binding.hasAnyManipulation = device?.hasAnyManipulation ?: false @@ -62,6 +63,7 @@ object ManageDeviceManipulation { binding.deviceAdminDisabledCheckbox, binding.usageAccessCheckbox, binding.notificationAccessCheckbox, + binding.overlayPermissionCheckbox, binding.rebootCheckbox, binding.hadManipulationCheckbox ) @@ -80,6 +82,7 @@ object ManageDeviceManipulation { ignoreNotificationAccessManipulation = binding.notificationAccessCheckbox.isChecked && binding.hasManipulatedNotificationAccess == true, ignoreDeviceAdminManipulationAttempt = binding.deviceAdminDisableAttemptCheckbox.isChecked && binding.hasTriedManipulatingDeviceAdmin == true, ignoreDeviceAdminManipulation = binding.deviceAdminDisabledCheckbox.isChecked && binding.hasManipulatedDeviceAdmin == true, + ignoreOverlayPermissionManipulation = binding.overlayPermissionCheckbox.isChecked && binding.hasManipulatedOverlayPermission == 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 20cb56f..219d479 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 @@ -35,6 +35,8 @@ import io.timelimit.android.integration.platform.android.AdminReceiver import io.timelimit.android.logic.AppLogic import io.timelimit.android.logic.DefaultAppLogic import io.timelimit.android.ui.manage.device.manage.InformAboutDeviceOwnerDialogFragment +import android.net.Uri + class SetupDevicePermissionsFragment : Fragment() { private val logic: AppLogic by lazy { DefaultAppLogic.with(context!!) } @@ -93,6 +95,14 @@ class SetupDevicePermissionsFragment : Fragment() { } } + override fun openDrawOverOtherAppsScreen() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + startActivity( + Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context!!.packageName)) + ) + } + } + override fun gotoNextStep() { navigation.safeNavigate( SetupDevicePermissionsFragmentDirections @@ -113,6 +123,7 @@ class SetupDevicePermissionsFragment : Fragment() { binding.notificationAccessPermission = platform.getNotificationAccessPermissionStatus() binding.protectionLevel = platform.getCurrentProtectionLevel() binding.usageStatsAccess = platform.getForegroundAppPermissionStatus() + binding.overlayPermission = platform.getOverlayPermissionStatus() } override fun onResume() { @@ -126,5 +137,6 @@ interface SetupDevicePermissionsHandlers { fun manageDeviceAdmin() fun openUsageStatsSettings() fun openNotificationAccessSettings() + fun openDrawOverOtherAppsScreen() fun gotoNextStep() } diff --git a/app/src/main/res/layout/blocking_overlay.xml b/app/src/main/res/layout/blocking_overlay.xml new file mode 100644 index 0000000..0bb1108 --- /dev/null +++ b/app/src/main/res/layout/blocking_overlay.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_manage_device.xml b/app/src/main/res/layout/fragment_manage_device.xml index 67f8c4e..3e4e55f 100644 --- a/app/src/main/res/layout/fragment_manage_device.xml +++ b/app/src/main/res/layout/fragment_manage_device.xml @@ -54,6 +54,10 @@ name="protectionLevel" type="io.timelimit.android.integration.platform.ProtectionLevel" /> + + @@ -390,6 +394,73 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -229,6 +233,65 @@ + + + + + + + + + + + + + + + + + + + @@ -107,6 +111,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + + Netzwerkzeit erforderlich nicht als aktuelles Gerät gewählt alle Benachrichtigungen werden blockiert + + + Öffnen des Sperrbildschirms fehlgeschlagen. + Versuche es, den Home- oder Anwendungslisten-Button zu verwenden, um diesen Bildschirm zu verlassen. + diff --git a/app/src/main/res/values-de/strings-manage-device-manipulation.xml b/app/src/main/res/values-de/strings-manage-device-manipulation.xml index 5498ce4..3eb12fa 100644 --- a/app/src/main/res/values-de/strings-manage-device-manipulation.xml +++ b/app/src/main/res/values-de/strings-manage-device-manipulation.xml @@ -22,6 +22,7 @@ Geräteadministrator-Deaktivierung wurde versucht Nutzungsdatenzugriff Benachrichtigungszugriff + Berechtigung zum Anzeigen über anderen Apps ä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 182e518..6c9851d 100644 --- a/app/src/main/res/values-de/strings-manage-device.xml +++ b/app/src/main/res/values-de/strings-manage-device.xml @@ -47,6 +47,11 @@ deren Benachrichtigungen zu sperren. + Über anderen Apps anzeigen + + Das Anzeigen über anderen Apps 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-lock.xml b/app/src/main/res/values/strings-lock.xml index 25e9ec2..d4a5304 100644 --- a/app/src/main/res/values/strings-lock.xml +++ b/app/src/main/res/values/strings-lock.xml @@ -87,4 +87,9 @@ missing network time device must be the current device all notifications are blocked + + + Failed to open the lock screen. + You can try using the home or overview button to leave this screen. + 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 b1a8a88..2b8d368 100644 --- a/app/src/main/res/values/strings-manage-device-manipulation.xml +++ b/app/src/main/res/values/strings-manage-device-manipulation.xml @@ -22,6 +22,7 @@ disabling devcie admin was attempted usage stats access notification access + draw over other Apps 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 1aaf939..68568ed 100644 --- a/app/src/main/res/values/strings-manage-device.xml +++ b/app/src/main/res/values/strings-manage-device.xml @@ -47,6 +47,11 @@ Notifications and their contents are not saved. + Draw over other Apps + + Enabling drawing on top of other Apps can improve blocking in some cases. + + Device admin The device admin permission was not granted.