diff --git a/app/schemas/io.timelimit.android.data.RoomDatabase/26.json b/app/schemas/io.timelimit.android.data.RoomDatabase/26.json new file mode 100644 index 0000000..90a4489 --- /dev/null +++ b/app/schemas/io.timelimit.android.data.RoomDatabase/26.json @@ -0,0 +1,828 @@ +{ + "formatVersion": 1, + "database": { + "version": 26, + "identityHash": "2a202b43acf918df8278ab09c67b5ddf", + "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, `blocked_times` TEXT 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 + }, + { + "fieldPath": "blockedTimes", + "columnName": "blocked_times", + "affinity": "TEXT", + "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, `had_manipulation_flags` 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, `enable_activity_level_blocking` INTEGER NOT NULL, `q_or_later` 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": "hadManipulationFlags", + "columnName": "had_manipulation_flags", + "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 + }, + { + "fieldPath": "enableActivityLevelBlocking", + "columnName": "enable_activity_level_blocking", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "qOrLater", + "columnName": "q_or_later", + "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 IF NOT EXISTS `index_app_device_id` ON `${TABLE_NAME}` (`device_id`)" + }, + { + "name": "index_app_package_name", + "unique": false, + "columnNames": [ + "package_name" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `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 IF NOT EXISTS `index_category_app_category_id` ON `${TABLE_NAME}` (`category_id`)" + }, + { + "name": "index_category_app_package_name", + "unique": false, + "columnNames": [ + "package_name" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `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, `temporarily_blocked_end_time` 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, `time_warnings` INTEGER NOT NULL, `min_battery_charging` INTEGER NOT NULL, `min_battery_mobile` INTEGER NOT NULL, `sort` 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": "temporarilyBlockedEndTime", + "columnName": "temporarily_blocked_end_time", + "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 + }, + { + "fieldPath": "timeWarnings", + "columnName": "time_warnings", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minBatteryLevelWhileCharging", + "columnName": "min_battery_charging", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minBatteryLevelMobile", + "columnName": "min_battery_mobile", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sort", + "columnName": "sort", + "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 IF NOT EXISTS `index_pending_sync_action_scheduled_for_upload` ON `${TABLE_NAME}` (`scheduled_for_upload`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "app_activity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`device_id` TEXT NOT NULL, `app_package_name` TEXT NOT NULL, `activity_class_name` TEXT NOT NULL, `activity_title` TEXT NOT NULL, PRIMARY KEY(`device_id`, `app_package_name`, `activity_class_name`))", + "fields": [ + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "appPackageName", + "columnName": "app_package_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "activityClassName", + "columnName": "activity_class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "activity_title", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "device_id", + "app_package_name", + "activity_class_name" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL, `id` TEXT NOT NULL, `first_notify_time` INTEGER NOT NULL, `dismissed` INTEGER NOT NULL, PRIMARY KEY(`type`, `id`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstNotifyTime", + "columnName": "first_notify_time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDismissed", + "columnName": "dismissed", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "type", + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "allowed_contact", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `phone` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phone", + "columnName": "phone", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "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, '2a202b43acf918df8278ab09c67b5ddf')" + ] + } +} \ No newline at end of file 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 c9faaeb..2b66cc1 100644 --- a/app/src/main/java/io/timelimit/android/data/Migrations.kt +++ b/app/src/main/java/io/timelimit/android/data/Migrations.kt @@ -178,4 +178,12 @@ object DatabaseMigrations { database.execSQL("ALTER TABLE `category` ADD COLUMN `sort` INTEGER NOT NULL DEFAULT 0") } } + + val MIGRATE_TO_V26 = object: Migration(25, 26) { + override fun migrate(database: SupportSQLiteDatabase) { + // this is empty + // + // a new possible enum value was added, the version upgrade enables the downgrade mechanism + } + } } 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 0e3a6a3..5c6932c 100644 --- a/app/src/main/java/io/timelimit/android/data/RoomDatabase.kt +++ b/app/src/main/java/io/timelimit/android/data/RoomDatabase.kt @@ -35,7 +35,7 @@ import io.timelimit.android.data.model.* AppActivity::class, Notification::class, AllowedContact::class -], version = 25) +], version = 26) abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database { companion object { private val lock = Object() @@ -94,7 +94,8 @@ abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database DatabaseMigrations.MIGRATE_TO_V22, DatabaseMigrations.MIGRATE_TO_V23, DatabaseMigrations.MIGRATE_TO_V24, - DatabaseMigrations.MIGRATE_TO_V25 + DatabaseMigrations.MIGRATE_TO_V25, + DatabaseMigrations.MIGRATE_TO_V26 ) .build() } diff --git a/app/src/main/java/io/timelimit/android/data/dao/ConfigDao.kt b/app/src/main/java/io/timelimit/android/data/dao/ConfigDao.kt index 840e99b..f69e340 100644 --- a/app/src/main/java/io/timelimit/android/data/dao/ConfigDao.kt +++ b/app/src/main/java/io/timelimit/android/data/dao/ConfigDao.kt @@ -268,4 +268,7 @@ abstract class ConfigDao { } fun setDefaultHomescreenSync(componentName: ComponentName?) = updateValueSync(ConfigurationItemType.DefaultHomescreen, componentName?.flattenToString()) + + fun getHomescreenDelaySync(): Int = getValueOfKeySync(ConfigurationItemType.HomescreenDelay)?.toIntOrNull(radix = 10) ?: 5 + fun setHomescreenDelaySync(delay: Int) = updateValueSync(ConfigurationItemType.HomescreenDelay, delay.toString(radix = 10)) } diff --git a/app/src/main/java/io/timelimit/android/data/model/ConfigurationItem.kt b/app/src/main/java/io/timelimit/android/data/model/ConfigurationItem.kt index b9d197e..07e2835 100644 --- a/app/src/main/java/io/timelimit/android/data/model/ConfigurationItem.kt +++ b/app/src/main/java/io/timelimit/android/data/model/ConfigurationItem.kt @@ -93,7 +93,8 @@ enum class ConfigurationItemType { EnableBackgroundSync, EnableAlternativeDurationSelection, ExperimentalFlags, - DefaultHomescreen + DefaultHomescreen, + HomescreenDelay } object ConfigurationItemTypeUtil { @@ -114,6 +115,7 @@ object ConfigurationItemTypeUtil { private const val ENABLE_ALTERNATIVE_DURATION_SELECTION = 16 private const val EXPERIMENTAL_FLAGS = 17 private const val DEFAULT_HOMESCREEN = 18 + private const val HOMESCREEN_DELAY = 19 val TYPES = listOf( ConfigurationItemType.OwnDeviceId, @@ -132,7 +134,8 @@ object ConfigurationItemTypeUtil { ConfigurationItemType.EnableBackgroundSync, ConfigurationItemType.EnableAlternativeDurationSelection, ConfigurationItemType.ExperimentalFlags, - ConfigurationItemType.DefaultHomescreen + ConfigurationItemType.DefaultHomescreen, + ConfigurationItemType.HomescreenDelay ) fun serialize(value: ConfigurationItemType) = when(value) { @@ -153,6 +156,7 @@ object ConfigurationItemTypeUtil { ConfigurationItemType.EnableAlternativeDurationSelection -> ENABLE_ALTERNATIVE_DURATION_SELECTION ConfigurationItemType.ExperimentalFlags -> EXPERIMENTAL_FLAGS ConfigurationItemType.DefaultHomescreen -> DEFAULT_HOMESCREEN + ConfigurationItemType.HomescreenDelay -> HOMESCREEN_DELAY } fun parse(value: Int) = when(value) { @@ -173,6 +177,7 @@ object ConfigurationItemTypeUtil { ENABLE_ALTERNATIVE_DURATION_SELECTION -> ConfigurationItemType.EnableAlternativeDurationSelection EXPERIMENTAL_FLAGS -> ConfigurationItemType.ExperimentalFlags DEFAULT_HOMESCREEN -> ConfigurationItemType.DefaultHomescreen + HOMESCREEN_DELAY -> ConfigurationItemType.HomescreenDelay else -> throw IllegalArgumentException() } } diff --git a/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExperimentalFlagFragment.kt b/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExperimentalFlagFragment.kt index 273125b..9824a83 100644 --- a/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExperimentalFlagFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/diagnose/DiagnoseExperimentalFlagFragment.kt @@ -21,6 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.CheckBox +import androidx.fragment.app.FragmentManager import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import io.timelimit.android.BuildConfig @@ -29,8 +30,10 @@ import io.timelimit.android.async.Threads import io.timelimit.android.data.Database import io.timelimit.android.data.model.ExperimentalFlags import io.timelimit.android.databinding.DiagnoseExperimentalFlagFragmentBinding +import io.timelimit.android.databinding.DiagnoseExperimentalFlagItemBinding import io.timelimit.android.livedata.liveDataFromValue import io.timelimit.android.logic.DefaultAppLogic +import io.timelimit.android.ui.homescreen.ConfigureHomescreenDelayDialogFragment import io.timelimit.android.ui.main.ActivityViewModelHolder import io.timelimit.android.ui.main.AuthenticationFab import io.timelimit.android.ui.main.FragmentWithCustomTitle @@ -54,23 +57,29 @@ class DiagnoseExperimentalFlagFragment : Fragment(), FragmentWithCustomTitle { binding.fab.setOnClickListener { activity.showAuthenticationScreen() } val flags = DiagnoseExperimentalFlagItem.items - val checkboxes = flags.map { - CheckBox(context).apply { - setText(it.label) + val checkboxes = flags.map { flag -> + DiagnoseExperimentalFlagItemBinding.inflate(LayoutInflater.from(context), binding.container, true).apply { + label = getString(flag.label) + + configButton.setOnClickListener { + if (auth.requestAuthenticationOrReturnTrue()) { + flag.configHook?.invoke(parentFragmentManager) + } + } } } - checkboxes.forEach { binding.container.addView(it) } - - database.config().experimentalFlags.observe(this, Observer { setFlags -> + database.config().experimentalFlags.observe(viewLifecycleOwner, Observer { setFlags -> flags.forEachIndexed { index, flag -> val checkbox = checkboxes[index] val isFlagSet = (setFlags and flag.enableFlags) == flag.enableFlags - checkbox.setOnCheckedChangeListener { _, _ -> } - checkbox.isChecked = isFlagSet - checkbox.isEnabled = flag.enable(setFlags) - checkbox.setOnCheckedChangeListener { _, didCheck -> + checkbox.enabled = flag.enable(setFlags) + checkbox.showConfigButton = isFlagSet && flag.configHook != null + + checkbox.checkbox.setOnCheckedChangeListener { _, _ -> } + checkbox.checkbox.isChecked = isFlagSet + checkbox.checkbox.setOnCheckedChangeListener { _, didCheck -> if (didCheck != isFlagSet) { if (auth.requestAuthenticationOrReturnTrue()) { Threads.database.execute { @@ -83,7 +92,7 @@ class DiagnoseExperimentalFlagFragment : Fragment(), FragmentWithCustomTitle { } } } else { - checkbox.isChecked = isFlagSet + checkbox.checkbox.isChecked = isFlagSet } } } @@ -101,7 +110,8 @@ data class DiagnoseExperimentalFlagItem( val enableFlags: Long, val disableFlags: Long, val enable: (flags: Long) -> Boolean, - val postEnableHook: ((Database) -> Unit)? = null + val postEnableHook: ((Database) -> Unit)? = null, + val configHook: ((FragmentManager) -> Unit)? = null ) { companion object { val items = listOf( @@ -142,13 +152,14 @@ data class DiagnoseExperimentalFlagItem( enableFlags = ExperimentalFlags.CUSTOM_HOME_SCREEN, disableFlags = ExperimentalFlags.CUSTOM_HOME_SCREEN or ExperimentalFlags.CUSTOM_HOMESCREEN_DELAY, enable = { true }, - postEnableHook = { it.config().setDefaultHomescreenSync(null) } + postEnableHook = { database -> database.config().setDefaultHomescreenSync(null) } ), DiagnoseExperimentalFlagItem( label = R.string.diagnose_exf_chd, enableFlags = ExperimentalFlags.CUSTOM_HOMESCREEN_DELAY, disableFlags = ExperimentalFlags.CUSTOM_HOMESCREEN_DELAY, - enable = { flags -> flags and ExperimentalFlags.CUSTOM_HOME_SCREEN == ExperimentalFlags.CUSTOM_HOME_SCREEN } + enable = { flags -> flags and ExperimentalFlags.CUSTOM_HOME_SCREEN == ExperimentalFlags.CUSTOM_HOME_SCREEN }, + configHook = { fragmentManager -> ConfigureHomescreenDelayDialogFragment().show(fragmentManager) } ) ) } diff --git a/app/src/main/java/io/timelimit/android/ui/homescreen/ConfigureHomescreenDelayDialogFragment.kt b/app/src/main/java/io/timelimit/android/ui/homescreen/ConfigureHomescreenDelayDialogFragment.kt new file mode 100644 index 0000000..101c16d --- /dev/null +++ b/app/src/main/java/io/timelimit/android/ui/homescreen/ConfigureHomescreenDelayDialogFragment.kt @@ -0,0 +1,100 @@ +/* + * TimeLimit Copyright 2019 - 2020 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.ui.homescreen + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Observer +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import io.timelimit.android.async.Threads +import io.timelimit.android.coroutines.executeAndWait +import io.timelimit.android.coroutines.runAsync +import io.timelimit.android.data.model.UserType +import io.timelimit.android.databinding.ConfigureHomescreenDelayDialogBinding +import io.timelimit.android.extensions.showSafe +import io.timelimit.android.ui.main.ActivityViewModelHolder + +class ConfigureHomescreenDelayDialogFragment: BottomSheetDialogFragment() { + companion object { + private const val DIALOG_TAG = "ConfigureHomescreenDelayDialogFragment" + private const val STATUS_DID_LOAD_VALUE = "didLoadValue" + private const val STATUS_CURRENT_VALUE = "currentValue" + } + + private lateinit var binding: ConfigureHomescreenDelayDialogBinding + private var didLoadValue = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (savedInstanceState != null) { + didLoadValue = savedInstanceState.getBoolean(STATUS_DID_LOAD_VALUE, false) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + + outState.putBoolean(STATUS_DID_LOAD_VALUE, didLoadValue) + outState.putInt(STATUS_CURRENT_VALUE, binding.numberpicker.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding = ConfigureHomescreenDelayDialogBinding.inflate(inflater, container, false) + val auth = (activity as ActivityViewModelHolder).getActivityViewModel() + + auth.authenticatedUser.observe(viewLifecycleOwner, Observer { + if (it?.second?.type != UserType.Parent) { + dismissAllowingStateLoss() + } + }) + + binding.numberpicker.apply { + minValue = 3 + maxValue = 300 + } + + if (savedInstanceState != null) { + binding.numberpicker.value = savedInstanceState.getInt(STATUS_CURRENT_VALUE) + } + + if (!didLoadValue) { + runAsync { + val value = Threads.database.executeAndWait { auth.logic.database.config().getHomescreenDelaySync() } + + binding.numberpicker.value = value + didLoadValue = true + } + } + + binding.saveButton.setOnClickListener { + if (auth.isParentAuthenticated()) { + Threads.database.execute { + auth.database.config().setHomescreenDelaySync(binding.numberpicker.value) + } + + dismiss() + } + } + + return binding.root + } + + fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG) +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/homescreen/HomescreenModel.kt b/app/src/main/java/io/timelimit/android/ui/homescreen/HomescreenModel.kt index 49cd1cb..2428904 100644 --- a/app/src/main/java/io/timelimit/android/ui/homescreen/HomescreenModel.kt +++ b/app/src/main/java/io/timelimit/android/ui/homescreen/HomescreenModel.kt @@ -29,8 +29,6 @@ import kotlinx.coroutines.delay class HomescreenModel(application: Application): AndroidViewModel(application) { companion object { - private const val DELAY = 5000L // 5 seconds - private const val COUNTER_DURATION = 1000 * 60L // 1 minute private const val COUNTER_MIN = 20 // 20 times @@ -64,18 +62,22 @@ class HomescreenModel(application: Application): AndroidViewModel(application) { isHandlingLaunch = true runAsync { - val enableDelay = Threads.database.executeAndWait { - logic.database.config().isExperimentalFlagsSetSync(ExperimentalFlags.CUSTOM_HOMESCREEN_DELAY) + val delay = Threads.database.executeAndWait { + if (logic.database.config().isExperimentalFlagsSetSync(ExperimentalFlags.CUSTOM_HOMESCREEN_DELAY)) { + logic.database.config().getHomescreenDelaySync() * 1000 + } else { + 0 + } } - if (enableDelay && (!hadFirstRunAfterLaunch)) { + if (delay > 0 && (!hadFirstRunAfterLaunch)) { val timeApi = logic.timeApi var start = timeApi.getCurrentUptimeInMillis() - var end = start + DELAY + var end = start + delay while (true) { val now = timeApi.getCurrentUptimeInMillis() - val progress = (now - start) * 100L / DELAY + val progress = (now - start) * 100L / delay if (now >= end) { break @@ -94,10 +96,10 @@ class HomescreenModel(application: Application): AndroidViewModel(application) { } val afterPause = timeApi.getCurrentUptimeInMillis() - val delay = afterPause - beforePause + val timeToAdd = afterPause - beforePause - start += delay - end += delay + start += timeToAdd + end += timeToAdd } } } diff --git a/app/src/main/res/layout/configure_homescreen_delay_dialog.xml b/app/src/main/res/layout/configure_homescreen_delay_dialog.xml new file mode 100644 index 0000000..56a8427 --- /dev/null +++ b/app/src/main/res/layout/configure_homescreen_delay_dialog.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + +