mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 17:59:51 +02:00
Adjust for GPlay policies related to the list of installed Apps
This commit is contained in:
parent
d16117f885
commit
fd5ffe73ce
21 changed files with 1911 additions and 261 deletions
1264
app/schemas/io.timelimit.android.data.RoomDatabase/41.json
Normal file
1264
app/schemas/io.timelimit.android.data.RoomDatabase/41.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -21,37 +21,37 @@ import io.timelimit.android.data.model.TimeLimitRule
|
||||||
import io.timelimit.android.extensions.MinuteOfDay
|
import io.timelimit.android.extensions.MinuteOfDay
|
||||||
|
|
||||||
object DatabaseMigrations {
|
object DatabaseMigrations {
|
||||||
val MIGRATE_TO_V2 = object: Migration(1, 2) {
|
private val MIGRATE_TO_V2 = object: Migration(1, 2) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE device ADD COLUMN did_report_uninstall INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE device ADD COLUMN did_report_uninstall INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V3 = object: Migration(2, 3) {
|
private val MIGRATE_TO_V3 = object: Migration(2, 3) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE device ADD COLUMN is_user_kept_signed_in INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE device ADD COLUMN is_user_kept_signed_in INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V4 = object: Migration(3, 4) {
|
private val MIGRATE_TO_V4 = object: Migration(3, 4) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `user` ADD COLUMN `category_for_not_assigned_apps` TEXT NOT NULL DEFAULT \"\"")
|
database.execSQL("ALTER TABLE `user` ADD COLUMN `category_for_not_assigned_apps` TEXT NOT NULL DEFAULT \"\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V5 = object: Migration(4, 5) {
|
private val MIGRATE_TO_V5 = object: Migration(4, 5) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `parent_category_id` TEXT NOT NULL DEFAULT \"\"")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `parent_category_id` TEXT NOT NULL DEFAULT \"\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V6 = object: Migration(5, 6) {
|
private val MIGRATE_TO_V6 = object: Migration(5, 6) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `device` ADD COLUMN `show_device_connected` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `show_device_connected` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V7 = object: Migration(6, 7) {
|
private val MIGRATE_TO_V7 = object: Migration(6, 7) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `device` ADD COLUMN `default_user` TEXT NOT NULL DEFAULT \"\"")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `default_user` TEXT NOT NULL DEFAULT \"\"")
|
||||||
database.execSQL("ALTER TABLE `device` ADD COLUMN `default_user_timeout` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `default_user_timeout` INTEGER NOT NULL DEFAULT 0")
|
||||||
|
@ -59,7 +59,7 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V8 = object: Migration(7, 8) {
|
private val MIGRATE_TO_V8 = object: Migration(7, 8) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// this is empty
|
// this is empty
|
||||||
//
|
//
|
||||||
|
@ -67,14 +67,14 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V9 = object: Migration(8, 9) {
|
private val MIGRATE_TO_V9 = object: Migration(8, 9) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `device` ADD COLUMN `did_reboot` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `did_reboot` INTEGER NOT NULL DEFAULT 0")
|
||||||
database.execSQL("ALTER TABLE `device` ADD COLUMN `consider_reboot_manipulation` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `consider_reboot_manipulation` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V10 = object: Migration(9, 10) {
|
private val MIGRATE_TO_V10 = object: Migration(9, 10) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// this is empty
|
// this is empty
|
||||||
//
|
//
|
||||||
|
@ -82,40 +82,40 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V11 = object: Migration(10, 11) {
|
private val MIGRATE_TO_V11 = object: Migration(10, 11) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `user` ADD COLUMN `mail_notification_flags` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `user` ADD COLUMN `mail_notification_flags` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V12 = object: Migration(11, 12) {
|
private val MIGRATE_TO_V12 = object: Migration(11, 12) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `block_all_notifications` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `block_all_notifications` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V13 = object: Migration(12, 13) {
|
private val MIGRATE_TO_V13 = object: Migration(12, 13) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
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 `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\"")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `highest_overlay_permission` TEXT NOT NULL DEFAULT \"not granted\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V14 = object: Migration(13, 14) {
|
private val MIGRATE_TO_V14 = object: Migration(13, 14) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `device` ADD COLUMN `current_accessibility_service_permission` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `current_accessibility_service_permission` INTEGER NOT NULL DEFAULT 0")
|
||||||
database.execSQL("ALTER TABLE `device` ADD COLUMN `was_accessibility_service_permission` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `was_accessibility_service_permission` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V15 = object: Migration(14, 15) {
|
private val MIGRATE_TO_V15 = object: Migration(14, 15) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("CREATE TABLE `app_activity` (`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`))")
|
database.execSQL("CREATE TABLE `app_activity` (`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`))")
|
||||||
database.execSQL("ALTER TABLE `device` ADD COLUMN `enable_activity_level_blocking` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `enable_activity_level_blocking` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V16 = object: Migration(15, 16) {
|
private val MIGRATE_TO_V16 = object: Migration(15, 16) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// this is empty
|
// this is empty
|
||||||
//
|
//
|
||||||
|
@ -123,20 +123,20 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V17 = object: Migration(16, 17) {
|
private val MIGRATE_TO_V17 = object: Migration(16, 17) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `notification` (`type` INTEGER NOT NULL, `id` TEXT NOT NULL, `first_notify_time` INTEGER NOT NULL, `dismissed` INTEGER NOT NULL, PRIMARY KEY(`type`, `id`))")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `notification` (`type` INTEGER NOT NULL, `id` TEXT NOT NULL, `first_notify_time` INTEGER NOT NULL, `dismissed` INTEGER NOT NULL, PRIMARY KEY(`type`, `id`))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V18 = object: Migration(17, 18) {
|
private val MIGRATE_TO_V18 = object: Migration(17, 18) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `device` ADD COLUMN `q_or_later` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `q_or_later` INTEGER NOT NULL DEFAULT 0")
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `time_warnings` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `time_warnings` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V19 = object: Migration(18, 19) {
|
private val MIGRATE_TO_V19 = object: Migration(18, 19) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// this is empty
|
// this is empty
|
||||||
//
|
//
|
||||||
|
@ -144,44 +144,44 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V20 = object: Migration(19, 20) {
|
private val MIGRATE_TO_V20 = object: Migration(19, 20) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `allowed_contact` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `phone` TEXT NOT NULL)")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `allowed_contact` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `phone` TEXT NOT NULL)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V21 = object: Migration(20, 21) {
|
private val MIGRATE_TO_V21 = object: Migration(20, 21) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `device` ADD COLUMN `had_manipulation_flags` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `had_manipulation_flags` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V22 = object: Migration(21, 22) {
|
private val MIGRATE_TO_V22 = object: Migration(21, 22) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `user` ADD COLUMN `blocked_times` TEXT NOT NULL DEFAULT \"\"")
|
database.execSQL("ALTER TABLE `user` ADD COLUMN `blocked_times` TEXT NOT NULL DEFAULT \"\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V23 = object: Migration(22, 23) {
|
private val MIGRATE_TO_V23 = object: Migration(22, 23) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `min_battery_charging` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `min_battery_charging` INTEGER NOT NULL DEFAULT 0")
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `min_battery_mobile` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `min_battery_mobile` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V24 = object: Migration(23, 24) {
|
private val MIGRATE_TO_V24 = object: Migration(23, 24) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `temporarily_blocked_end_time` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `temporarily_blocked_end_time` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V25 = object: Migration(24, 25) {
|
private val MIGRATE_TO_V25 = object: Migration(24, 25) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `sort` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `sort` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V26 = object: Migration(25, 26) {
|
private val MIGRATE_TO_V26 = object: Migration(25, 26) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// this is empty
|
// this is empty
|
||||||
//
|
//
|
||||||
|
@ -189,20 +189,20 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V27 = object: Migration(26, 27) {
|
private val MIGRATE_TO_V27 = object: Migration(26, 27) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `extra_time_day` INTEGER NOT NULL DEFAULT -1")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `extra_time_day` INTEGER NOT NULL DEFAULT -1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V28 = object: Migration(27, 28) {
|
private val MIGRATE_TO_V28 = object: Migration(27, 28) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `user_key` (`user_id` TEXT NOT NULL, `key` BLOB NOT NULL, `last_use` INTEGER NOT NULL, PRIMARY KEY(`user_id`), FOREIGN KEY(`user_id`) REFERENCES `user`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `user_key` (`user_id` TEXT NOT NULL, `key` BLOB NOT NULL, `last_use` INTEGER NOT NULL, PRIMARY KEY(`user_id`), FOREIGN KEY(`user_id`) REFERENCES `user`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )")
|
||||||
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_user_key_key` ON `user_key` (`key`)")
|
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_user_key_key` ON `user_key` (`key`)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V29 = object: Migration(28, 29) {
|
private val MIGRATE_TO_V29 = object: Migration(28, 29) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `start_minute_of_day` INTEGER NOT NULL DEFAULT ${TimeLimitRule.MIN_START_MINUTE}")
|
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `start_minute_of_day` INTEGER NOT NULL DEFAULT ${TimeLimitRule.MIN_START_MINUTE}")
|
||||||
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `end_minute_of_day` INTEGER NOT NULL DEFAULT ${TimeLimitRule.MAX_END_MINUTE}")
|
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `end_minute_of_day` INTEGER NOT NULL DEFAULT ${TimeLimitRule.MAX_END_MINUTE}")
|
||||||
|
@ -219,71 +219,120 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V30 = object: Migration(29, 30) {
|
private val MIGRATE_TO_V30 = object: Migration(29, 30) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `user` ADD COLUMN `flags` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `user` ADD COLUMN `flags` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V31 = object: Migration(30, 31) {
|
private val MIGRATE_TO_V31 = object: Migration(30, 31) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `user_limit_login_category` (`user_id` TEXT NOT NULL, `category_id` TEXT NOT NULL, PRIMARY KEY(`user_id`), FOREIGN KEY(`user_id`) REFERENCES `user`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `user_limit_login_category` (`user_id` TEXT NOT NULL, `category_id` TEXT NOT NULL, PRIMARY KEY(`user_id`), FOREIGN KEY(`user_id`) REFERENCES `user`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )")
|
||||||
database.execSQL("CREATE INDEX IF NOT EXISTS `user_limit_login_category_index_category_id` ON `user_limit_login_category` (`category_id`)")
|
database.execSQL("CREATE INDEX IF NOT EXISTS `user_limit_login_category_index_category_id` ON `user_limit_login_category` (`category_id`)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V32 = object: Migration(31, 32) {
|
private val MIGRATE_TO_V32 = object: Migration(31, 32) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `category_network_id` (`category_id` TEXT NOT NULL, `network_item_id` TEXT NOT NULL, `hashed_network_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `network_item_id`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `category_network_id` (`category_id` TEXT NOT NULL, `network_item_id` TEXT NOT NULL, `hashed_network_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `network_item_id`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V33 = object: Migration(32, 33) {
|
private val MIGRATE_TO_V33 = object: Migration(32, 33) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `disable_limits_until` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `disable_limits_until` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V34 = object: Migration(33, 34) {
|
private val MIGRATE_TO_V34 = object: Migration(33, 34) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `child_task` (`task_id` TEXT NOT NULL, `category_id` TEXT NOT NULL, `task_title` TEXT NOT NULL, `extra_time_duration` INTEGER NOT NULL, `pending_request` INTEGER NOT NULL, `last_grant_timestamp` INTEGER NOT NULL, PRIMARY KEY(`task_id`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `child_task` (`task_id` TEXT NOT NULL, `category_id` TEXT NOT NULL, `task_title` TEXT NOT NULL, `extra_time_duration` INTEGER NOT NULL, `pending_request` INTEGER NOT NULL, `last_grant_timestamp` INTEGER NOT NULL, PRIMARY KEY(`task_id`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )")
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `tasks_version` TEXT NOT NULL DEFAULT ''")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `tasks_version` TEXT NOT NULL DEFAULT ''")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V35 = object: Migration(34, 35) {
|
private val MIGRATE_TO_V35 = object: Migration(34, 35) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `per_day` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `per_day` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V36 = object: Migration(35, 36) {
|
private val MIGRATE_TO_V36 = object: Migration(35, 36) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `user_limit_login_category` ADD COLUMN pre_block_duration INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `user_limit_login_category` ADD COLUMN pre_block_duration INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V37 = object: Migration(36, 37) {
|
private val MIGRATE_TO_V37 = object: Migration(36, 37) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE `category` ADD COLUMN `flags` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `flags` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V38 = object: Migration(37, 38) {
|
private val MIGRATE_TO_V38 = object: Migration(37, 38) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("ALTER TABLE category ADD COLUMN block_notification_delay INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE category ADD COLUMN block_notification_delay INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V39 = object: Migration(38, 39) {
|
private val MIGRATE_TO_V39 = object: Migration(38, 39) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// nothing to do, there was just a new config item type added
|
// nothing to do, there was just a new config item type added
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V40 = object: Migration(39, 40) {
|
private val MIGRATE_TO_V40 = object: Migration(39, 40) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
database.execSQL("CREATE TABLE IF NOT EXISTS `category_time_warning` (`category_id` TEXT NOT NULL, `minutes` INTEGER NOT NULL, PRIMARY KEY(`category_id`, `minutes`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `category_time_warning` (`category_id` TEXT NOT NULL, `minutes` INTEGER NOT NULL, PRIMARY KEY(`category_id`, `minutes`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val MIGRATE_TO_V41 = object: Migration(40, 41) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
// nothing to do, there was just a new config item type added
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val ALL = arrayOf(
|
||||||
|
MIGRATE_TO_V2,
|
||||||
|
MIGRATE_TO_V3,
|
||||||
|
MIGRATE_TO_V4,
|
||||||
|
MIGRATE_TO_V5,
|
||||||
|
MIGRATE_TO_V6,
|
||||||
|
MIGRATE_TO_V7,
|
||||||
|
MIGRATE_TO_V8,
|
||||||
|
MIGRATE_TO_V9,
|
||||||
|
MIGRATE_TO_V10,
|
||||||
|
MIGRATE_TO_V11,
|
||||||
|
MIGRATE_TO_V12,
|
||||||
|
MIGRATE_TO_V13,
|
||||||
|
MIGRATE_TO_V14,
|
||||||
|
MIGRATE_TO_V15,
|
||||||
|
MIGRATE_TO_V16,
|
||||||
|
MIGRATE_TO_V17,
|
||||||
|
MIGRATE_TO_V18,
|
||||||
|
MIGRATE_TO_V19,
|
||||||
|
MIGRATE_TO_V20,
|
||||||
|
MIGRATE_TO_V21,
|
||||||
|
MIGRATE_TO_V22,
|
||||||
|
MIGRATE_TO_V23,
|
||||||
|
MIGRATE_TO_V24,
|
||||||
|
MIGRATE_TO_V25,
|
||||||
|
MIGRATE_TO_V26,
|
||||||
|
MIGRATE_TO_V27,
|
||||||
|
MIGRATE_TO_V28,
|
||||||
|
MIGRATE_TO_V29,
|
||||||
|
MIGRATE_TO_V30,
|
||||||
|
MIGRATE_TO_V31,
|
||||||
|
MIGRATE_TO_V32,
|
||||||
|
MIGRATE_TO_V33,
|
||||||
|
MIGRATE_TO_V34,
|
||||||
|
MIGRATE_TO_V35,
|
||||||
|
MIGRATE_TO_V36,
|
||||||
|
MIGRATE_TO_V37,
|
||||||
|
MIGRATE_TO_V38,
|
||||||
|
MIGRATE_TO_V39,
|
||||||
|
MIGRATE_TO_V40,
|
||||||
|
MIGRATE_TO_V41
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.room.Database
|
||||||
import androidx.room.InvalidationTracker
|
import androidx.room.InvalidationTracker
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.migration.Migration
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
import io.timelimit.android.async.Threads
|
import io.timelimit.android.async.Threads
|
||||||
import io.timelimit.android.data.dao.DerivedDataDao
|
import io.timelimit.android.data.dao.DerivedDataDao
|
||||||
|
@ -52,7 +53,7 @@ import java.util.concurrent.TimeUnit
|
||||||
CategoryNetworkId::class,
|
CategoryNetworkId::class,
|
||||||
ChildTask::class,
|
ChildTask::class,
|
||||||
CategoryTimeWarning::class
|
CategoryTimeWarning::class
|
||||||
], version = 40)
|
], version = 41)
|
||||||
abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database {
|
abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database {
|
||||||
companion object {
|
companion object {
|
||||||
private val lock = Object()
|
private val lock = Object()
|
||||||
|
@ -87,47 +88,7 @@ abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database
|
||||||
)
|
)
|
||||||
.setJournalMode(JournalMode.TRUNCATE)
|
.setJournalMode(JournalMode.TRUNCATE)
|
||||||
.fallbackToDestructiveMigrationOnDowngrade()
|
.fallbackToDestructiveMigrationOnDowngrade()
|
||||||
.addMigrations(
|
.addMigrations(*DatabaseMigrations.ALL)
|
||||||
DatabaseMigrations.MIGRATE_TO_V2,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V3,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V4,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V5,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V6,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V7,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V8,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V9,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V10,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V11,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V12,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V13,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V14,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V15,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V16,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V17,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V18,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V19,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V20,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V21,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V22,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V23,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V24,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V25,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V26,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V27,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V28,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V29,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V30,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V31,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V32,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V33,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V34,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V35,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V36,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V37,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V38,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V39,
|
|
||||||
DatabaseMigrations.MIGRATE_TO_V40
|
|
||||||
)
|
|
||||||
.setQueryExecutor(Threads.database)
|
.setQueryExecutor(Threads.database)
|
||||||
.addCallback(object: Callback() {
|
.addCallback(object: Callback() {
|
||||||
override fun onOpen(db: SupportSQLiteDatabase) {
|
override fun onOpen(db: SupportSQLiteDatabase) {
|
||||||
|
|
|
@ -330,4 +330,28 @@ abstract class ConfigDao {
|
||||||
|
|
||||||
fun getAnnoyManualUnblockCounter() = getValueOfKeySync(ConfigurationItemType.AnnoyManualUnblockCounter).let { it?.toInt() ?: 0 }
|
fun getAnnoyManualUnblockCounter() = getValueOfKeySync(ConfigurationItemType.AnnoyManualUnblockCounter).let { it?.toInt() ?: 0 }
|
||||||
fun setAnoyManualUnblockCounterSync(counter: Int) { updateValueSync(ConfigurationItemType.AnnoyManualUnblockCounter, counter.toString()) }
|
fun setAnoyManualUnblockCounterSync(counter: Int) { updateValueSync(ConfigurationItemType.AnnoyManualUnblockCounter, counter.toString()) }
|
||||||
|
|
||||||
|
private val consentFlags: LiveData<Long> by lazy {
|
||||||
|
getValueOfKeyAsync(ConfigurationItemType.ConsentFlags).map {
|
||||||
|
it?.toLong(16) ?: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getConsentFlagsSync(): Long = getValueOfKeySync(ConfigurationItemType.ConsentFlags).let {
|
||||||
|
it?.toLong(16) ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isConsentFlagSetAsync(flags: Long) = consentFlags.map {
|
||||||
|
(it and flags) == flags
|
||||||
|
}.ignoreUnchanged()
|
||||||
|
|
||||||
|
fun setConsentFlagSync(flags: Long, enable: Boolean) {
|
||||||
|
updateValueSync(
|
||||||
|
ConfigurationItemType.ConsentFlags,
|
||||||
|
if (enable)
|
||||||
|
(getConsentFlagsSync() or flags).toString(16)
|
||||||
|
else
|
||||||
|
(getConsentFlagsSync() and (flags.inv())).toString(16)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,7 @@ enum class ConfigurationItemType {
|
||||||
CustomOrganizationName,
|
CustomOrganizationName,
|
||||||
ServerApiLevel,
|
ServerApiLevel,
|
||||||
AnnoyManualUnblockCounter,
|
AnnoyManualUnblockCounter,
|
||||||
|
ConsentFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
object ConfigurationItemTypeUtil {
|
object ConfigurationItemTypeUtil {
|
||||||
|
@ -128,6 +129,7 @@ object ConfigurationItemTypeUtil {
|
||||||
private const val CUSTOM_ORGANIZATION_NAME = 23
|
private const val CUSTOM_ORGANIZATION_NAME = 23
|
||||||
private const val SERVER_API_LEVEL = 24
|
private const val SERVER_API_LEVEL = 24
|
||||||
private const val ANNOY_MANUAL_UNBLOCK_COUNTER = 25
|
private const val ANNOY_MANUAL_UNBLOCK_COUNTER = 25
|
||||||
|
private const val CONSENT_FLAGS = 26
|
||||||
|
|
||||||
val TYPES = listOf(
|
val TYPES = listOf(
|
||||||
ConfigurationItemType.OwnDeviceId,
|
ConfigurationItemType.OwnDeviceId,
|
||||||
|
@ -153,7 +155,8 @@ object ConfigurationItemTypeUtil {
|
||||||
ConfigurationItemType.UpdateStatus,
|
ConfigurationItemType.UpdateStatus,
|
||||||
ConfigurationItemType.CustomOrganizationName,
|
ConfigurationItemType.CustomOrganizationName,
|
||||||
ConfigurationItemType.ServerApiLevel,
|
ConfigurationItemType.ServerApiLevel,
|
||||||
ConfigurationItemType.AnnoyManualUnblockCounter
|
ConfigurationItemType.AnnoyManualUnblockCounter,
|
||||||
|
ConfigurationItemType.ConsentFlags
|
||||||
)
|
)
|
||||||
|
|
||||||
fun serialize(value: ConfigurationItemType) = when(value) {
|
fun serialize(value: ConfigurationItemType) = when(value) {
|
||||||
|
@ -181,6 +184,7 @@ object ConfigurationItemTypeUtil {
|
||||||
ConfigurationItemType.CustomOrganizationName -> CUSTOM_ORGANIZATION_NAME
|
ConfigurationItemType.CustomOrganizationName -> CUSTOM_ORGANIZATION_NAME
|
||||||
ConfigurationItemType.ServerApiLevel -> SERVER_API_LEVEL
|
ConfigurationItemType.ServerApiLevel -> SERVER_API_LEVEL
|
||||||
ConfigurationItemType.AnnoyManualUnblockCounter -> ANNOY_MANUAL_UNBLOCK_COUNTER
|
ConfigurationItemType.AnnoyManualUnblockCounter -> ANNOY_MANUAL_UNBLOCK_COUNTER
|
||||||
|
ConfigurationItemType.ConsentFlags -> CONSENT_FLAGS
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parse(value: Int) = when(value) {
|
fun parse(value: Int) = when(value) {
|
||||||
|
@ -208,6 +212,7 @@ object ConfigurationItemTypeUtil {
|
||||||
CUSTOM_ORGANIZATION_NAME -> ConfigurationItemType.CustomOrganizationName
|
CUSTOM_ORGANIZATION_NAME -> ConfigurationItemType.CustomOrganizationName
|
||||||
SERVER_API_LEVEL -> ConfigurationItemType.ServerApiLevel
|
SERVER_API_LEVEL -> ConfigurationItemType.ServerApiLevel
|
||||||
ANNOY_MANUAL_UNBLOCK_COUNTER -> ConfigurationItemType.AnnoyManualUnblockCounter
|
ANNOY_MANUAL_UNBLOCK_COUNTER -> ConfigurationItemType.AnnoyManualUnblockCounter
|
||||||
|
CONSENT_FLAGS -> ConfigurationItemType.ConsentFlags
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,3 +257,7 @@ object ExperimentalFlags {
|
||||||
// private const val OBSOLETE_DISABLE_FG_APP_DETECTION_FALLBACK = 131072L
|
// private const val OBSOLETE_DISABLE_FG_APP_DETECTION_FALLBACK = 131072L
|
||||||
const val STRICT_OVERLAY_CHECKING = 0x40000L
|
const val STRICT_OVERLAY_CHECKING = 0x40000L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object ConsentFlags {
|
||||||
|
const val APP_LIST_SYNC = 1L
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -28,7 +28,8 @@ data class DeviceRelatedData (
|
||||||
val isLocalMode: Boolean,
|
val isLocalMode: Boolean,
|
||||||
val hasValidDefaultUser: Boolean,
|
val hasValidDefaultUser: Boolean,
|
||||||
val temporarilyAllowedApps: Set<String>,
|
val temporarilyAllowedApps: Set<String>,
|
||||||
val experimentalFlags: Long
|
val experimentalFlags: Long,
|
||||||
|
val consentFlags: Long
|
||||||
): Observer {
|
): Observer {
|
||||||
companion object {
|
companion object {
|
||||||
private val relatedTables = arrayOf(Table.ConfigurationItem, Table.Device, Table.User, Table.TemporarilyAllowedApp)
|
private val relatedTables = arrayOf(Table.ConfigurationItem, Table.Device, Table.User, Table.TemporarilyAllowedApp)
|
||||||
|
@ -41,6 +42,7 @@ data class DeviceRelatedData (
|
||||||
val hasValidDefaultUser = database.user().getUserByIdSync(deviceEntry.defaultUser) != null
|
val hasValidDefaultUser = database.user().getUserByIdSync(deviceEntry.defaultUser) != null
|
||||||
val temporarilyAllowedApps = database.temporarilyAllowedApp().getTemporarilyAllowedAppsSync().toSet()
|
val temporarilyAllowedApps = database.temporarilyAllowedApp().getTemporarilyAllowedAppsSync().toSet()
|
||||||
val experimentalFlags = database.config().getExperimentalFlagsSync()
|
val experimentalFlags = database.config().getExperimentalFlagsSync()
|
||||||
|
val consentFlags = database.config().getConsentFlagsSync()
|
||||||
|
|
||||||
DeviceRelatedData(
|
DeviceRelatedData(
|
||||||
deviceEntry = deviceEntry,
|
deviceEntry = deviceEntry,
|
||||||
|
@ -48,7 +50,8 @@ data class DeviceRelatedData (
|
||||||
isLocalMode = isLocalMode,
|
isLocalMode = isLocalMode,
|
||||||
hasValidDefaultUser = hasValidDefaultUser,
|
hasValidDefaultUser = hasValidDefaultUser,
|
||||||
temporarilyAllowedApps = temporarilyAllowedApps,
|
temporarilyAllowedApps = temporarilyAllowedApps,
|
||||||
experimentalFlags = experimentalFlags
|
experimentalFlags = experimentalFlags,
|
||||||
|
consentFlags = consentFlags
|
||||||
).also {
|
).also {
|
||||||
database.registerWeakObserver(relatedTables, WeakReference(it))
|
database.registerWeakObserver(relatedTables, WeakReference(it))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -238,3 +238,48 @@ fun <T1, T2, T3, T4> mergeLiveDataWaitForValues(d1: LiveData<T1>, d2: LiveData<T
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T1, T2, T3, T4, T5> mergeLiveDataWaitForValues(d1: LiveData<T1>, d2: LiveData<T2>, d3: LiveData<T3>, d4: LiveData<T4>, d5: LiveData<T5>): LiveData<FiveTuple<T1, T2, T3, T4, T5>> {
|
||||||
|
val result = MediatorLiveData<FiveTuple<T1, T2, T3, T4, T5>>()
|
||||||
|
var state = FiveTuple<Option<T1>, Option<T2>, Option<T3>, Option<T4>, Option<T5>>(Option.None(), Option.None(), Option.None(), Option.None(), Option.None())
|
||||||
|
|
||||||
|
fun update() {
|
||||||
|
val (a, b, c, d, e) = state
|
||||||
|
|
||||||
|
if (a is Option.Some && b is Option.Some && c is Option.Some && d is Option.Some && e is Option.Some) {
|
||||||
|
result.value = FiveTuple(a.value, b.value, c.value, d.value, e.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.addSource(d1) {
|
||||||
|
state = state.copy(first = Option.Some(it))
|
||||||
|
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
result.addSource(d2) {
|
||||||
|
state = state.copy(second = Option.Some(it))
|
||||||
|
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
result.addSource(d3) {
|
||||||
|
state = state.copy(third = Option.Some(it))
|
||||||
|
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
result.addSource(d4) {
|
||||||
|
state = state.copy(forth = Option.Some(it))
|
||||||
|
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
result.addSource(d5) {
|
||||||
|
state = state.copy(fifth = Option.Some(it))
|
||||||
|
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -54,7 +54,7 @@ class AppLogic(
|
||||||
}
|
}
|
||||||
}.ignoreUnchanged()
|
}.ignoreUnchanged()
|
||||||
|
|
||||||
val deviceEntryIfEnabled = enable.switchMap {
|
val deviceEntryIfEnabled: LiveData<Device?> = enable.switchMap {
|
||||||
if (it == null || it == false) {
|
if (it == null || it == false) {
|
||||||
liveDataFromNullableValue(null as Device?)
|
liveDataFromNullableValue(null as Device?)
|
||||||
} else {
|
} else {
|
||||||
|
@ -95,8 +95,9 @@ class AppLogic(
|
||||||
websocketClientCreator = websocketClientCreator
|
websocketClientCreator = websocketClientCreator
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val syncAppsLogic = SyncInstalledAppsLogic(this)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
SyncInstalledAppsLogic(this)
|
|
||||||
WatchdogLogic(this)
|
WatchdogLogic(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import io.timelimit.android.coroutines.executeAndWait
|
||||||
import io.timelimit.android.coroutines.runAsyncExpectForever
|
import io.timelimit.android.coroutines.runAsyncExpectForever
|
||||||
import io.timelimit.android.data.model.App
|
import io.timelimit.android.data.model.App
|
||||||
import io.timelimit.android.data.model.AppActivity
|
import io.timelimit.android.data.model.AppActivity
|
||||||
|
import io.timelimit.android.data.model.ConsentFlags
|
||||||
import io.timelimit.android.data.model.UserType
|
import io.timelimit.android.data.model.UserType
|
||||||
import io.timelimit.android.integration.platform.ProtectionLevel
|
import io.timelimit.android.integration.platform.ProtectionLevel
|
||||||
import io.timelimit.android.livedata.*
|
import io.timelimit.android.livedata.*
|
||||||
|
@ -45,17 +46,53 @@ class SyncInstalledAppsLogic(val appLogic: AppLogic) {
|
||||||
requestSync.value = true
|
requestSync.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val deviceStateLive = mergeLiveDataWaitForValues(
|
||||||
|
appLogic.deviceEntryIfEnabled,
|
||||||
|
appLogic.database.config().isConsentFlagSetAsync(ConsentFlags.APP_LIST_SYNC),
|
||||||
|
appLogic.database.config().getDeviceAuthTokenAsync().map { it.isEmpty() },
|
||||||
|
appLogic.deviceUserEntry,
|
||||||
|
appLogic.deviceEntryIfEnabled.switchMap { deviceEntry ->
|
||||||
|
val defaultUser = deviceEntry?.defaultUser
|
||||||
|
|
||||||
|
if (defaultUser.isNullOrEmpty()) liveDataFromNullableValue(null)
|
||||||
|
else appLogic.database.user().getUserByIdLive(defaultUser)
|
||||||
|
}
|
||||||
|
).map { (deviceEntry, hasSyncConsent, isLocalMode, deviceUser, deviceDefaultUser) ->
|
||||||
|
deviceEntry?.let { device ->
|
||||||
|
DeviceState(
|
||||||
|
id = device.id,
|
||||||
|
isCurrentUserChild = deviceUser?.type == UserType.Child,
|
||||||
|
isDefaultUserChild = deviceDefaultUser?.type == UserType.Child,
|
||||||
|
enableActivityLevelBlocking = device.enableActivityLevelBlocking,
|
||||||
|
isDeviceOwner = device.currentProtectionLevel == ProtectionLevel.DeviceOwner,
|
||||||
|
hasSyncConsent = hasSyncConsent,
|
||||||
|
isLocalMode = isLocalMode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.ignoreUnchanged()
|
||||||
|
|
||||||
|
val shouldAskForConsent = deviceStateLive.map { it?.shouldAskForConsent ?: false }.ignoreUnchanged()
|
||||||
|
|
||||||
|
private fun getDeviceStateSync(): DeviceState? {
|
||||||
|
val userAndDeviceData = appLogic.database.derivedDataDao().getUserAndDeviceRelatedDataSync() ?: return null
|
||||||
|
val deviceRelatedData = userAndDeviceData.deviceRelatedData
|
||||||
|
val device = deviceRelatedData.deviceEntry
|
||||||
|
val defaultUser = if (device.defaultUser.isNotEmpty()) appLogic.database.user().getUserByIdSync(device.defaultUser) else null
|
||||||
|
|
||||||
|
return DeviceState(
|
||||||
|
id = device.id,
|
||||||
|
isCurrentUserChild = userAndDeviceData.userRelatedData?.user?.type == UserType.Child,
|
||||||
|
isDefaultUserChild = defaultUser?.type == UserType.Child,
|
||||||
|
enableActivityLevelBlocking = device.enableActivityLevelBlocking,
|
||||||
|
isDeviceOwner = device.currentProtectionLevel == ProtectionLevel.DeviceOwner,
|
||||||
|
hasSyncConsent = deviceRelatedData.consentFlags and ConsentFlags.APP_LIST_SYNC == ConsentFlags.APP_LIST_SYNC,
|
||||||
|
isLocalMode = deviceRelatedData.isLocalMode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
appLogic.platformIntegration.installedAppsChangeListener = Runnable { requestSync() }
|
appLogic.platformIntegration.installedAppsChangeListener = Runnable { requestSync() }
|
||||||
appLogic.deviceEntryIfEnabled.map { device ->
|
deviceStateLive.observeForever { requestSync() }
|
||||||
device?.let { DeviceState(
|
|
||||||
id = device.id,
|
|
||||||
currentUserId = device.currentUserId,
|
|
||||||
defaultUser = device.defaultUser,
|
|
||||||
enableActivityLevelBlocking = device.enableActivityLevelBlocking,
|
|
||||||
isDeviceOwner = device.currentProtectionLevel == ProtectionLevel.DeviceOwner
|
|
||||||
) }
|
|
||||||
}.ignoreUnchanged().observeForever { requestSync() }
|
|
||||||
|
|
||||||
runAsyncExpectForever { syncLoop() }
|
runAsyncExpectForever { syncLoop() }
|
||||||
}
|
}
|
||||||
|
@ -88,26 +125,17 @@ class SyncInstalledAppsLogic(val appLogic: AppLogic) {
|
||||||
|
|
||||||
private suspend fun doSyncNow() {
|
private suspend fun doSyncNow() {
|
||||||
doSyncLock.withLock {
|
doSyncLock.withLock {
|
||||||
val deviceEntry = appLogic.deviceEntryIfEnabled.waitForNullableValue()
|
val deviceState = Threads.database.executeAndWait { getDeviceStateSync() } ?: return
|
||||||
|
|
||||||
if (deviceEntry == null) {
|
if (deviceState.isLocalMode) {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appLogic.database.config().getDeviceAuthTokenAsync().waitForNullableValue().isNullOrEmpty()) {
|
|
||||||
// local mode -> sync always
|
// local mode -> sync always
|
||||||
} else {
|
} else {
|
||||||
// connected mode -> don't sync always
|
// connected mode -> don't sync always
|
||||||
|
if (!deviceState.hasSyncConsent) return@withLock
|
||||||
val userEntry = appLogic.deviceUserEntry.waitForNullableValue()
|
if (!deviceState.hasAnyChildUser) return@withLock
|
||||||
val defaultUserEntry = appLogic.database.user().getUserByIdLive(deviceEntry.defaultUser).waitForNullableValue()
|
|
||||||
|
|
||||||
if (userEntry?.type != UserType.Child && defaultUserEntry?.type != UserType.Child) {
|
|
||||||
return@withLock
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val deviceId = deviceEntry.id
|
val deviceId = deviceState.id
|
||||||
|
|
||||||
val currentlyInstalledApps = getCurrentApps(deviceId)
|
val currentlyInstalledApps = getCurrentApps(deviceId)
|
||||||
|
|
||||||
|
@ -153,7 +181,7 @@ class SyncInstalledAppsLogic(val appLogic: AppLogic) {
|
||||||
run {
|
run {
|
||||||
fun buildKey(activity: AppActivity) = "${activity.appPackageName}:${activity.activityClassName}"
|
fun buildKey(activity: AppActivity) = "${activity.appPackageName}:${activity.activityClassName}"
|
||||||
|
|
||||||
val currentlyInstalled = if (deviceEntry.enableActivityLevelBlocking)
|
val currentlyInstalled = if (deviceState.enableActivityLevelBlocking)
|
||||||
Threads.backgroundOSInteraction.executeAndWait {
|
Threads.backgroundOSInteraction.executeAndWait {
|
||||||
val realActivities = appLogic.platformIntegration.getLocalAppActivities(deviceId = deviceId)
|
val realActivities = appLogic.platformIntegration.getLocalAppActivities(deviceId = deviceId)
|
||||||
val dummyActivities = currentlyInstalledApps.keys.map { packageName ->
|
val dummyActivities = currentlyInstalledApps.keys.map { packageName ->
|
||||||
|
@ -220,9 +248,14 @@ class SyncInstalledAppsLogic(val appLogic: AppLogic) {
|
||||||
|
|
||||||
internal data class DeviceState(
|
internal data class DeviceState(
|
||||||
val id: String,
|
val id: String,
|
||||||
val currentUserId: String,
|
val isCurrentUserChild: Boolean,
|
||||||
val defaultUser: String,
|
val isDefaultUserChild: Boolean,
|
||||||
val enableActivityLevelBlocking: Boolean,
|
val enableActivityLevelBlocking: Boolean,
|
||||||
val isDeviceOwner: Boolean
|
val isDeviceOwner: Boolean,
|
||||||
)
|
val hasSyncConsent: Boolean,
|
||||||
|
val isLocalMode: Boolean
|
||||||
|
) {
|
||||||
|
val hasAnyChildUser = isCurrentUserChild || isDefaultUserChild
|
||||||
|
val shouldAskForConsent = hasAnyChildUser && !isLocalMode && !hasSyncConsent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package io.timelimit.android.ui.consent
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import io.timelimit.android.BuildConfig
|
||||||
|
import io.timelimit.android.R
|
||||||
|
import io.timelimit.android.async.Threads
|
||||||
|
import io.timelimit.android.data.model.ConsentFlags
|
||||||
|
import io.timelimit.android.extensions.showSafe
|
||||||
|
import io.timelimit.android.logic.DefaultAppLogic
|
||||||
|
|
||||||
|
class SyncAppListConsentDialogFragment: DialogFragment() {
|
||||||
|
companion object {
|
||||||
|
private const val DIALOG_TAG = "SyncAppListConsentDialogFragment"
|
||||||
|
private const val LOG_TAG = "SyncAppListConsent"
|
||||||
|
|
||||||
|
fun newInstance() = SyncAppListConsentDialogFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = AlertDialog.Builder(requireContext(), theme)
|
||||||
|
.setTitle(R.string.consent_app_list_sync_dialog_title)
|
||||||
|
.setMessage(R.string.consent_app_list_sync_dialog_text)
|
||||||
|
.setNegativeButton(R.string.generic_reject, null)
|
||||||
|
.setPositiveButton(R.string.generic_accept) { _, _ ->
|
||||||
|
val database = DefaultAppLogic.with(requireContext()).database
|
||||||
|
|
||||||
|
Threads.database.execute {
|
||||||
|
try {
|
||||||
|
database.config().setConsentFlagSync(ConsentFlags.APP_LIST_SYNC, true)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.w(LOG_TAG, "Could not save consent", ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
|
||||||
|
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -23,6 +23,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
import io.timelimit.android.data.model.Category
|
import io.timelimit.android.data.model.Category
|
||||||
import io.timelimit.android.databinding.AddItemViewBinding
|
import io.timelimit.android.databinding.AddItemViewBinding
|
||||||
|
import io.timelimit.android.databinding.AppListSyncPermissionRequestCardBinding
|
||||||
import io.timelimit.android.databinding.CategoryRichCardBinding
|
import io.timelimit.android.databinding.CategoryRichCardBinding
|
||||||
import io.timelimit.android.databinding.IntroCardBinding
|
import io.timelimit.android.databinding.IntroCardBinding
|
||||||
import io.timelimit.android.ui.util.DateUtil
|
import io.timelimit.android.ui.util.DateUtil
|
||||||
|
@ -35,6 +36,7 @@ class Adapter: RecyclerView.Adapter<ViewHolder>() {
|
||||||
private const val TYPE_ADD = 1
|
private const val TYPE_ADD = 1
|
||||||
private const val TYPE_INTRO = 2
|
private const val TYPE_INTRO = 2
|
||||||
private const val TYPE_MANIPULATION_WARNING = 3
|
private const val TYPE_MANIPULATION_WARNING = 3
|
||||||
|
private const val TYPE_APP_LIST_BANNER = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
var categories: List<ManageChildCategoriesListItem>? by Delegates.observable(null as List<ManageChildCategoriesListItem>?) { _, _, _ -> notifyDataSetChanged() }
|
var categories: List<ManageChildCategoriesListItem>? by Delegates.observable(null as List<ManageChildCategoriesListItem>?) { _, _, _ -> notifyDataSetChanged() }
|
||||||
|
@ -53,6 +55,7 @@ class Adapter: RecyclerView.Adapter<ViewHolder>() {
|
||||||
CreateCategoryItem -> item.hashCode()
|
CreateCategoryItem -> item.hashCode()
|
||||||
CategoriesIntroductionHeader -> item.hashCode()
|
CategoriesIntroductionHeader -> item.hashCode()
|
||||||
ManipulationWarningCategoryItem -> item.hashCode()
|
ManipulationWarningCategoryItem -> item.hashCode()
|
||||||
|
ManageChildCategoriesListItem.SyncAppListBanner -> item.hashCode()
|
||||||
}.toLong()
|
}.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +64,7 @@ class Adapter: RecyclerView.Adapter<ViewHolder>() {
|
||||||
CreateCategoryItem -> TYPE_ADD
|
CreateCategoryItem -> TYPE_ADD
|
||||||
CategoriesIntroductionHeader -> TYPE_INTRO
|
CategoriesIntroductionHeader -> TYPE_INTRO
|
||||||
ManipulationWarningCategoryItem -> TYPE_MANIPULATION_WARNING
|
ManipulationWarningCategoryItem -> TYPE_MANIPULATION_WARNING
|
||||||
|
ManageChildCategoriesListItem.SyncAppListBanner -> TYPE_APP_LIST_BANNER
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = categories?.size ?: 0
|
override fun getItemCount() = categories?.size ?: 0
|
||||||
|
@ -104,6 +108,13 @@ class Adapter: RecyclerView.Adapter<ViewHolder>() {
|
||||||
.inflate(R.layout.manage_child_manipulation_warning, parent, false)
|
.inflate(R.layout.manage_child_manipulation_warning, parent, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TYPE_APP_LIST_BANNER ->
|
||||||
|
SyncAppListViewHolder(
|
||||||
|
AppListSyncPermissionRequestCardBinding.inflate(LayoutInflater.from(parent.context), parent, false).also {
|
||||||
|
it.detailButton.setOnClickListener { handlers?.onRequestAppListSyncConsentClicked() }
|
||||||
|
}.root
|
||||||
|
)
|
||||||
|
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +174,9 @@ class Adapter: RecyclerView.Adapter<ViewHolder>() {
|
||||||
ManipulationWarningCategoryItem -> {
|
ManipulationWarningCategoryItem -> {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
ManageChildCategoriesListItem.SyncAppListBanner -> {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
}.let { }
|
}.let { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,10 +185,12 @@ sealed class ViewHolder(view: View): RecyclerView.ViewHolder(view)
|
||||||
class AddViewHolder(view: View): ViewHolder(view)
|
class AddViewHolder(view: View): ViewHolder(view)
|
||||||
class IntroViewHolder(view: View): ViewHolder(view)
|
class IntroViewHolder(view: View): ViewHolder(view)
|
||||||
class ManipulationWarningViewHolder(view: View): ViewHolder(view)
|
class ManipulationWarningViewHolder(view: View): ViewHolder(view)
|
||||||
|
class SyncAppListViewHolder(view: View): ViewHolder(view)
|
||||||
class ItemViewHolder(val binding: CategoryRichCardBinding): ViewHolder(binding.root)
|
class ItemViewHolder(val binding: CategoryRichCardBinding): ViewHolder(binding.root)
|
||||||
|
|
||||||
interface Handlers {
|
interface Handlers {
|
||||||
fun onCategoryClicked(category: Category)
|
fun onCategoryClicked(category: Category)
|
||||||
fun onCreateCategoryClicked()
|
fun onCreateCategoryClicked()
|
||||||
fun onCategorySwitched(category: CategoryItem, isChecked: Boolean): Boolean
|
fun onCategorySwitched(category: CategoryItem, isChecked: Boolean): Boolean
|
||||||
|
fun onRequestAppListSyncConsentClicked()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,7 +17,10 @@ package io.timelimit.android.ui.manage.child.category
|
||||||
|
|
||||||
import io.timelimit.android.data.model.Category
|
import io.timelimit.android.data.model.Category
|
||||||
|
|
||||||
sealed class ManageChildCategoriesListItem
|
sealed class ManageChildCategoriesListItem {
|
||||||
|
object SyncAppListBanner: ManageChildCategoriesListItem()
|
||||||
|
}
|
||||||
|
|
||||||
object CategoriesIntroductionHeader: ManageChildCategoriesListItem()
|
object CategoriesIntroductionHeader: ManageChildCategoriesListItem()
|
||||||
object CreateCategoryItem: ManageChildCategoriesListItem()
|
object CreateCategoryItem: ManageChildCategoriesListItem()
|
||||||
object ManipulationWarningCategoryItem: ManageChildCategoriesListItem()
|
object ManipulationWarningCategoryItem: ManageChildCategoriesListItem()
|
||||||
|
|
|
@ -37,6 +37,7 @@ import io.timelimit.android.logic.DefaultAppLogic
|
||||||
import io.timelimit.android.sync.actions.UpdateCategoryDisableLimitsAction
|
import io.timelimit.android.sync.actions.UpdateCategoryDisableLimitsAction
|
||||||
import io.timelimit.android.sync.actions.UpdateCategorySortingAction
|
import io.timelimit.android.sync.actions.UpdateCategorySortingAction
|
||||||
import io.timelimit.android.sync.actions.UpdateCategoryTemporarilyBlockedAction
|
import io.timelimit.android.sync.actions.UpdateCategoryTemporarilyBlockedAction
|
||||||
|
import io.timelimit.android.ui.consent.SyncAppListConsentDialogFragment
|
||||||
import io.timelimit.android.ui.main.ActivityViewModel
|
import io.timelimit.android.ui.main.ActivityViewModel
|
||||||
import io.timelimit.android.ui.main.getActivityViewModel
|
import io.timelimit.android.ui.main.getActivityViewModel
|
||||||
import io.timelimit.android.ui.manage.child.ManageChildFragmentArgs
|
import io.timelimit.android.ui.manage.child.ManageChildFragmentArgs
|
||||||
|
@ -58,7 +59,7 @@ class ManageChildCategoriesFragment : Fragment() {
|
||||||
private val model: ManageChildCategoriesModel by viewModels()
|
private val model: ManageChildCategoriesModel by viewModels()
|
||||||
private lateinit var binding: RecyclerFragmentBinding
|
private lateinit var binding: RecyclerFragmentBinding
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
binding = RecyclerFragmentBinding.inflate(inflater, container, false)
|
binding = RecyclerFragmentBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
|
@ -136,6 +137,10 @@ class ManageChildCategoriesFragment : Fragment() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRequestAppListSyncConsentClicked() {
|
||||||
|
SyncAppListConsentDialogFragment.newInstance().show(parentFragmentManager)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.recycler.adapter = adapter
|
binding.recycler.adapter = adapter
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -144,23 +144,25 @@ class ManageChildCategoriesModel(application: Application): AndroidViewModel(app
|
||||||
|
|
||||||
private val hasShownHint = logic.database.config().wereHintsShown(HintsToShow.CATEGORIES_INTRODUCTION)
|
private val hasShownHint = logic.database.config().wereHintsShown(HintsToShow.CATEGORIES_INTRODUCTION)
|
||||||
|
|
||||||
private val listContentStep1 = hasShownHint.switchMap { hasShownHint ->
|
private val showSyncConsentBanner = logic.syncAppsLogic.shouldAskForConsent
|
||||||
categoryItems.map { categoryItems ->
|
|
||||||
if (hasShownHint) {
|
|
||||||
categoryItems + listOf(CreateCategoryItem)
|
|
||||||
} else {
|
|
||||||
listOf(CategoriesIntroductionHeader) + categoryItems + listOf(CreateCategoryItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val listContent = hasNotSuppressedChildDeviceManipulation.switchMap { hasChildDevicesWithManipulation ->
|
val listContent = mergeLiveDataWaitForValues(
|
||||||
listContentStep1.map { listContent ->
|
categoryItems,
|
||||||
if (hasChildDevicesWithManipulation) {
|
hasShownHint,
|
||||||
listOf(ManipulationWarningCategoryItem) + listContent
|
showSyncConsentBanner,
|
||||||
} else {
|
hasNotSuppressedChildDeviceManipulation
|
||||||
listContent
|
).map { (categoryItems, hasShownHint, showSyncConsentBanner, hasChildDevicesWithManipulation) ->
|
||||||
}
|
val headers1 = emptyList<ManageChildCategoriesListItem>()
|
||||||
}
|
|
||||||
|
val headers2 = if (hasShownHint) headers1
|
||||||
|
else headers1 + listOf(CategoriesIntroductionHeader)
|
||||||
|
|
||||||
|
val headers3 = if (showSyncConsentBanner) headers2 + listOf(ManageChildCategoriesListItem.SyncAppListBanner)
|
||||||
|
else headers2
|
||||||
|
|
||||||
|
val headers4 = if (hasChildDevicesWithManipulation) headers3 + listOf(ManipulationWarningCategoryItem)
|
||||||
|
else headers3
|
||||||
|
|
||||||
|
headers4 + categoryItems + listOf(CreateCategoryItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -24,10 +24,10 @@ import android.view.ViewGroup
|
||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
import androidx.appcompat.widget.AppCompatRadioButton
|
import androidx.appcompat.widget.AppCompatRadioButton
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
|
||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
import io.timelimit.android.coroutines.runAsync
|
import io.timelimit.android.coroutines.runAsync
|
||||||
|
@ -60,6 +60,7 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
private const val STATUS_ALLOWED_APPS_CATEGORY = "c"
|
private const val STATUS_ALLOWED_APPS_CATEGORY = "c"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val model: SetupDeviceModel by viewModels()
|
||||||
private val selectedUser = MutableLiveData<String>()
|
private val selectedUser = MutableLiveData<String>()
|
||||||
private val selectedAppsToNotWhitelist = mutableSetOf<String>()
|
private val selectedAppsToNotWhitelist = mutableSetOf<String>()
|
||||||
private var allowedAppsCategory = ""
|
private var allowedAppsCategory = ""
|
||||||
|
@ -89,11 +90,10 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
outState.putString(STATUS_ALLOWED_APPS_CATEGORY, allowedAppsCategory)
|
outState.putString(STATUS_ALLOWED_APPS_CATEGORY, allowedAppsCategory)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
val binding = FragmentSetupDeviceBinding.inflate(inflater, container, false)
|
val binding = FragmentSetupDeviceBinding.inflate(inflater, container, false)
|
||||||
val logic = DefaultAppLogic.with(requireContext())
|
val logic = DefaultAppLogic.with(requireContext())
|
||||||
val activity = activity as ActivityViewModelHolder
|
val activity = activity as ActivityViewModelHolder
|
||||||
val model = ViewModelProviders.of(this).get(SetupDeviceModel::class.java)
|
|
||||||
val navigation = Navigation.findNavController(container!!)
|
val navigation = Navigation.findNavController(container!!)
|
||||||
|
|
||||||
binding.needsParent.authBtn.setOnClickListener {
|
binding.needsParent.authBtn.setOnClickListener {
|
||||||
|
@ -136,7 +136,7 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
logic.database.user().getAllUsersLive().observe(this, Observer { users ->
|
logic.database.user().getAllUsersLive().observe(viewLifecycleOwner) { users ->
|
||||||
// ID to label
|
// ID to label
|
||||||
val items = mutableListOf<Pair<String, String>>()
|
val items = mutableListOf<Pair<String, String>>()
|
||||||
|
|
||||||
|
@ -150,7 +150,9 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
|
|
||||||
// select the first item if nothing is selected currently
|
// select the first item if nothing is selected currently
|
||||||
if (items.find { (id) -> id == selectedUser.value } == null) {
|
if (items.find { (id) -> id == selectedUser.value } == null) {
|
||||||
selectedUser.value = items.first().first
|
items.firstOrNull()?.first?.let {
|
||||||
|
selectedUser.value = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the views
|
// build the views
|
||||||
|
@ -166,7 +168,7 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
binding.selectUserRadioGroup.removeAllViews()
|
binding.selectUserRadioGroup.removeAllViews()
|
||||||
views.forEach { view -> binding.selectUserRadioGroup.addView(view) }
|
views.forEach { view -> binding.selectUserRadioGroup.addView(view) }
|
||||||
views.find { it.tag == selectedUser.value }?.isChecked = true
|
views.find { it.tag == selectedUser.value }?.isChecked = true
|
||||||
})
|
}
|
||||||
|
|
||||||
val isNewUser = selectedUser.map { NEW_USER.contains(it) }
|
val isNewUser = selectedUser.map { NEW_USER.contains(it) }
|
||||||
val isParentUser = selectedUser.switchMap {
|
val isParentUser = selectedUser.switchMap {
|
||||||
|
@ -181,8 +183,8 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isNewUser.observe(this, Observer { binding.isAddingNewUser = it })
|
isNewUser.observe(viewLifecycleOwner) { binding.isAddingNewUser = it }
|
||||||
isParentUser.observe(this, Observer { binding.isAddingChild = !it })
|
isParentUser.observe(viewLifecycleOwner) { binding.isAddingChild = !it }
|
||||||
|
|
||||||
val categoriesOfTheSelectedUser = selectedUser.switchMap { user ->
|
val categoriesOfTheSelectedUser = selectedUser.switchMap { user ->
|
||||||
if (NEW_USER.contains(user)) {
|
if (NEW_USER.contains(user)) {
|
||||||
|
@ -204,7 +206,7 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
recommendWhitelistLocalApps.filterNot { app -> assignedApps.contains(app.packageName) }
|
recommendWhitelistLocalApps.filterNot { app -> assignedApps.contains(app.packageName) }
|
||||||
}
|
}
|
||||||
|
|
||||||
appsToWhitelist.observe(this, Observer { apps ->
|
appsToWhitelist.observe(viewLifecycleOwner) { apps ->
|
||||||
binding.areThereAnyApps = apps.isNotEmpty()
|
binding.areThereAnyApps = apps.isNotEmpty()
|
||||||
|
|
||||||
binding.suggestedAllowedApps.removeAllViews()
|
binding.suggestedAllowedApps.removeAllViews()
|
||||||
|
@ -225,9 +227,9 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
categoriesOfTheSelectedUser.observe(this, Observer { categories ->
|
categoriesOfTheSelectedUser.observe(viewLifecycleOwner) { categories ->
|
||||||
// id to title
|
// id to title
|
||||||
val items = mutableListOf<Pair<String, String>>()
|
val items = mutableListOf<Pair<String, String>>()
|
||||||
|
|
||||||
|
@ -241,7 +243,7 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
} else {
|
} else {
|
||||||
if (items.find { (id) -> id == allowedAppsCategory } == null) {
|
if (items.find { (id) -> id == allowedAppsCategory } == null) {
|
||||||
// use the one with the lowest blocked times
|
// use the one with the lowest blocked times
|
||||||
allowedAppsCategory = categories.sortedBy { it.blockedMinutesInWeek.dataNotToModify.cardinality() }.first().id
|
allowedAppsCategory = categories.minByOrNull { it.blockedMinutesInWeek.dataNotToModify.cardinality() }!!.id
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.areThereAnyCategories = true
|
binding.areThereAnyCategories = true
|
||||||
|
@ -258,7 +260,7 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
views.forEach { view -> binding.allowedAppsCategory.addView(view) }
|
views.forEach { view -> binding.allowedAppsCategory.addView(view) }
|
||||||
views.find { it.tag == allowedAppsCategory }?.isChecked = true
|
views.find { it.tag == allowedAppsCategory }?.isChecked = true
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
val selectedName = MutableLiveData<String>().apply { value = binding.newUserName.text.toString() }
|
val selectedName = MutableLiveData<String>().apply { value = binding.newUserName.text.toString() }
|
||||||
binding.newUserName.addTextChangedListener(object: TextWatcher {
|
binding.newUserName.addTextChangedListener(object: TextWatcher {
|
||||||
|
@ -281,9 +283,9 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
)
|
)
|
||||||
val validationOfAll = (validationOfName.and(validationOfPassword)).or(isNewUser.invert())
|
val validationOfAll = (validationOfName.and(validationOfPassword)).or(isNewUser.invert())
|
||||||
|
|
||||||
validationOfAll.observe(this, Observer { binding.confirmBtn.isEnabled = it })
|
validationOfAll.observe(viewLifecycleOwner) { binding.confirmBtn.isEnabled = it }
|
||||||
|
|
||||||
isPasswordRequired.observe(this, Observer { binding.setPasswordView.allowNoPassword.value = !it })
|
isPasswordRequired.observe(viewLifecycleOwner) { binding.setPasswordView.allowNoPassword.value = !it }
|
||||||
|
|
||||||
ManageDeviceBackgroundSync.bind(
|
ManageDeviceBackgroundSync.bind(
|
||||||
view = binding.backgroundSync,
|
view = binding.backgroundSync,
|
||||||
|
@ -302,7 +304,8 @@ class SetupDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
||||||
appsToNotWhitelist = selectedAppsToNotWhitelist,
|
appsToNotWhitelist = selectedAppsToNotWhitelist,
|
||||||
model = activity.getActivityViewModel(),
|
model = activity.getActivityViewModel(),
|
||||||
networkTime = SetupNetworkTimeVerification.readSelection(binding.networkTimeVerification),
|
networkTime = SetupNetworkTimeVerification.readSelection(binding.networkTimeVerification),
|
||||||
enableUpdateChecks = binding.update.enableSwitch.isChecked
|
enableUpdateChecks = binding.update.enableSwitch.isChecked,
|
||||||
|
enableAppListSync = binding.appListSync.enableSwitch.isChecked && (isParentUser.value == false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,18 @@
|
||||||
package io.timelimit.android.ui.setup.device
|
package io.timelimit.android.ui.setup.device
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import io.timelimit.android.BuildConfig
|
||||||
|
import io.timelimit.android.R
|
||||||
import io.timelimit.android.async.Threads
|
import io.timelimit.android.async.Threads
|
||||||
import io.timelimit.android.coroutines.executeAndWait
|
import io.timelimit.android.coroutines.executeAndWait
|
||||||
import io.timelimit.android.coroutines.runAsync
|
import io.timelimit.android.coroutines.runAsync
|
||||||
import io.timelimit.android.data.IdGenerator
|
import io.timelimit.android.data.IdGenerator
|
||||||
import io.timelimit.android.data.model.AppRecommendation
|
import io.timelimit.android.data.model.AppRecommendation
|
||||||
|
import io.timelimit.android.data.model.ConsentFlags
|
||||||
import io.timelimit.android.data.model.NetworkTime
|
import io.timelimit.android.data.model.NetworkTime
|
||||||
import io.timelimit.android.data.model.UserType
|
import io.timelimit.android.data.model.UserType
|
||||||
import io.timelimit.android.livedata.castDown
|
import io.timelimit.android.livedata.castDown
|
||||||
|
@ -35,6 +40,10 @@ import io.timelimit.android.ui.user.create.DefaultCategories
|
||||||
import io.timelimit.android.update.UpdateUtil
|
import io.timelimit.android.update.UpdateUtil
|
||||||
|
|
||||||
class SetupDeviceModel(application: Application): AndroidViewModel(application) {
|
class SetupDeviceModel(application: Application): AndroidViewModel(application) {
|
||||||
|
companion object {
|
||||||
|
private const val LOG_TAG = "SetupDeviceModel"
|
||||||
|
}
|
||||||
|
|
||||||
private val logic = DefaultAppLogic.with(application)
|
private val logic = DefaultAppLogic.with(application)
|
||||||
private val statusInternal = MutableLiveData<SetupDeviceModelStatus>().apply { value = SetupDeviceModelStatus.Ready }
|
private val statusInternal = MutableLiveData<SetupDeviceModelStatus>().apply { value = SetupDeviceModelStatus.Ready }
|
||||||
|
|
||||||
|
@ -48,7 +57,8 @@ class SetupDeviceModel(application: Application): AndroidViewModel(application)
|
||||||
appsToNotWhitelist: Set<String>,
|
appsToNotWhitelist: Set<String>,
|
||||||
model: ActivityViewModel,
|
model: ActivityViewModel,
|
||||||
networkTime: NetworkTime,
|
networkTime: NetworkTime,
|
||||||
enableUpdateChecks: Boolean
|
enableUpdateChecks: Boolean,
|
||||||
|
enableAppListSync: Boolean
|
||||||
) {
|
) {
|
||||||
if (statusInternal.value != SetupDeviceModelStatus.Ready) {
|
if (statusInternal.value != SetupDeviceModelStatus.Ready) {
|
||||||
return
|
return
|
||||||
|
@ -57,6 +67,7 @@ class SetupDeviceModel(application: Application): AndroidViewModel(application)
|
||||||
statusInternal.value = SetupDeviceModelStatus.Working
|
statusInternal.value = SetupDeviceModelStatus.Working
|
||||||
|
|
||||||
runAsync {
|
runAsync {
|
||||||
|
try {
|
||||||
val actions = mutableListOf<ParentAction>()
|
val actions = mutableListOf<ParentAction>()
|
||||||
var realUserId = userId
|
var realUserId = userId
|
||||||
var realAllowedAppsCategory = allowedAppsCategory
|
var realAllowedAppsCategory = allowedAppsCategory
|
||||||
|
@ -68,13 +79,15 @@ class SetupDeviceModel(application: Application): AndroidViewModel(application)
|
||||||
realUserId = IdGenerator.generateId()
|
realUserId = IdGenerator.generateId()
|
||||||
|
|
||||||
// create parent
|
// create parent
|
||||||
actions.add(AddUserAction(
|
actions.add(
|
||||||
|
AddUserAction(
|
||||||
userId = realUserId,
|
userId = realUserId,
|
||||||
name = username,
|
name = username,
|
||||||
timeZone = logic.timeApi.getSystemTimeZone().id,
|
timeZone = logic.timeApi.getSystemTimeZone().id,
|
||||||
userType = UserType.Parent,
|
userType = UserType.Parent,
|
||||||
password = ParentPassword.createCoroutine(password)
|
password = ParentPassword.createCoroutine(password)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -83,38 +96,48 @@ class SetupDeviceModel(application: Application): AndroidViewModel(application)
|
||||||
realUserId = IdGenerator.generateId()
|
realUserId = IdGenerator.generateId()
|
||||||
|
|
||||||
// create child
|
// create child
|
||||||
actions.add(AddUserAction(
|
actions.add(
|
||||||
|
AddUserAction(
|
||||||
userId = realUserId,
|
userId = realUserId,
|
||||||
name = username,
|
name = username,
|
||||||
timeZone = logic.timeApi.getSystemTimeZone().id,
|
timeZone = logic.timeApi.getSystemTimeZone().id,
|
||||||
userType = UserType.Child,
|
userType = UserType.Child,
|
||||||
password = if (password.isEmpty()) null else ParentPassword.createCoroutine(password)
|
password = if (password.isEmpty()) null else ParentPassword.createCoroutine(
|
||||||
))
|
password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
// create default categories
|
// create default categories
|
||||||
realAllowedAppsCategory = IdGenerator.generateId()
|
realAllowedAppsCategory = IdGenerator.generateId()
|
||||||
val allowedGamesCategory = IdGenerator.generateId()
|
val allowedGamesCategory = IdGenerator.generateId()
|
||||||
|
|
||||||
actions.add(CreateCategoryAction(
|
actions.add(
|
||||||
|
CreateCategoryAction(
|
||||||
childId = realUserId,
|
childId = realUserId,
|
||||||
categoryId = realAllowedAppsCategory,
|
categoryId = realAllowedAppsCategory,
|
||||||
title = defaultCategories.allowedAppsTitle
|
title = defaultCategories.allowedAppsTitle
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
actions.add(CreateCategoryAction(
|
actions.add(
|
||||||
|
CreateCategoryAction(
|
||||||
childId = realUserId,
|
childId = realUserId,
|
||||||
categoryId = allowedGamesCategory,
|
categoryId = allowedGamesCategory,
|
||||||
title = defaultCategories.allowedGamesTitle
|
title = defaultCategories.allowedGamesTitle
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
defaultCategories.generateGamesTimeLimitRules(allowedGamesCategory).forEach { rule ->
|
defaultCategories.generateGamesTimeLimitRules(allowedGamesCategory)
|
||||||
|
.forEach { rule ->
|
||||||
actions.add(CreateTimeLimitRuleAction(rule))
|
actions.add(CreateTimeLimitRuleAction(rule))
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
logic.database.user().getUserByIdLive(userId).waitForNullableValue()!!.type == UserType.Child
|
logic.database.user().getUserByIdLive(userId)
|
||||||
|
.waitForNullableValue()!!.type == UserType.Child
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,11 +146,13 @@ class SetupDeviceModel(application: Application): AndroidViewModel(application)
|
||||||
// create allowed apps category if none was specified and overwrite its id
|
// create allowed apps category if none was specified and overwrite its id
|
||||||
realAllowedAppsCategory = IdGenerator.generateId()
|
realAllowedAppsCategory = IdGenerator.generateId()
|
||||||
|
|
||||||
actions.add(CreateCategoryAction(
|
actions.add(
|
||||||
|
CreateCategoryAction(
|
||||||
childId = realUserId,
|
childId = realUserId,
|
||||||
categoryId = realAllowedAppsCategory,
|
categoryId = realAllowedAppsCategory,
|
||||||
title = defaultCategories.allowedAppsTitle
|
title = defaultCategories.allowedAppsTitle
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val alreadyAssignedApps = Threads.database.executeAndWait {
|
val alreadyAssignedApps = Threads.database.executeAndWait {
|
||||||
|
@ -138,7 +163,8 @@ class SetupDeviceModel(application: Application): AndroidViewModel(application)
|
||||||
}
|
}
|
||||||
|
|
||||||
// add allowed apps
|
// add allowed apps
|
||||||
val allowedAppsPackages = logic.platformIntegration.getLocalApps(IdGenerator.generateId())
|
val allowedAppsPackages =
|
||||||
|
logic.platformIntegration.getLocalApps(IdGenerator.generateId())
|
||||||
.filter { app -> app.recommendation == AppRecommendation.Whitelist }
|
.filter { app -> app.recommendation == AppRecommendation.Whitelist }
|
||||||
.map { app -> app.packageName }
|
.map { app -> app.packageName }
|
||||||
.toMutableSet().apply {
|
.toMutableSet().apply {
|
||||||
|
@ -147,33 +173,52 @@ class SetupDeviceModel(application: Application): AndroidViewModel(application)
|
||||||
}.toList()
|
}.toList()
|
||||||
|
|
||||||
if (allowedAppsPackages.isNotEmpty()) {
|
if (allowedAppsPackages.isNotEmpty()) {
|
||||||
actions.add(AddCategoryAppsAction(
|
actions.add(
|
||||||
|
AddCategoryAppsAction(
|
||||||
categoryId = realAllowedAppsCategory,
|
categoryId = realAllowedAppsCategory,
|
||||||
packageNames = allowedAppsPackages
|
packageNames = allowedAppsPackages
|
||||||
))
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the network time mode
|
// apply the network time mode
|
||||||
val deviceId = logic.deviceId.waitForNullableValue()!!
|
val deviceId = logic.deviceId.waitForNullableValue()!!
|
||||||
|
|
||||||
actions.add(UpdateNetworkTimeVerificationAction(
|
actions.add(
|
||||||
|
UpdateNetworkTimeVerificationAction(
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
mode = networkTime
|
mode = networkTime
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
// assign user to this device
|
// assign user to this device
|
||||||
actions.add(SetDeviceUserAction(
|
actions.add(
|
||||||
|
SetDeviceUserAction(
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
userId = realUserId
|
userId = realUserId
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
// configure update check
|
// configure update check
|
||||||
UpdateUtil.setEnableChecks(getApplication(), enableUpdateChecks)
|
UpdateUtil.setEnableChecks(getApplication(), enableUpdateChecks)
|
||||||
|
|
||||||
|
Threads.database.executeAndWait {
|
||||||
|
DefaultAppLogic.with(getApplication()).database.config().setConsentFlagSync(ConsentFlags.APP_LIST_SYNC, enableAppListSync)
|
||||||
|
}
|
||||||
|
|
||||||
if (model.tryDispatchParentActions(actions)) {
|
if (model.tryDispatchParentActions(actions)) {
|
||||||
statusInternal.value = SetupDeviceModelStatus.Done
|
statusInternal.value = SetupDeviceModelStatus.Done
|
||||||
} else {
|
} else {
|
||||||
|
statusInternal.value = SetupDeviceModelStatus.Ready
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d(LOG_TAG, "could not setup device", ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(getApplication(), R.string.error_general, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
statusInternal.value = SetupDeviceModelStatus.Ready
|
statusInternal.value = SetupDeviceModelStatus.Ready
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation version 3 of the License.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardUseCompatPadding="true">
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:textAppearance="?android:textAppearanceLarge"
|
||||||
|
android:text="@string/consent_app_list_sync_dialog_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:textAppearance="?android:textAppearanceMedium"
|
||||||
|
android:text="@string/consent_app_list_sync_card_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:id="@+id/detail_button"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/generic_show_details"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
</layout>
|
|
@ -1,5 +1,5 @@
|
||||||
<!--
|
<!--
|
||||||
TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation version 3 of the License.
|
the Free Software Foundation version 3 of the License.
|
||||||
|
@ -203,6 +203,10 @@
|
||||||
<include android:id="@+id/network_time_verification"
|
<include android:id="@+id/network_time_verification"
|
||||||
layout="@layout/setup_network_time_verification" />
|
layout="@layout/setup_network_time_verification" />
|
||||||
|
|
||||||
|
<include android:id="@+id/app_list_sync"
|
||||||
|
android:visibility="@{isAddingChild ? View.VISIBLE : View.GONE}"
|
||||||
|
layout="@layout/setup_device_app_list_sync" />
|
||||||
|
|
||||||
<include android:id="@+id/background_sync"
|
<include android:id="@+id/background_sync"
|
||||||
layout="@layout/manage_device_background_sync_view" />
|
layout="@layout/manage_device_background_sync_view" />
|
||||||
|
|
||||||
|
|
48
app/src/main/res/layout/setup_device_app_list_sync.xml
Normal file
48
app/src/main/res/layout/setup_device_app_list_sync.xml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation version 3 of the License.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardUseCompatPadding="true">
|
||||||
|
<LinearLayout
|
||||||
|
android:padding="8dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:textAppearance="?android:textAppearanceLarge"
|
||||||
|
android:text="@string/consent_app_list_sync_dialog_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:textAppearance="?android:textAppearanceMedium"
|
||||||
|
android:text="@string/consent_app_list_sync_dialog_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:text="@string/consent_app_list_sync_switch_label"
|
||||||
|
android:id="@+id/enable_switch"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
</layout>
|
|
@ -30,6 +30,8 @@
|
||||||
<string name="generic_yes">Ja</string>
|
<string name="generic_yes">Ja</string>
|
||||||
<string name="generic_skip">Überspringen</string>
|
<string name="generic_skip">Überspringen</string>
|
||||||
<string name="generic_show_details">Details anzeigen</string>
|
<string name="generic_show_details">Details anzeigen</string>
|
||||||
|
<string name="generic_accept">Akzeptieren</string>
|
||||||
|
<string name="generic_reject">Ablehnen</string>
|
||||||
|
|
||||||
<string name="generic_swipe_to_dismiss">Sie können diesen Hinweis entfernen, indem Sie ihn zur Seite wischen</string>
|
<string name="generic_swipe_to_dismiss">Sie können diesen Hinweis entfernen, indem Sie ihn zur Seite wischen</string>
|
||||||
<string name="generic_runtime_permission_rejected">Berechtigung abgelehnt; Sie können die Berechtigungen in den Systemeinstellungen verwalten</string>
|
<string name="generic_runtime_permission_rejected">Berechtigung abgelehnt; Sie können die Berechtigungen in den Systemeinstellungen verwalten</string>
|
||||||
|
@ -275,7 +277,8 @@
|
||||||
und daher auch keine Apps auf Geräten, die dem Benutzer zugeordnet wurden
|
und daher auch keine Apps auf Geräten, die dem Benutzer zugeordnet wurden
|
||||||
</string>
|
</string>
|
||||||
<string name="category_apps_add_empty_child_devices_no_apps">Es gibt mindestens ein Gerät für diesen Benutzer,
|
<string name="category_apps_add_empty_child_devices_no_apps">Es gibt mindestens ein Gerät für diesen Benutzer,
|
||||||
aber es sind keine Apps von diesem Gerät bekannt. Ist die Benutzerzuordnung am entsprechenden Gerät angekommen?
|
aber es sind keine Apps von diesem Gerät bekannt. Ist die Benutzerzuordnung am entsprechenden Gerät angekommen
|
||||||
|
und wurde dort die Synchronisation der App-Liste aktiviert?
|
||||||
</string>
|
</string>
|
||||||
<string name="category_apps_add_empty_all_devices_no_apps_no_childs">Es gibt kein Gerät, das einem Kind zugeordnet wurde.
|
<string name="category_apps_add_empty_all_devices_no_apps_no_childs">Es gibt kein Gerät, das einem Kind zugeordnet wurde.
|
||||||
Daher wurden keine Apps erfasst und dadurch sind jetzt keine Apps bekannt, die zugerodnet werden können.
|
Daher wurden keine Apps erfasst und dadurch sind jetzt keine Apps bekannt, die zugerodnet werden können.
|
||||||
|
@ -1391,7 +1394,7 @@
|
||||||
<string name="setup_privacy_connected_title">Vernetzung und Datenschutz</string>
|
<string name="setup_privacy_connected_title">Vernetzung und Datenschutz</string>
|
||||||
<string name="setup_privacy_connected_text_general_intro">Für den vernetzten Modus
|
<string name="setup_privacy_connected_text_general_intro">Für den vernetzten Modus
|
||||||
gibt es eine Zentrale - den Server. Auf diesen werden die Einstellungen,
|
gibt es eine Zentrale - den Server. Auf diesen werden die Einstellungen,
|
||||||
die Nutzungsdauern und die Gerätezustände (erteilte Berechtigungen, installierte Apps und die App- und System-Version)
|
die Nutzungsdauern und die Gerätezustände (erteilte Berechtigungen und die TimeLimit- und System-Version)
|
||||||
gespeichert.
|
gespeichert.
|
||||||
</string>
|
</string>
|
||||||
<string name="setup_privacy_connected_text_default_server">Sie verwenden - da Sie Nichts anderes gewählt haben -
|
<string name="setup_privacy_connected_text_default_server">Sie verwenden - da Sie Nichts anderes gewählt haben -
|
||||||
|
@ -1404,6 +1407,17 @@
|
||||||
verhindern, indem Sie den lokalen Modus verwenden.
|
verhindern, indem Sie den lokalen Modus verwenden.
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
|
<string name="consent_app_list_sync_dialog_title">App-Listen-Synchronisation</string>
|
||||||
|
<string name="consent_app_list_sync_dialog_text">TimeLimit möchte die Liste
|
||||||
|
der installierten Apps synchronisieren, um das Freigeben von Apps mittels Fernsteuerung von anderen
|
||||||
|
verknüpften Geräten aus zu ermöglichen. Dafür wird die Liste auf dem TimeLimit-Server
|
||||||
|
gespeichert. Diese Daten werden für keine anderen Zwecke verwendet.
|
||||||
|
Alternativ können Sie auch den lokalen Modus verwenden.
|
||||||
|
</string>
|
||||||
|
<string name="consent_app_list_sync_switch_label">Synchronisation aktivieren</string>
|
||||||
|
<string name="consent_app_list_sync_card_text">TimeLimit möchte die Liste der installierten Apps
|
||||||
|
für die Fernsteuerungsfunktion synchronisieren.</string>
|
||||||
|
|
||||||
<string name="setup_remote_child_code_invalid">Dieser Code ist ungültig</string>
|
<string name="setup_remote_child_code_invalid">Dieser Code ist ungültig</string>
|
||||||
<string name="setup_remote_child_title">Dieses Gerät verknüpfen</string>
|
<string name="setup_remote_child_title">Dieses Gerät verknüpfen</string>
|
||||||
<string name="setup_remote_child_text">
|
<string name="setup_remote_child_text">
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
<string name="generic_yes">Yes</string>
|
<string name="generic_yes">Yes</string>
|
||||||
<string name="generic_skip">Skip</string>
|
<string name="generic_skip">Skip</string>
|
||||||
<string name="generic_show_details">Show details</string>
|
<string name="generic_show_details">Show details</string>
|
||||||
|
<string name="generic_accept">Accept</string>
|
||||||
|
<string name="generic_reject">Reject</string>
|
||||||
|
|
||||||
<string name="generic_swipe_to_dismiss">Swipe to the side to remove this message</string>
|
<string name="generic_swipe_to_dismiss">Swipe to the side to remove this message</string>
|
||||||
<string name="generic_runtime_permission_rejected">Permission rejected; You can manage permissions in the system settings</string>
|
<string name="generic_runtime_permission_rejected">Permission rejected; You can manage permissions in the system settings</string>
|
||||||
|
@ -315,7 +317,8 @@
|
||||||
</string>
|
</string>
|
||||||
<string name="category_apps_add_empty_child_devices_no_apps">
|
<string name="category_apps_add_empty_child_devices_no_apps">
|
||||||
There is a device assigned to this user, but there are no know Apps.
|
There is a device assigned to this user, but there are no know Apps.
|
||||||
Did the device receive the user assignment?
|
Did the device receive the user assignment and was the App List
|
||||||
|
Synchronisation enabled at this device?
|
||||||
</string>
|
</string>
|
||||||
<string name="category_apps_add_empty_all_devices_no_apps_no_childs">There is no device with any child assigned.
|
<string name="category_apps_add_empty_all_devices_no_apps_no_childs">There is no device with any child assigned.
|
||||||
Due to that, no Apps were recorded and thus no assignable Apps are known.
|
Due to that, no Apps were recorded and thus no assignable Apps are known.
|
||||||
|
@ -1427,7 +1430,7 @@
|
||||||
<string name="setup_privacy_connected_title">Networking and Privacy</string>
|
<string name="setup_privacy_connected_title">Networking and Privacy</string>
|
||||||
<string name="setup_privacy_connected_text_general_intro">For the connected mode,
|
<string name="setup_privacy_connected_text_general_intro">For the connected mode,
|
||||||
there is a central unit - the server. This server saves the settings, the usage durations
|
there is a central unit - the server. This server saves the settings, the usage durations
|
||||||
and the status of the devices (granted permissions, installed Apps, App and OS version).
|
and the status of the devices (granted permissions, TimeLimit version and OS version).
|
||||||
</string>
|
</string>
|
||||||
<string name="setup_privacy_connected_text_default_server">Because you have not selected anything different,
|
<string name="setup_privacy_connected_text_default_server">Because you have not selected anything different,
|
||||||
you are using the default server which is provided by the developer of the App.
|
you are using the default server which is provided by the developer of the App.
|
||||||
|
@ -1440,6 +1443,15 @@
|
||||||
the data transmission by using the local mode.
|
the data transmission by using the local mode.
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
|
<string name="consent_app_list_sync_dialog_title">App List Synchronization</string>
|
||||||
|
<string name="consent_app_list_sync_dialog_text">TimeLimit would like to sync the
|
||||||
|
list of the installed Apps to make it possible to allow Apps remotely. For this,
|
||||||
|
the list is saved at the TimeLimit server. This data is not used for any other purposes.
|
||||||
|
Alternatively, you can use the local mode.
|
||||||
|
</string>
|
||||||
|
<string name="consent_app_list_sync_switch_label">Enable Synchronization</string>
|
||||||
|
<string name="consent_app_list_sync_card_text">TimeLimit would like to sync the list of installed Apps for the remote control.</string>
|
||||||
|
|
||||||
<string name="setup_remote_child_code_invalid">This code is invalid</string>
|
<string name="setup_remote_child_code_invalid">This code is invalid</string>
|
||||||
<string name="setup_remote_child_title">Link this device</string>
|
<string name="setup_remote_child_title">Link this device</string>
|
||||||
<string name="setup_remote_child_text">
|
<string name="setup_remote_child_text">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue