mirror of
https://codeberg.org/timelimit/opentimelimit-android.git
synced 2025-10-06 03:50:27 +02:00
Allow interpreting a device reboot as manipulation
This commit is contained in:
parent
c37b888b56
commit
212aaafd78
20 changed files with 726 additions and 21 deletions
455
app/schemas/io.timelimit.android.data.RoomDatabase/4.json
Normal file
455
app/schemas/io.timelimit.android.data.RoomDatabase/4.json
Normal file
|
@ -0,0 +1,455 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "193ef7d6dcc9fbda20e13c8d49c5295f",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "user",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `password` TEXT NOT NULL, `type` TEXT NOT NULL, `timezone` TEXT NOT NULL, `disable_limits_until` INTEGER NOT NULL, `category_for_not_assigned_apps` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timeZone",
|
||||
"columnName": "timezone",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "disableLimitsUntil",
|
||||
"columnName": "disable_limits_until",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categoryForNotAssignedApps",
|
||||
"columnName": "category_for_not_assigned_apps",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "device",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `model` TEXT NOT NULL, `added_at` INTEGER NOT NULL, `current_user_id` TEXT NOT NULL, `current_protection_level` TEXT NOT NULL, `highest_permission_level` TEXT NOT NULL, `current_usage_stats_permission` TEXT NOT NULL, `highest_usage_stats_permission` TEXT NOT NULL, `current_notification_access_permission` TEXT NOT NULL, `highest_notification_access_permission` TEXT NOT NULL, `current_app_version` INTEGER NOT NULL, `highest_app_version` INTEGER NOT NULL, `tried_disabling_device_admin` INTEGER NOT NULL, `did_reboot` INTEGER NOT NULL, `had_manipulation` INTEGER NOT NULL, `consider_reboot_manipulation` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "model",
|
||||
"columnName": "model",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "addedAt",
|
||||
"columnName": "added_at",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "currentUserId",
|
||||
"columnName": "current_user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "currentProtectionLevel",
|
||||
"columnName": "current_protection_level",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "highestProtectionLevel",
|
||||
"columnName": "highest_permission_level",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "currentUsageStatsPermission",
|
||||
"columnName": "current_usage_stats_permission",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "highestUsageStatsPermission",
|
||||
"columnName": "highest_usage_stats_permission",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "currentNotificationAccessPermission",
|
||||
"columnName": "current_notification_access_permission",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "highestNotificationAccessPermission",
|
||||
"columnName": "highest_notification_access_permission",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "currentAppVersion",
|
||||
"columnName": "current_app_version",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "highestAppVersion",
|
||||
"columnName": "highest_app_version",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "manipulationTriedDisablingDeviceAdmin",
|
||||
"columnName": "tried_disabling_device_admin",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "manipulationDidReboot",
|
||||
"columnName": "did_reboot",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hadManipulation",
|
||||
"columnName": "had_manipulation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "considerRebootManipulation",
|
||||
"columnName": "consider_reboot_manipulation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "app",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `title` TEXT NOT NULL, `launchable` INTEGER NOT NULL, `recommendation` TEXT NOT NULL, PRIMARY KEY(`package_name`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isLaunchable",
|
||||
"columnName": "launchable",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "recommendation",
|
||||
"columnName": "recommendation",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"package_name"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_app_package_name",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"package_name"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_app_package_name` ON `${TABLE_NAME}` (`package_name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "category_app",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` TEXT NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`category_id`, `package_name`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "categoryId",
|
||||
"columnName": "category_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"category_id",
|
||||
"package_name"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_category_app_category_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"category_id"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_category_app_category_id` ON `${TABLE_NAME}` (`category_id`)"
|
||||
},
|
||||
{
|
||||
"name": "index_category_app_package_name",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"package_name"
|
||||
],
|
||||
"createSql": "CREATE INDEX `index_category_app_package_name` ON `${TABLE_NAME}` (`package_name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "category",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `child_id` TEXT NOT NULL, `title` TEXT NOT NULL, `blocked_times` TEXT NOT NULL, `extra_time` INTEGER NOT NULL, `temporarily_blocked` INTEGER NOT NULL, `parent_category_id` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "childId",
|
||||
"columnName": "child_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "blockedMinutesInWeek",
|
||||
"columnName": "blocked_times",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "extraTimeInMillis",
|
||||
"columnName": "extra_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "temporarilyBlocked",
|
||||
"columnName": "temporarily_blocked",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentCategoryId",
|
||||
"columnName": "parent_category_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "used_time",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`day_of_epoch` INTEGER NOT NULL, `used_time` INTEGER NOT NULL, `category_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `day_of_epoch`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "dayOfEpoch",
|
||||
"columnName": "day_of_epoch",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "usedMillis",
|
||||
"columnName": "used_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categoryId",
|
||||
"columnName": "category_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"category_id",
|
||||
"day_of_epoch"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "time_limit_rule",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `category_id` TEXT NOT NULL, `apply_to_extra_time_usage` INTEGER NOT NULL, `day_mask` INTEGER NOT NULL, `max_time` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "categoryId",
|
||||
"columnName": "category_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "applyToExtraTimeUsage",
|
||||
"columnName": "apply_to_extra_time_usage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "dayMask",
|
||||
"columnName": "day_mask",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "maximumTimeInMillis",
|
||||
"columnName": "max_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "config",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "key",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "temporarily_allowed_app",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, PRIMARY KEY(`package_name`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"package_name"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"193ef7d6dcc9fbda20e13c8d49c5295f\")"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -15,4 +15,11 @@ object DatabaseMigrations {
|
|||
database.execSQL("ALTER TABLE `category` ADD COLUMN `parent_category_id` TEXT NOT NULL DEFAULT \"\"")
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATE_TO_V4 = object: Migration(3, 4) {
|
||||
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 `consider_reboot_manipulation` INTEGER NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ import io.timelimit.android.data.model.*
|
|||
TimeLimitRule::class,
|
||||
ConfigurationItem::class,
|
||||
TemporarilyAllowedApp::class
|
||||
], version = 3)
|
||||
], version = 4)
|
||||
abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database {
|
||||
companion object {
|
||||
private val lock = Object()
|
||||
|
@ -68,7 +68,8 @@ abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database
|
|||
.fallbackToDestructiveMigration()
|
||||
.addMigrations(
|
||||
DatabaseMigrations.MIGRATE_TO_V2,
|
||||
DatabaseMigrations.MIGRATE_TO_V3
|
||||
DatabaseMigrations.MIGRATE_TO_V3,
|
||||
DatabaseMigrations.MIGRATE_TO_V4
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -61,8 +61,12 @@ data class Device(
|
|||
val highestAppVersion: Int,
|
||||
@ColumnInfo(name = "tried_disabling_device_admin")
|
||||
val manipulationTriedDisablingDeviceAdmin: Boolean,
|
||||
@ColumnInfo(name = "did_reboot")
|
||||
val manipulationDidReboot: Boolean,
|
||||
@ColumnInfo(name = "had_manipulation")
|
||||
val hadManipulation: Boolean
|
||||
val hadManipulation: Boolean,
|
||||
@ColumnInfo(name = "consider_reboot_manipulation")
|
||||
val considerRebootManipulation: Boolean
|
||||
): JsonSerializable {
|
||||
companion object {
|
||||
private const val ID = "id"
|
||||
|
@ -79,7 +83,9 @@ data class Device(
|
|||
private const val CURRENT_APP_VERSION = "ac"
|
||||
private const val HIGHEST_APP_VERSION = "am"
|
||||
private const val TRIED_DISABLING_DEVICE_ADMIN = "tdda"
|
||||
private const val MANIPULATION_DID_REBOOT = "mdr"
|
||||
private const val HAD_MANIPULATION = "hm"
|
||||
private const val CONSIDER_REBOOT_A_MANIPULATION = "cram"
|
||||
|
||||
fun parse(reader: JsonReader): Device {
|
||||
var id: String? = null
|
||||
|
@ -96,7 +102,9 @@ data class Device(
|
|||
var currentAppVersion: Int? = null
|
||||
var highestAppVersion: Int? = null
|
||||
var manipulationTriedDisablingDeviceAdmin: Boolean? = null
|
||||
var manipulationDidReboot: Boolean = false
|
||||
var hadManipulation: Boolean? = null
|
||||
var considerRebootManipulation = false
|
||||
|
||||
reader.beginObject()
|
||||
|
||||
|
@ -116,7 +124,9 @@ data class Device(
|
|||
CURRENT_APP_VERSION -> currentAppVersion = reader.nextInt()
|
||||
HIGHEST_APP_VERSION -> highestAppVersion = reader.nextInt()
|
||||
TRIED_DISABLING_DEVICE_ADMIN -> manipulationTriedDisablingDeviceAdmin = reader.nextBoolean()
|
||||
MANIPULATION_DID_REBOOT -> manipulationDidReboot = reader.nextBoolean()
|
||||
HAD_MANIPULATION -> hadManipulation = reader.nextBoolean()
|
||||
CONSIDER_REBOOT_A_MANIPULATION -> considerRebootManipulation = reader.nextBoolean()
|
||||
else -> reader.skipValue()
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +148,9 @@ data class Device(
|
|||
currentAppVersion = currentAppVersion!!,
|
||||
highestAppVersion = highestAppVersion!!,
|
||||
manipulationTriedDisablingDeviceAdmin = manipulationTriedDisablingDeviceAdmin!!,
|
||||
hadManipulation = hadManipulation!!
|
||||
manipulationDidReboot = manipulationDidReboot,
|
||||
hadManipulation = hadManipulation!!,
|
||||
considerRebootManipulation = considerRebootManipulation
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +196,9 @@ data class Device(
|
|||
writer.name(CURRENT_APP_VERSION).value(currentAppVersion)
|
||||
writer.name(HIGHEST_APP_VERSION).value(highestAppVersion)
|
||||
writer.name(TRIED_DISABLING_DEVICE_ADMIN).value(manipulationTriedDisablingDeviceAdmin)
|
||||
writer.name(MANIPULATION_DID_REBOOT).value(manipulationDidReboot)
|
||||
writer.name(HAD_MANIPULATION).value(hadManipulation)
|
||||
writer.name(CONSIDER_REBOOT_A_MANIPULATION).value(considerRebootManipulation)
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
|
@ -203,7 +217,8 @@ data class Device(
|
|||
manipulationOfUsageStats ||
|
||||
manipulationOfNotificationAccess ||
|
||||
manipulationOfAppVersion ||
|
||||
manipulationTriedDisablingDeviceAdmin
|
||||
manipulationTriedDisablingDeviceAdmin ||
|
||||
manipulationDidReboot
|
||||
|
||||
@Transient
|
||||
val hasAnyManipulation = hasActiveManipulationWarning || hadManipulation
|
||||
|
|
|
@ -24,7 +24,7 @@ class BootReceiver : BroadcastReceiver() {
|
|||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
|
||||
// this starts the logic (if not yet done)
|
||||
DefaultAppLogic.with(context)
|
||||
DefaultAppLogic.with(context).backgroundTaskLogic.reportDeviceReboot()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,9 @@ class AppSetupLogic(private val appLogic: AppLogic) {
|
|||
currentAppVersion = 0,
|
||||
highestAppVersion = 0,
|
||||
manipulationTriedDisablingDeviceAdmin = false,
|
||||
hadManipulation = false
|
||||
manipulationDidReboot = false,
|
||||
hadManipulation = false,
|
||||
considerRebootManipulation = false
|
||||
)
|
||||
|
||||
appLogic.database.device().addDeviceSync(device)
|
||||
|
|
|
@ -402,10 +402,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
if (deviceEntry != null) {
|
||||
if (deviceEntry.currentAppVersion != currentAppVersion) {
|
||||
ApplyActionUtil.applyAppLogicAction(
|
||||
UpdateDeviceStatusAction(
|
||||
newProtectionLevel = null,
|
||||
newUsageStatsPermissionStatus = null,
|
||||
newNotificationAccessPermission = null,
|
||||
UpdateDeviceStatusAction.empty.copy(
|
||||
newAppVersion = currentAppVersion
|
||||
),
|
||||
appLogic
|
||||
|
@ -432,6 +429,21 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
|
||||
private val syncDeviceStatusLock = Mutex()
|
||||
|
||||
fun reportDeviceReboot() {
|
||||
runAsync {
|
||||
val deviceEntry = appLogic.deviceEntry.waitForNullableValue()
|
||||
|
||||
if (deviceEntry?.considerRebootManipulation == true) {
|
||||
ApplyActionUtil.applyAppLogicAction(
|
||||
UpdateDeviceStatusAction.empty.copy(
|
||||
didReboot = true
|
||||
),
|
||||
appLogic
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun syncDeviceStatus() {
|
||||
syncDeviceStatusLock.withLock {
|
||||
val deviceEntry = appLogic.deviceEntry.waitForNullableValue()
|
||||
|
@ -441,14 +453,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
val usageStatsPermission = appLogic.platformIntegration.getForegroundAppPermissionStatus()
|
||||
val notificationAccess = appLogic.platformIntegration.getNotificationAccessPermissionStatus()
|
||||
|
||||
val emptyChanges = UpdateDeviceStatusAction(
|
||||
newProtectionLevel = null,
|
||||
newUsageStatsPermissionStatus = null,
|
||||
newNotificationAccessPermission = null,
|
||||
newAppVersion = null
|
||||
)
|
||||
|
||||
var changes = emptyChanges
|
||||
var changes = UpdateDeviceStatusAction.empty
|
||||
|
||||
if (protectionLevel != deviceEntry.currentProtectionLevel) {
|
||||
changes = changes.copy(
|
||||
|
@ -472,7 +477,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
)
|
||||
}
|
||||
|
||||
if (changes != emptyChanges) {
|
||||
if (changes != UpdateDeviceStatusAction.empty) {
|
||||
ApplyActionUtil.applyAppLogicAction(changes, appLogic)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,8 +155,19 @@ data class UpdateDeviceStatusAction(
|
|||
val newProtectionLevel: ProtectionLevel?,
|
||||
val newUsageStatsPermissionStatus: RuntimePermissionStatus?,
|
||||
val newNotificationAccessPermission: NewPermissionStatus?,
|
||||
val newAppVersion: Int?
|
||||
val newAppVersion: Int?,
|
||||
val didReboot: Boolean
|
||||
): AppLogicAction() {
|
||||
companion object {
|
||||
val empty = UpdateDeviceStatusAction(
|
||||
newProtectionLevel = null,
|
||||
newUsageStatsPermissionStatus = null,
|
||||
newNotificationAccessPermission = null,
|
||||
newAppVersion = null,
|
||||
didReboot = false
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
if (newAppVersion != null && newAppVersion < 0) {
|
||||
throw IllegalArgumentException()
|
||||
|
@ -171,6 +182,7 @@ data class IgnoreManipulationAction(
|
|||
val ignoreAppDowngrade: Boolean,
|
||||
val ignoreNotificationAccessManipulation: Boolean,
|
||||
val ignoreUsageStatsAccessManipulation: Boolean,
|
||||
val ignoreReboot: Boolean,
|
||||
val ignoreHadManipulation: Boolean
|
||||
): ParentAction() {
|
||||
init {
|
||||
|
@ -182,6 +194,7 @@ data class IgnoreManipulationAction(
|
|||
(!ignoreAppDowngrade) &&
|
||||
(!ignoreNotificationAccessManipulation) &&
|
||||
(!ignoreUsageStatsAccessManipulation) &&
|
||||
(!ignoreReboot) &&
|
||||
(!ignoreHadManipulation)
|
||||
}
|
||||
|
||||
|
@ -198,6 +211,12 @@ data class SetDeviceUserAction(val deviceId: String, val userId: String): Parent
|
|||
}
|
||||
}
|
||||
|
||||
data class SetConsiderRebootManipulationAction(val deviceId: String, val considerRebootManipulation: Boolean): ParentAction() {
|
||||
init {
|
||||
IdGenerator.assertIdValid(deviceId)
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateCategoryBlockedTimesAction(val categoryId: String, val blockedTimes: ImmutableBitmask): ParentAction() {
|
||||
init {
|
||||
IdGenerator.assertIdValid(categoryId)
|
||||
|
|
|
@ -161,6 +161,12 @@ object LocalDatabaseAppLogicActionDispatcher {
|
|||
}
|
||||
}
|
||||
|
||||
if (action.didReboot && device.considerRebootManipulation) {
|
||||
device = device.copy(
|
||||
manipulationDidReboot = true
|
||||
)
|
||||
}
|
||||
|
||||
database.device().updateDeviceEntry(device)
|
||||
|
||||
if (device.hasActiveManipulationWarning) {
|
||||
|
|
|
@ -271,6 +271,10 @@ object LocalDatabaseParentActionDispatcher {
|
|||
deviceEntry = deviceEntry.copy(highestUsageStatsPermission = deviceEntry.currentUsageStatsPermission)
|
||||
}
|
||||
|
||||
if (action.ignoreReboot) {
|
||||
deviceEntry = deviceEntry.copy(manipulationDidReboot = false)
|
||||
}
|
||||
|
||||
if (action.ignoreHadManipulation) {
|
||||
deviceEntry = deviceEntry.copy(hadManipulation = false)
|
||||
}
|
||||
|
@ -324,6 +328,16 @@ object LocalDatabaseParentActionDispatcher {
|
|||
timezone = action.timezone
|
||||
)
|
||||
}
|
||||
is SetConsiderRebootManipulationAction -> {
|
||||
val deviceEntry = database.device().getDeviceByIdSync(action.deviceId)
|
||||
?: throw IllegalArgumentException("device not found")
|
||||
|
||||
database.device().updateDeviceEntry(
|
||||
deviceEntry.copy(
|
||||
considerRebootManipulation = action.considerRebootManipulation
|
||||
)
|
||||
)
|
||||
}
|
||||
}.let { }
|
||||
|
||||
database.setTransactionSuccessful()
|
||||
|
|
|
@ -270,6 +270,13 @@ class ManageDeviceFragment : Fragment(), FragmentWithCustomTitle {
|
|||
lifecycleOwner = this
|
||||
)
|
||||
|
||||
ManageDeviceRebootManipulationView.bind(
|
||||
view = binding.deviceRebootManipulation,
|
||||
lifecycleOwner = this,
|
||||
deviceEntry = deviceEntry,
|
||||
auth = auth
|
||||
)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ object ManageDeviceManipulation {
|
|||
binding.hasManipulatedDeviceAdmin = device?.manipulationOfProtectionLevel ?: false
|
||||
binding.hasManipulatedUsageStatsAccess = device?.manipulationOfUsageStats ?: false
|
||||
binding.hasManipulatedNotificationAccess = device?.manipulationOfNotificationAccess ?: false
|
||||
binding.hasManipulationReboot = device?.manipulationDidReboot ?: false
|
||||
binding.hasHadManipulation = (device?.hadManipulation ?: false) and (! (device?.hasActiveManipulationWarning ?: false))
|
||||
binding.hasAnyManipulation = device?.hasAnyManipulation ?: false
|
||||
})
|
||||
|
@ -61,6 +62,7 @@ object ManageDeviceManipulation {
|
|||
binding.deviceAdminDisabledCheckbox,
|
||||
binding.usageAccessCheckbox,
|
||||
binding.notificationAccessCheckbox,
|
||||
binding.rebootCheckbox,
|
||||
binding.hadManipulationCheckbox
|
||||
)
|
||||
|
||||
|
@ -79,6 +81,7 @@ object ManageDeviceManipulation {
|
|||
ignoreDeviceAdminManipulationAttempt = binding.deviceAdminDisableAttemptCheckbox.isChecked && binding.hasTriedManipulatingDeviceAdmin == true,
|
||||
ignoreDeviceAdminManipulation = binding.deviceAdminDisabledCheckbox.isChecked && binding.hasManipulatedDeviceAdmin == true,
|
||||
ignoreAppDowngrade = binding.appVersionCheckbox.isChecked && binding.hasManipulatedAppVersion == true,
|
||||
ignoreReboot = binding.rebootCheckbox.isChecked && binding.hasManipulationReboot == true,
|
||||
ignoreHadManipulation = binding.hadManipulationCheckbox.isChecked || (
|
||||
device.hadManipulation and device.hasActiveManipulationWarning
|
||||
),
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Open TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.timelimit.android.ui.manage.device.manage
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import io.timelimit.android.data.model.Device
|
||||
import io.timelimit.android.databinding.ManageDeviceRebootManipulationViewBinding
|
||||
import io.timelimit.android.sync.actions.SetConsiderRebootManipulationAction
|
||||
import io.timelimit.android.ui.main.ActivityViewModel
|
||||
|
||||
object ManageDeviceRebootManipulationView {
|
||||
fun bind(
|
||||
view: ManageDeviceRebootManipulationViewBinding,
|
||||
deviceEntry: LiveData<Device?>,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
auth: ActivityViewModel
|
||||
) {
|
||||
deviceEntry.observe(lifecycleOwner, Observer { device ->
|
||||
val checked = device?.considerRebootManipulation ?: false
|
||||
|
||||
view.checkbox.setOnCheckedChangeListener { _, _ -> }
|
||||
view.checkbox.isChecked = checked
|
||||
view.checkbox.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (isChecked != checked) {
|
||||
if (
|
||||
device != null &&
|
||||
!auth.tryDispatchParentAction(
|
||||
SetConsiderRebootManipulationAction(
|
||||
deviceId = device.id,
|
||||
considerRebootManipulation = isChecked
|
||||
)
|
||||
)
|
||||
) {
|
||||
view.checkbox.isChecked = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -379,6 +379,9 @@
|
|||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<include android:id="@+id/device_reboot_manipulation"
|
||||
layout="@layout/manage_device_reboot_manipulation_view" />
|
||||
|
||||
<include android:id="@+id/troubleshooting_view"
|
||||
layout="@layout/manage_device_troubleshooting_view" />
|
||||
|
||||
|
|
|
@ -38,6 +38,10 @@
|
|||
name="hasManipulatedAppVersion"
|
||||
type="Boolean" />
|
||||
|
||||
<variable
|
||||
name="hasManipulationReboot"
|
||||
type="Boolean" />
|
||||
|
||||
<variable
|
||||
name="hasHadManipulation"
|
||||
type="Boolean" />
|
||||
|
@ -111,6 +115,13 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<CheckBox
|
||||
android:visibility="@{safeUnbox(hasManipulationReboot) ? View.VISIBLE : View.GONE}"
|
||||
android:id="@+id/reboot_checkbox"
|
||||
android:text="@string/manage_device_manipulation_reboot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<CheckBox
|
||||
android:visibility="@{safeUnbox(hasHadManipulation) ? View.VISIBLE : View.GONE}"
|
||||
android:id="@+id/had_manipulation_checkbox"
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Open TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <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
|
||||
app:cardUseCompatPadding="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:textAppearance="?android:textAppearanceLarge"
|
||||
android:text="@string/manage_device_reboot_manipulation_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:textAppearance="?android:textAppearanceMedium"
|
||||
android:text="@string/manage_device_reboot_manipulation_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:text="@string/manage_device_reboot_manipulation_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</layout>
|
|
@ -24,6 +24,7 @@
|
|||
<string name="manage_device_manipulation_usage_stats_access">Nutzungsdatenzugriff</string>
|
||||
<string name="manage_device_manipulation_notification_access">Benachrichtigungszugriff</string>
|
||||
<string name="manage_device_manipulation_app_version">ältere App-Version installiert</string>
|
||||
<string name="manage_device_manipulation_reboot">Gerät wurde neu gestartet</string>
|
||||
<string name="manage_device_manipulation_existed">Es gab eine Manipulation, die wieder beendet wurde</string>
|
||||
<string name="manage_device_manipulation_btn_ignore">Warnungen ignorieren</string>
|
||||
<string name="manage_device_manipulation_toast_nothing_selected">Sie müssen zuerst Warnungen wählen, die Sie ignorieren möchten</string>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Open TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="manage_device_reboot_manipulation_title">Neustart als Manipulation einstufen</string>
|
||||
<string name="manage_device_reboot_manipulation_text">
|
||||
Wenn TimeLimit nach einem Neustart nicht schnell genug startet,
|
||||
dann können Sie hier einstellen,
|
||||
dass das als Manipulation gewertet werden soll.
|
||||
</string>
|
||||
<string name="manage_device_reboot_manipulation_checkbox">Neustart als Manipulation einstufen</string>
|
||||
</resources>
|
|
@ -24,6 +24,7 @@
|
|||
<string name="manage_device_manipulation_usage_stats_access">usage stats access</string>
|
||||
<string name="manage_device_manipulation_notification_access">notification access</string>
|
||||
<string name="manage_device_manipulation_app_version">older App version installed</string>
|
||||
<string name="manage_device_manipulation_reboot">device was rebooted</string>
|
||||
<string name="manage_device_manipulation_existed">there was a manipulation which was stopped again</string>
|
||||
<string name="manage_device_manipulation_btn_ignore">ignore warnings</string>
|
||||
<string name="manage_device_manipulation_toast_nothing_selected">You have to select warnings to ignore first</string>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Open TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="manage_device_reboot_manipulation_title">Consider reboot a manipulation</string>
|
||||
<string name="manage_device_reboot_manipulation_text">
|
||||
If TimeLimit does not start fast enough after a reboot, then you can
|
||||
enable considering that as a manipulation.
|
||||
</string>
|
||||
<string name="manage_device_reboot_manipulation_checkbox">consider reboot a manipulation</string>
|
||||
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue