New concept: annoying after a manipulation

This commit is contained in:
Jonas Lochmann 2022-03-07 01:00:00 +01:00
parent c3b9efa37a
commit 393a9104ae
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
23 changed files with 298 additions and 531 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
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
the Free Software Foundation version 3 of the License. the Free Software Foundation version 3 of the License.
@ -74,15 +74,6 @@
android:taskAffinity=":lock" android:taskAffinity=":lock"
tools:ignore="UnusedAttribute" /> tools:ignore="UnusedAttribute" />
<activity
tools:ignore="UnusedAttribute"
android:excludeFromRecents="true"
android:autoRemoveFromRecents="true"
android:taskAffinity=":manipulationwarning"
android:showOnLockScreen="true"
android:exported="false"
android:name=".ui.manipulation.UnlockAfterManipulationActivity" />
<activity <activity
tools:ignore="UnusedAttribute" tools:ignore="UnusedAttribute"
android:excludeFromRecents="true" android:excludeFromRecents="true"

View file

@ -214,9 +214,6 @@ abstract class ConfigDao {
updateValueSync(ConfigurationItemType.ShownHints, null) updateValueSync(ConfigurationItemType.ShownHints, null)
} }
fun wasDeviceLockedSync() = getValueOfKeySync(ConfigurationItemType.WasDeviceLocked) == "true"
fun setWasDeviceLockedSync(value: Boolean) = updateValueSync(ConfigurationItemType.WasDeviceLocked, if (value) "true" else "false")
fun getLastAppVersionWhichSynced() = getValueOfKeySync(ConfigurationItemType.LastAppVersionWhichSynced) fun getLastAppVersionWhichSynced() = getValueOfKeySync(ConfigurationItemType.LastAppVersionWhichSynced)
fun setLastAppVersionWhichSynced(version: String) = updateValueSync(ConfigurationItemType.LastAppVersionWhichSynced, version) fun setLastAppVersionWhichSynced(version: String) = updateValueSync(ConfigurationItemType.LastAppVersionWhichSynced, version)

View file

@ -84,7 +84,7 @@ enum class ConfigurationItemType {
DeviceAuthToken, DeviceAuthToken,
FullVersionUntil, FullVersionUntil,
ShownHints, ShownHints,
WasDeviceLocked, ObsoleteWasDeviceLocked,
LastAppVersionWhichSynced, LastAppVersionWhichSynced,
LastScreenOnTime, LastScreenOnTime,
ServerMessage, ServerMessage,
@ -135,7 +135,7 @@ object ConfigurationItemTypeUtil {
ConfigurationItemType.DeviceAuthToken, ConfigurationItemType.DeviceAuthToken,
ConfigurationItemType.FullVersionUntil, ConfigurationItemType.FullVersionUntil,
ConfigurationItemType.ShownHints, ConfigurationItemType.ShownHints,
ConfigurationItemType.WasDeviceLocked, ConfigurationItemType.ObsoleteWasDeviceLocked,
ConfigurationItemType.LastAppVersionWhichSynced, ConfigurationItemType.LastAppVersionWhichSynced,
ConfigurationItemType.LastScreenOnTime, ConfigurationItemType.LastScreenOnTime,
ConfigurationItemType.ServerMessage, ConfigurationItemType.ServerMessage,
@ -161,7 +161,7 @@ object ConfigurationItemTypeUtil {
ConfigurationItemType.DeviceAuthToken -> DEVICE_AUTH_TOKEN ConfigurationItemType.DeviceAuthToken -> DEVICE_AUTH_TOKEN
ConfigurationItemType.FullVersionUntil -> FULL_VERSION_UNTIL ConfigurationItemType.FullVersionUntil -> FULL_VERSION_UNTIL
ConfigurationItemType.ShownHints -> SHOWN_HINTS ConfigurationItemType.ShownHints -> SHOWN_HINTS
ConfigurationItemType.WasDeviceLocked -> WAS_DEVICE_LOCKED ConfigurationItemType.ObsoleteWasDeviceLocked -> WAS_DEVICE_LOCKED
ConfigurationItemType.LastAppVersionWhichSynced -> LAST_APP_VERSION_WHICH_SYNCED ConfigurationItemType.LastAppVersionWhichSynced -> LAST_APP_VERSION_WHICH_SYNCED
ConfigurationItemType.LastScreenOnTime -> LAST_SCREEN_ON_TIME ConfigurationItemType.LastScreenOnTime -> LAST_SCREEN_ON_TIME
ConfigurationItemType.ServerMessage -> SERVER_MESSAGE ConfigurationItemType.ServerMessage -> SERVER_MESSAGE
@ -187,7 +187,7 @@ object ConfigurationItemTypeUtil {
DEVICE_AUTH_TOKEN -> ConfigurationItemType.DeviceAuthToken DEVICE_AUTH_TOKEN -> ConfigurationItemType.DeviceAuthToken
FULL_VERSION_UNTIL -> ConfigurationItemType.FullVersionUntil FULL_VERSION_UNTIL -> ConfigurationItemType.FullVersionUntil
SHOWN_HINTS -> ConfigurationItemType.ShownHints SHOWN_HINTS -> ConfigurationItemType.ShownHints
WAS_DEVICE_LOCKED -> ConfigurationItemType.WasDeviceLocked WAS_DEVICE_LOCKED -> ConfigurationItemType.ObsoleteWasDeviceLocked
LAST_APP_VERSION_WHICH_SYNCED -> ConfigurationItemType.LastAppVersionWhichSynced LAST_APP_VERSION_WHICH_SYNCED -> ConfigurationItemType.LastAppVersionWhichSynced
LAST_SCREEN_ON_TIME -> ConfigurationItemType.LastScreenOnTime LAST_SCREEN_ON_TIME -> ConfigurationItemType.LastScreenOnTime
SERVER_MESSAGE -> ConfigurationItemType.ServerMessage SERVER_MESSAGE -> ConfigurationItemType.ServerMessage
@ -227,10 +227,9 @@ object HintsToShow {
} }
object ExperimentalFlags { object ExperimentalFlags {
const val DISABLE_BLOCK_ON_MANIPULATION = 1L private const val OBSOLETE_DISABLE_BLOCK_ON_MANIPULATION = 1L
const val SYSTEM_LEVEL_BLOCKING = 2L const val SYSTEM_LEVEL_BLOCKING = 2L
const val MANIPULATION_ANNOY_USER_ONLY = 4L private const val OBSOLETE_MANIPULATION_ANNOY_USER_ONLY = 4L
const val MANIPULATION_ANNOY_USER = MANIPULATION_ANNOY_USER_ONLY or DISABLE_BLOCK_ON_MANIPULATION // otherwise there would be a conflict between both features
const val IGNORE_SYSTEM_CONNECTION_STATUS = 8L const val IGNORE_SYSTEM_CONNECTION_STATUS = 8L
const val CUSTOM_HOME_SCREEN = 16L const val CUSTOM_HOME_SCREEN = 16L
const val CUSTOM_HOMESCREEN_DELAY = 32L const val CUSTOM_HOMESCREEN_DELAY = 32L

View file

@ -44,7 +44,7 @@ abstract class PlatformIntegration(
abstract fun showOverlayMessage(text: String) abstract fun showOverlayMessage(text: String)
abstract fun showAppLockScreen(currentPackageName: String, currentActivityName: String?) abstract fun showAppLockScreen(currentPackageName: String, currentActivityName: String?)
abstract fun showAnnoyScreen(annoyDuration: Long) abstract fun showAnnoyScreen()
// true = success // true = success
abstract suspend fun muteAudioIfPossible(packageName: String): Boolean abstract suspend fun muteAudioIfPossible(packageName: String): Boolean
abstract fun setShowBlockingOverlay(show: Boolean, blockedElement: String? = null) abstract fun setShowBlockingOverlay(show: Boolean, blockedElement: String? = null)

View file

@ -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
@ -18,7 +18,6 @@ package io.timelimit.android.integration.platform.android
import android.app.admin.DeviceAdminReceiver import android.app.admin.DeviceAdminReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.UserHandle
import io.timelimit.android.R import io.timelimit.android.R
import io.timelimit.android.coroutines.runAsync import io.timelimit.android.coroutines.runAsync
import io.timelimit.android.logic.DefaultAppLogic import io.timelimit.android.logic.DefaultAppLogic
@ -49,16 +48,4 @@ class AdminReceiver: DeviceAdminReceiver() {
return context.getString(R.string.admin_disable_warning) return context.getString(R.string.admin_disable_warning)
} }
override fun onPasswordSucceeded(context: Context, intent: Intent) {
super.onPasswordSucceeded(context, intent)
DefaultAppLogic.with(context).manipulationLogic.reportManualUnlock()
}
override fun onPasswordSucceeded(context: Context, intent: Intent, user: UserHandle) {
super.onPasswordSucceeded(context, intent, user)
DefaultAppLogic.with(context).manipulationLogic.reportManualUnlock()
}
} }

View file

@ -261,8 +261,8 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
LockActivity.start(context, currentPackageName, currentActivityName) LockActivity.start(context, currentPackageName, currentActivityName)
} }
override fun showAnnoyScreen(annoyDuration: Long) { override fun showAnnoyScreen() {
AnnoyActivity.start(context, annoyDuration) AnnoyActivity.start(context)
} }
override suspend fun muteAudioIfPossible(packageName: String): Boolean { override suspend fun muteAudioIfPossible(packageName: String): Boolean {

View file

@ -91,7 +91,7 @@ class DummyIntegration(
launchLockScreenForPackage = currentPackageName launchLockScreenForPackage = currentPackageName
} }
override fun showAnnoyScreen(annoyDuration: Long) { override fun showAnnoyScreen() {
// ignore // ignore
} }

View file

@ -0,0 +1,107 @@
/*
* 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.logic
import android.os.Build
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import io.timelimit.android.BuildConfig
import io.timelimit.android.async.Threads
import io.timelimit.android.integration.platform.ProtectionLevel
import io.timelimit.android.livedata.*
class AnnoyLogic (val appLogic: AppLogic) {
// config
companion object {
private const val ENABLE = !BuildConfig.storeCompilant
private const val TEMP_UNBLOCK_DURATION = 1000 * 45L
private const val TEMP_UNBLOCK_PARENT_DURATION = 1000 * 60 * 10L
private const val MAX_BLOCK_DURATION = 15
private fun manualUnblockDelay(counter: Int): Long {
return if (counter <= 0) 0
else (counter + 4).coerceAtMost(MAX_BLOCK_DURATION).toLong() * 1000 * 60
}
}
// input: clock
private fun now() = appLogic.timeApi.getCurrentUptimeInMillis()
// input: is manipulated (bool)
private val isManipulated = appLogic.deviceEntryIfEnabled.map { it?.hasActiveManipulationWarning ?: false }
private val isDeviceOwner = appLogic.deviceEntryIfEnabled.map { it?.currentProtectionLevel == ProtectionLevel.DeviceOwner }
private val enableAnnoyNow = if (ENABLE) isManipulated.and(isDeviceOwner) else liveDataFromNonNullValue(false)
private val annoyTempDisabled = MutableLiveData<Boolean>().apply { value = false }
private val annoyTempDisabledSetFalse: Runnable = Runnable {
annoyTempDisabled.value = false
isManipulated.removeObserver(resetTempDisabledObserver)
}
private val resetTempDisabledObserver = Observer<Boolean> { isManipulated ->
if (!isManipulated) {
Threads.mainThreadHandler.removeCallbacks(annoyTempDisabledSetFalse)
Threads.mainThreadHandler.post(annoyTempDisabledSetFalse)
}
}
// output: should block right now
val shouldAnnoyRightNow = enableAnnoyNow.and(annoyTempDisabled.invert())
// state: block duration
private var nextManualUnblockTimestamp = now()
private var manualUnblockCounter = 0
// output: duration until next manual unblock
val nextManualUnblockCountdown = liveDataFromFunction {
(nextManualUnblockTimestamp - now()).coerceAtLeast(0)
}
// input: trigger temp unblock (event)
fun doManualTempUnlock() {
val now = now()
if (now < nextManualUnblockTimestamp) return
if (annoyTempDisabled.value == true) return
// eventually reset
if (nextManualUnblockTimestamp + manualUnblockDelay(manualUnblockCounter) < now) {
manualUnblockCounter = 1
} else {
manualUnblockCounter += 1
}
nextManualUnblockTimestamp = now + manualUnblockDelay(manualUnblockCounter)
enableTempDisabled(TEMP_UNBLOCK_DURATION)
}
// input: trigger temp unblock by parents (event)
fun doParentTempUnlock() {
manualUnblockCounter = 0
nextManualUnblockTimestamp = now()
enableTempDisabled(TEMP_UNBLOCK_PARENT_DURATION)
}
private fun enableTempDisabled(duration: Long) {
annoyTempDisabled.value = true
Threads.mainThreadHandler.removeCallbacks(annoyTempDisabledSetFalse)
Threads.mainThreadHandler.postDelayed(annoyTempDisabledSetFalse, duration)
isManipulated.observeForever(resetTempDisabledObserver)
}
}

View file

@ -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
@ -104,8 +104,8 @@ class AppLogic(
WatchdogLogic(this) WatchdogLogic(this)
} }
val manipulationLogic = ManipulationLogic(this)
val suspendAppsLogic = SuspendAppsLogic(this) val suspendAppsLogic = SuspendAppsLogic(this)
val annoyLogic = AnnoyLogic(this)
fun shutdown() { fun shutdown() {
enable.value = false enable.value = false

View file

@ -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
@ -54,6 +54,7 @@ import io.timelimit.android.work.PeriodicSyncInBackgroundWorker
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import java.util.* import java.util.*
class BackgroundTaskLogic(val appLogic: AppLogic) { class BackgroundTaskLogic(val appLogic: AppLogic) {
@ -983,65 +984,15 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
} }
} }
// first time: annoy for 20 seconds; free for 5 minutes
// second time: annoy for 30 seconds; free for 2 minutes
// third time: annoy for 1 minute; free for 1 minute
// then: annoy for 2 minutes; free for 1 minute
private suspend fun annoyUserOnManipulationLoop() { private suspend fun annoyUserOnManipulationLoop() {
val isManipulated = appLogic.deviceEntryIfEnabled.map { it?.hasActiveManipulationWarning ?: false } val shouldAnnoyNow = appLogic.annoyLogic.shouldAnnoyRightNow
val enableAnnoy = appLogic.database.config().isExperimentalFlagsSetAsync(ExperimentalFlags.MANIPULATION_ANNOY_USER)
val timeLimitNotActive = IsAppInForeground.isRunning.invert()
var counter = 0
var globalCounter = 0
val shouldAnnoyNow = isManipulated.and(enableAnnoy).and(timeLimitNotActive)
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "delay before enabling annoying")
}
delay(1000 * 15)
while (true) { while (true) {
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "wait until should annoy")
}
shouldAnnoyNow.waitUntilValueMatches { it == true } shouldAnnoyNow.waitUntilValueMatches { it == true }
appLogic.platformIntegration.showAnnoyScreen()
val annoyDurationInSeconds = when (counter) { // bring into foreground all five seconds
0 -> 20 withTimeoutOrNull(5000L) { shouldAnnoyNow.waitUntilValueMatches { it == false } }
1 -> 30
2 -> 60
else -> 120
}
val freeDurationInSeconds = when (counter) {
0 -> 5 * 60
1 -> 2 * 60
else -> 60
}
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "annoy for $annoyDurationInSeconds seconds; free for $freeDurationInSeconds seconds")
}
appLogic.platformIntegration.showAnnoyScreen(annoyDurationInSeconds.toLong())
counter++
globalCounter++
// reset counter if there was nothing for one hour
val globalCounterBackup = globalCounter
appLogic.timeApi.runDelayed(Runnable {
if (globalCounter == globalCounterBackup) {
counter = 0
}
}, 1000 * 60 * 60 /* 1 hour */)
// wait before annoying next time
delay((annoyDurationInSeconds + freeDurationInSeconds) * 1000L)
} }
} }
} }

View file

@ -1,99 +0,0 @@
/*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.timelimit.android.logic
import android.content.Intent
import android.os.Build
import io.timelimit.android.async.Threads
import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.coroutines.runAsync
import io.timelimit.android.data.model.ExperimentalFlags
import io.timelimit.android.ui.MainActivity
import io.timelimit.android.ui.manipulation.UnlockAfterManipulationActivity
class ManipulationLogic(val appLogic: AppLogic) {
companion object {
private const val LOG_TAG = "ManipulationLogic"
}
init {
runAsync {
Threads.database.executeAndWait {
if (appLogic.database.config().wasDeviceLockedSync()) {
showManipulationScreen()
}
}
}
}
fun lockDeviceSync() {
if (!appLogic.database.config().isExperimentalFlagsSetSync(ExperimentalFlags.DISABLE_BLOCK_ON_MANIPULATION)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (appLogic.platformIntegration.setLockTaskPackages(listOf(appLogic.context.packageName))) {
appLogic.database.config().setWasDeviceLockedSync(true)
showManipulationScreen()
}
} else {
if (lockDeviceSync("12timelimit34")) {
appLogic.database.config().setWasDeviceLockedSync(true)
showManipulationScreen()
}
}
}
}
private fun lockDeviceSync(password: String) = appLogic.platformIntegration.trySetLockScreenPassword(password)
private fun showManipulationScreen() {
appLogic.context.startActivity(
Intent(appLogic.context, UnlockAfterManipulationActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}
fun showManipulationUnlockedScreen() {
appLogic.context.startActivity(
Intent(appLogic.context, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}
fun unlockDeviceSync() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
appLogic.database.config().setWasDeviceLockedSync(false)
} else {
if (lockDeviceSync("")) {
appLogic.database.config().setWasDeviceLockedSync(false)
}
}
}
fun reportManualUnlock() {
Threads.database.execute {
appLogic.database.runInTransaction {
if (appLogic.database.config().getOwnDeviceIdSync() != null) {
if (appLogic.database.config().wasDeviceLockedSync()) {
appLogic.database.config().setWasDeviceLockedSync(false)
showManipulationUnlockedScreen()
}
}
}
}
}
}

View file

@ -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
@ -27,7 +27,6 @@ import io.timelimit.android.data.model.PendingSyncActionType
import io.timelimit.android.data.model.UserType import io.timelimit.android.data.model.UserType
import io.timelimit.android.integration.platform.PlatformIntegration import io.timelimit.android.integration.platform.PlatformIntegration
import io.timelimit.android.logic.AppLogic import io.timelimit.android.logic.AppLogic
import io.timelimit.android.logic.ManipulationLogic
import io.timelimit.android.sync.SyncUtil import io.timelimit.android.sync.SyncUtil
import io.timelimit.android.sync.actions.* import io.timelimit.android.sync.actions.*
import io.timelimit.android.sync.actions.dispatch.LocalDatabaseAppLogicActionDispatcher import io.timelimit.android.sync.actions.dispatch.LocalDatabaseAppLogicActionDispatcher
@ -48,7 +47,6 @@ object ApplyActionUtil {
action = action, action = action,
database = appLogic.database, database = appLogic.database,
syncUtil = appLogic.syncUtil, syncUtil = appLogic.syncUtil,
manipulationLogic = appLogic.manipulationLogic,
ignoreIfDeviceIsNotConfigured = ignoreIfDeviceIsNotConfigured ignoreIfDeviceIsNotConfigured = ignoreIfDeviceIsNotConfigured
) )
} }
@ -57,7 +55,6 @@ object ApplyActionUtil {
action: AppLogicAction, action: AppLogicAction,
database: Database, database: Database,
syncUtil: SyncUtil, syncUtil: SyncUtil,
manipulationLogic: ManipulationLogic,
ignoreIfDeviceIsNotConfigured: Boolean ignoreIfDeviceIsNotConfigured: Boolean
) { ) {
// uncomment this if you need to know what's dispatching an action // uncomment this if you need to know what's dispatching an action
@ -79,7 +76,7 @@ object ApplyActionUtil {
return@runInTransaction return@runInTransaction
} }
LocalDatabaseAppLogicActionDispatcher.dispatchAppLogicActionSync(action, ownDeviceId!!, database, manipulationLogic) LocalDatabaseAppLogicActionDispatcher.dispatchAppLogicActionSync(action, ownDeviceId!!, database)
if (isSyncEnabled(database)) { if (isSyncEnabled(database)) {
if (action is AddUsedTimeActionVersion2) { if (action is AddUsedTimeActionVersion2) {

View file

@ -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,13 +24,12 @@ import io.timelimit.android.integration.platform.NewPermissionStatusUtil
import io.timelimit.android.integration.platform.ProtectionLevelUtil import io.timelimit.android.integration.platform.ProtectionLevelUtil
import io.timelimit.android.integration.platform.RuntimePermissionStatusUtil import io.timelimit.android.integration.platform.RuntimePermissionStatusUtil
import io.timelimit.android.logic.BackgroundTaskLogic import io.timelimit.android.logic.BackgroundTaskLogic
import io.timelimit.android.logic.ManipulationLogic
import io.timelimit.android.sync.actions.* import io.timelimit.android.sync.actions.*
object LocalDatabaseAppLogicActionDispatcher { object LocalDatabaseAppLogicActionDispatcher {
private const val LOG_TAG = "AppLogicAction" private const val LOG_TAG = "AppLogicAction"
fun dispatchAppLogicActionSync(action: AppLogicAction, deviceId: String, database: Database, manipulationLogic: ManipulationLogic) { fun dispatchAppLogicActionSync(action: AppLogicAction, deviceId: String, database: Database) {
DatabaseValidation.assertDeviceExists(database, deviceId) DatabaseValidation.assertDeviceExists(database, deviceId)
database.runInTransaction { database.runInTransaction {
@ -308,10 +307,6 @@ object LocalDatabaseAppLogicActionDispatcher {
database.device().updateDeviceEntry(device) database.device().updateDeviceEntry(device)
if (device.hasActiveManipulationWarning) {
manipulationLogic.lockDeviceSync()
}
null null
} }
is TriedDisablingDeviceAdminAction -> { is TriedDisablingDeviceAdminAction -> {
@ -323,8 +318,6 @@ object LocalDatabaseAppLogicActionDispatcher {
) )
) )
manipulationLogic.lockDeviceSync()
null null
} }
is SignOutAtDeviceAction -> { is SignOutAtDeviceAction -> {

View file

@ -116,14 +116,6 @@ data class DiagnoseExperimentalFlagItem(
) { ) {
companion object { companion object {
val items = listOf( val items = listOf(
DiagnoseExperimentalFlagItem(
label = R.string.diagnose_exf_lom,
enableFlags = ExperimentalFlags.DISABLE_BLOCK_ON_MANIPULATION,
disableFlags = ExperimentalFlags.DISABLE_BLOCK_ON_MANIPULATION,
enable = { flags ->
(!BuildConfig.storeCompilant) and ((flags and ExperimentalFlags.MANIPULATION_ANNOY_USER_ONLY) == 0L)
}
),
DiagnoseExperimentalFlagItem( DiagnoseExperimentalFlagItem(
label = R.string.diagnose_exf_slb, label = R.string.diagnose_exf_slb,
enableFlags = ExperimentalFlags.SYSTEM_LEVEL_BLOCKING, enableFlags = ExperimentalFlags.SYSTEM_LEVEL_BLOCKING,
@ -136,12 +128,6 @@ data class DiagnoseExperimentalFlagItem(
disableFlags = ExperimentalFlags.NETWORKTIME_AT_SYSTEMLEVEL, disableFlags = ExperimentalFlags.NETWORKTIME_AT_SYSTEMLEVEL,
enable = { !BuildConfig.storeCompilant } enable = { !BuildConfig.storeCompilant }
), ),
DiagnoseExperimentalFlagItem(
label = R.string.diagnose_exf_mau,
enableFlags = ExperimentalFlags.MANIPULATION_ANNOY_USER,
disableFlags = ExperimentalFlags.MANIPULATION_ANNOY_USER_ONLY,
enable = { !BuildConfig.storeCompilant }
),
DiagnoseExperimentalFlagItem( DiagnoseExperimentalFlagItem(
label = R.string.diagnose_exf_isc, label = R.string.diagnose_exf_isc,
enableFlags = ExperimentalFlags.IGNORE_SYSTEM_CONNECTION_STATUS, enableFlags = ExperimentalFlags.IGNORE_SYSTEM_CONNECTION_STATUS,

View file

@ -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
@ -45,6 +45,12 @@ class NewLoginFragment: DialogFragment() {
companion object { companion object {
const val SHOW_ON_LOCKSCREEN = "showOnLockscreen" const val SHOW_ON_LOCKSCREEN = "showOnLockscreen"
fun newInstance(showOnLockscreen: Boolean) = NewLoginFragment().apply {
arguments = Bundle().apply {
putBoolean(SHOW_ON_LOCKSCREEN, showOnLockscreen)
}
}
private const val SELECTED_USER_ID = "selectedUserId" private const val SELECTED_USER_ID = "selectedUserId"
private const val USER_LIST = 0 private const val USER_LIST = 0
private const val PARENT_AUTH = 1 private const val PARENT_AUTH = 1

View file

@ -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
@ -19,38 +19,43 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import io.timelimit.android.R import io.timelimit.android.R
import io.timelimit.android.data.model.UserType
import io.timelimit.android.databinding.AnnoyActivityBinding import io.timelimit.android.databinding.AnnoyActivityBinding
import io.timelimit.android.extensions.showSafe
import io.timelimit.android.livedata.map import io.timelimit.android.livedata.map
import io.timelimit.android.logic.DefaultAppLogic import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.backdoor.BackdoorDialogFragment
import io.timelimit.android.ui.login.NewLoginFragment
import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.main.ActivityViewModelHolder
import io.timelimit.android.ui.manage.device.manage.ManipulationWarningTypeLabel import io.timelimit.android.ui.manage.device.manage.ManipulationWarningTypeLabel
import io.timelimit.android.ui.manage.device.manage.ManipulationWarnings import io.timelimit.android.ui.manage.device.manage.ManipulationWarnings
import io.timelimit.android.util.TimeTextUtil import io.timelimit.android.util.TimeTextUtil
class AnnoyActivity : AppCompatActivity() { class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder {
companion object { companion object {
private const val EXTRA_DURATION = "duration" fun start(context: Context) {
fun start(context: Context, duration: Long) {
context.startActivity( context.startActivity(
Intent(context, AnnoyActivity::class.java) Intent(context, AnnoyActivity::class.java)
.putExtra(EXTRA_DURATION, duration)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
) )
} }
} }
private val model: ActivityViewModel by viewModels()
override var ignoreStop: Boolean = false
override val showPasswordRecovery: Boolean = false
override fun getActivityViewModel() = model
override fun showAuthenticationScreen() { NewLoginFragment.newInstance(showOnLockscreen = true).showSafe(supportFragmentManager, "nlf") }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val duration = intent!!.getLongExtra(EXTRA_DURATION, 10)
val model = ViewModelProviders.of(this).get(AnnoyModel::class.java)
val logic = DefaultAppLogic.with(this) val logic = DefaultAppLogic.with(this)
val binding = AnnoyActivityBinding.inflate(layoutInflater) val binding = AnnoyActivityBinding.inflate(layoutInflater)
@ -62,37 +67,36 @@ class AnnoyActivity : AppCompatActivity() {
} }
} }
model.init(duration = duration) logic.annoyLogic.shouldAnnoyRightNow.observe(this) { shouldRun ->
model.countdown.observe(this, Observer { if (!shouldRun) shutdown()
if (it == 0L) {
shutdown()
} }
binding.annoyTimer.setText( logic.annoyLogic.nextManualUnblockCountdown.observe(this) { countdown ->
getString(R.string.annoy_timer, TimeTextUtil.seconds(it.toInt(), this@AnnoyActivity)) binding.canRequestUnlock = countdown == 0L
) binding.countdownText = getString(R.string.annoy_timer, TimeTextUtil.seconds((countdown / 1000).toInt(), this@AnnoyActivity))
}) }
logic.deviceEntry.map { logic.deviceEntry.map {
val reasonItems = (it?.let { ManipulationWarnings.getFromDevice(it) } ?: ManipulationWarnings.empty) val reasonItems = (it?.let { ManipulationWarnings.getFromDevice(it) } ?: ManipulationWarnings.empty)
.current .current
.map { .map { getString(ManipulationWarningTypeLabel.getLabel(it)) }
getString(ManipulationWarningTypeLabel.getLabel(it))
}
if (reasonItems.isEmpty()) { if (reasonItems.isEmpty()) {
null null
} else { } else {
getString(R.string.annoy_reason, reasonItems.joinToString(separator = ", ")) getString(R.string.annoy_reason, reasonItems.joinToString(separator = ", "))
} }
}.observe(this, Observer { }.observe(this) { binding.reasonText = it }
if (it.isNullOrEmpty()) {
binding.annoyReason.visibility = View.GONE binding.unlockTemporarilyButton.setOnClickListener { logic.annoyLogic.doManualTempUnlock() }
} else { binding.parentUnlockButton.setOnClickListener { showAuthenticationScreen() }
binding.annoyReason.visibility = View.VISIBLE binding.useBackdoorButton.setOnClickListener { BackdoorDialogFragment().show(supportFragmentManager) }
binding.annoyReason.setText(it)
model.authenticatedUser.observe(this) { user ->
if (user?.second?.type == UserType.Parent) {
logic.annoyLogic.doParentTempUnlock()
}
} }
})
} }
private fun shutdown() { private fun shutdown() {

View file

@ -1,48 +0,0 @@
/*
* 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.manipulation
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import io.timelimit.android.coroutines.runAsync
import io.timelimit.android.livedata.castDown
import kotlinx.coroutines.delay
class AnnoyModel: ViewModel() {
private val countdownInternal = MutableLiveData<Long>()
private var hadInit = false
val countdown = countdownInternal.castDown()
fun init(duration: Long) {
if (!hadInit) {
hadInit = true
countdownInternal.value = duration
runAsync {
var timer = duration
while (timer >= 0) {
delay(1000)
timer--
countdownInternal.value = timer
}
}
}
}
}

View file

@ -1,107 +0,0 @@
/*
* TimeLimit Copyright <C> 2019 - 2021 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.manipulation
import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import io.timelimit.android.async.Threads
import io.timelimit.android.data.model.UserType
import io.timelimit.android.databinding.ActivityUnlockAfterManipulationBinding
import io.timelimit.android.extensions.showSafe
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.backdoor.BackdoorDialogFragment
import io.timelimit.android.ui.login.NewLoginFragment
import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.main.ActivityViewModelHolder
class UnlockAfterManipulationActivity : AppCompatActivity(), ActivityViewModelHolder {
private val model: ActivityViewModel by lazy {
ViewModelProviders.of(this).get(ActivityViewModel::class.java)
}
override var ignoreStop: Boolean = false
override val showPasswordRecovery: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityUnlockAfterManipulationBinding.inflate(layoutInflater)
setContentView(binding.root)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startLockTask()
} else {
window.addFlags(
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
)
}
model.authenticatedUser.observe(this, Observer {
user ->
if (user != null && user.second.type == UserType.Parent) {
onAuthSuccess()
}
})
model.logic.deviceId.observe(this, Observer {
if (it.isNullOrEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
stopLockTask()
}
DefaultAppLogic.with(this).appSetupLogic.resetAppCompletely()
finish()
}
})
binding.authBtn.setOnClickListener { showAuthenticationScreen() }
binding.useBackdoor.setOnClickListener { BackdoorDialogFragment().show(supportFragmentManager) }
}
override fun showAuthenticationScreen() {
NewLoginFragment().apply {
arguments = Bundle().apply {
putBoolean(NewLoginFragment.SHOW_ON_LOCKSCREEN, true)
}
}.showSafe(supportFragmentManager, "nlf")
}
override fun getActivityViewModel() = model
private fun onAuthSuccess() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
stopLockTask()
}
val appLogic = DefaultAppLogic.with(this@UnlockAfterManipulationActivity)
Threads.database.execute {
appLogic.manipulationLogic.unlockDeviceSync()
}
appLogic.manipulationLogic.showManipulationUnlockedScreen()
finish()
}
}

View file

@ -1,63 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.timelimit.android.ui.manipulation.UnlockAfterManipulationActivity">
<RelativeLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp">
<LinearLayout
android:layout_centerVertical="true"
android:orientation="vertical"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:gravity="center_horizontal"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manipulation_activity_intro"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:layout_marginTop="8dp"
android:layout_gravity="center_horizontal"
android:id="@+id/auth_btn"
android:text="@string/add_user_authentication_required_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</RelativeLayout>
<Button
style="?materialButtonOutlinedStyle"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_gravity="center_horizontal"
android:id="@+id/use_backdoor"
android:text="@string/backdoor_start_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View file

@ -1,6 +1,40 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <!--
xmlns:tools="http://schemas.android.com/tools" 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:tools="http://schemas.android.com/tools">
<data>
<variable
name="reasonText"
type="String" />
<variable
name="countdownText"
type="String" />
<variable
name="canRequestUnlock"
type="boolean" />
<import type="android.text.TextUtils" />
<import type="android.view.View" />
</data>
<LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -15,8 +49,9 @@
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<RelativeLayout <RelativeLayout
android:layout_weight="1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="0dp">
<LinearLayout <LinearLayout
android:layout_margin="8dp" android:layout_margin="8dp"
android:orientation="vertical" android:orientation="vertical"
@ -29,20 +64,59 @@
android:textAppearance="?android:textAppearanceMedium" android:textAppearance="?android:textAppearanceMedium"
android:gravity="center" android:gravity="center"
android:id="@+id/annoy_reason" android:id="@+id/annoy_reason"
android:text="@string/annoy_reason" tools:text="@string/annoy_reason"
android:text="@{reasonText}"
android:visibility="@{TextUtils.isEmpty(reasonText) ? View.GONE : View.VISIBLE}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<LinearLayout
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:enabled="@{canRequestUnlock}"
android:id="@+id/unlock_temporarily_button"
android:layout_gravity="center_horizontal"
android:text="@string/annoy_unlock_temp_button"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
style="?materialButtonOutlinedStyle"
android:id="@+id/parent_unlock_button"
android:layout_gravity="center_horizontal"
android:text="@string/annoy_unlock_parent_button"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView <TextView
android:layout_margin="4dp" android:layout_margin="4dp"
android:textAppearance="?android:textAppearanceMedium" android:textAppearance="?android:textAppearanceSmall"
android:gravity="center" android:gravity="center"
android:id="@+id/annoy_timer" android:id="@+id/annoy_timer"
android:text="@string/annoy_timer" tools:text="@string/annoy_timer"
android:text="@{countdownText}"
android:visibility="@{canRequestUnlock ? View.INVISIBLE : View.VISIBLE}"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>
</LinearLayout> <Button
style="?materialButtonOutlinedStyle"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_gravity="center_horizontal"
android:id="@+id/use_backdoor_button"
android:text="@string/backdoor_start_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>

View file

@ -109,11 +109,13 @@
<string name="add_user_authentication_required_btn">Anmelden</string> <string name="add_user_authentication_required_btn">Anmelden</string>
<string name="annoy_info"> <string name="annoy_info">
TimeLimit wurde manipuliert. Deshalb wird das Gerät für einen Moment gesperrt. TimeLimit wurde manipuliert. Deshalb wurde das Gerät gesperrt.
</string> </string>
<string name="annoy_timer"> <string name="annoy_timer">
Das Gerät wird in %s entsperrt. Das Gerät kann in %s entsperrt werden.
</string> </string>
<string name="annoy_unlock_temp_button">kurz entsperren</string>
<string name="annoy_unlock_parent_button">länger entsperren</string>
<string name="annoy_reason"> <string name="annoy_reason">
Folgendes wurde manipuliert: %s Folgendes wurde manipuliert: %s
</string> </string>
@ -502,9 +504,7 @@
<string name="diagnose_fga_query_range_min">Minimal (Standard)</string> <string name="diagnose_fga_query_range_min">Minimal (Standard)</string>
<string name="diagnose_exf_title">Experimentelle Parameter</string> <string name="diagnose_exf_title">Experimentelle Parameter</string>
<string name="diagnose_exf_lom">Sperrung nach Manipulationen deaktivieren</string>
<string name="diagnose_exf_slb">Apps auf Systemebene sperren</string> <string name="diagnose_exf_slb">Apps auf Systemebene sperren</string>
<string name="diagnose_exf_mau">Nutzer nach Manipulation nerven</string>
<string name="diagnose_exf_isc">Systemverbindungsstatus ignorieren</string> <string name="diagnose_exf_isc">Systemverbindungsstatus ignorieren</string>
<string name="diagnose_exf_chs">TimeLimit als Startbildschirm</string> <string name="diagnose_exf_chs">TimeLimit als Startbildschirm</string>
<string name="diagnose_exf_chd">Startbildschirmweiterleitung verzögern</string> <string name="diagnose_exf_chd">Startbildschirmweiterleitung verzögern</string>
@ -1080,8 +1080,6 @@
kann nicht mit einem weiteren Benutzer verknüpft werden kann nicht mit einem weiteren Benutzer verknüpft werden
</string> </string>
<string name="manipulation_activity_intro">Es gab eine Manipulation. Deswegen muss dieses Gerät durch ein Elternteil freigegeben werden.</string>
<string name="must_read_child_manipulation"> <string name="must_read_child_manipulation">
Da es manchmal nicht wahrgenommen wird: Da es manchmal nicht wahrgenommen wird:
\n1. In den Standardeinstellungen kann TimeLimit ganz leicht, \n1. In den Standardeinstellungen kann TimeLimit ganz leicht,

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
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
the Free Software Foundation version 3 of the License. the Free Software Foundation version 3 of the License.
@ -428,9 +428,7 @@ por exemplo, quando você quer adicionar o uso de tela
<string name="diagnose_fga_query_range_min">Mínimo (Padrão)</string> <string name="diagnose_fga_query_range_min">Mínimo (Padrão)</string>
<string name="diagnose_exf_title">Marcado res experimentais</string> <string name="diagnose_exf_title">Marcado res experimentais</string>
<string name="diagnose_exf_lom">Desativar o travamento após manipulações</string>
<string name="diagnose_exf_slb">Bloqueio de Aplicativos no nível de sistema</string> <string name="diagnose_exf_slb">Bloqueio de Aplicativos no nível de sistema</string>
<string name="diagnose_exf_mau">Irrita o usuário após a manipulação</string>
<string name="diagnose_exf_isc">Ignorar o status da conexão do sistema</string> <string name="diagnose_exf_isc">Ignorar o status da conexão do sistema</string>
<string name="diagnose_exf_chs">TimeLimit como lançador</string> <string name="diagnose_exf_chs">TimeLimit como lançador</string>
<string name="diagnose_exf_chd">Atraso no redirecionamento do lançador</string> <string name="diagnose_exf_chd">Atraso no redirecionamento do lançador</string>
@ -949,8 +947,6 @@ Esta %s só é permitida em algumas redes, mas nenhuma conexão com tal rede foi
não pode ser vinculada a um usuário adicional não pode ser vinculada a um usuário adicional
</string> </string>
<string name="manipulation_activity_intro">Houve uma manipulação. Devido a isso, este dispositivo deve ser desbloqueado por os Pais.</string>
<string name="must_read_child_manipulation"> <string name="must_read_child_manipulation">
Porque alguns usuários não se dão conta disso: Porque alguns usuários não se dão conta disso:
\n1st: Nas configurações padrão, TimeLimit pode ser facilmente desinstalado, mesmo por crianças. \n1st: Nas configurações padrão, TimeLimit pode ser facilmente desinstalado, mesmo por crianças.

View file

@ -153,11 +153,13 @@
<string name="add_user_authentication_required_btn">Authenticate</string> <string name="add_user_authentication_required_btn">Authenticate</string>
<string name="annoy_info"> <string name="annoy_info">
There was a manipulation of TimeLimit. Due to that, this device is locked for a moment. There was a manipulation of TimeLimit. Due to that, this device is locked now.
</string> </string>
<string name="annoy_timer"> <string name="annoy_timer">
The device will be unlocked in %s. The device can be unlocked in %s.
</string> </string>
<string name="annoy_unlock_temp_button">Unlock for a moment</string>
<string name="annoy_unlock_parent_button">Unlock a bit longer</string>
<string name="annoy_reason"> <string name="annoy_reason">
This was manipulated: %s This was manipulated: %s
</string> </string>
@ -555,9 +557,7 @@
<string name="diagnose_fga_query_range_min">Minimum (Default)</string> <string name="diagnose_fga_query_range_min">Minimum (Default)</string>
<string name="diagnose_exf_title">Experimental flags</string> <string name="diagnose_exf_title">Experimental flags</string>
<string name="diagnose_exf_lom">Disable locking after manipulations</string>
<string name="diagnose_exf_slb">Block Apps at system level</string> <string name="diagnose_exf_slb">Block Apps at system level</string>
<string name="diagnose_exf_mau">Annoy user after manipulation</string>
<string name="diagnose_exf_isc">Ignore system connection status</string> <string name="diagnose_exf_isc">Ignore system connection status</string>
<string name="diagnose_exf_chs">TimeLimit as launcher</string> <string name="diagnose_exf_chs">TimeLimit as launcher</string>
<string name="diagnose_exf_chd">Delay launcher redirection</string> <string name="diagnose_exf_chd">Delay launcher redirection</string>
@ -1125,8 +1125,6 @@
can not be linked to an additional user can not be linked to an additional user
</string> </string>
<string name="manipulation_activity_intro">There was a manipulation. Due to that, this device must be unlocked by a parent.</string>
<string name="must_read_child_manipulation"> <string name="must_read_child_manipulation">
Because some users do not realize it: Because some users do not realize it:
\n1st: In the default settings, TimeLimit can be removed easily, even by children. \n1st: In the default settings, TimeLimit can be removed easily, even by children.