mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 09:49:25 +02:00
Allow configuring foreground app query interval
This commit is contained in:
parent
15976189fa
commit
d3403b6866
21 changed files with 261 additions and 13 deletions
|
@ -24,6 +24,7 @@ import io.timelimit.android.data.model.ConfigurationItemTypeConverter
|
||||||
import io.timelimit.android.data.model.ConfigurationItemTypeUtil
|
import io.timelimit.android.data.model.ConfigurationItemTypeUtil
|
||||||
import io.timelimit.android.livedata.ignoreUnchanged
|
import io.timelimit.android.livedata.ignoreUnchanged
|
||||||
import io.timelimit.android.livedata.map
|
import io.timelimit.android.livedata.map
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
@TypeConverters(ConfigurationItemTypeConverter::class)
|
@TypeConverters(ConfigurationItemTypeConverter::class)
|
||||||
|
@ -208,4 +209,13 @@ abstract class ConfigDao {
|
||||||
fun getCustomServerUrlSync() = getValueOfKeySync(ConfigurationItemType.CustomServerUrl) ?: ""
|
fun getCustomServerUrlSync() = getValueOfKeySync(ConfigurationItemType.CustomServerUrl) ?: ""
|
||||||
fun getCustomServerUrlAsync() = getValueOfKeyAsync(ConfigurationItemType.CustomServerUrl).map { it ?: "" }
|
fun getCustomServerUrlAsync() = getValueOfKeyAsync(ConfigurationItemType.CustomServerUrl).map { it ?: "" }
|
||||||
fun setCustomServerUrlSync(url: String) = updateValueSync(ConfigurationItemType.CustomServerUrl, url)
|
fun setCustomServerUrlSync(url: String) = updateValueSync(ConfigurationItemType.CustomServerUrl, url)
|
||||||
|
|
||||||
|
fun getForegroundAppQueryIntervalAsync(): LiveData<Long> = getValueOfKeyAsync(ConfigurationItemType.ForegroundAppQueryRange).map { (it ?: "0").toLong() }
|
||||||
|
fun setForegroundAppQueryIntervalSync(interval: Long) {
|
||||||
|
if (interval < 0) {
|
||||||
|
throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateValueSync(ConfigurationItemType.ForegroundAppQueryRange, interval.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,8 @@ enum class ConfigurationItemType {
|
||||||
LastAppVersionWhichSynced,
|
LastAppVersionWhichSynced,
|
||||||
LastScreenOnTime,
|
LastScreenOnTime,
|
||||||
ServerMessage,
|
ServerMessage,
|
||||||
CustomServerUrl
|
CustomServerUrl,
|
||||||
|
ForegroundAppQueryRange
|
||||||
}
|
}
|
||||||
|
|
||||||
object ConfigurationItemTypeUtil {
|
object ConfigurationItemTypeUtil {
|
||||||
|
@ -104,6 +105,7 @@ object ConfigurationItemTypeUtil {
|
||||||
private const val LAST_SCREEN_ON_TIME = 11
|
private const val LAST_SCREEN_ON_TIME = 11
|
||||||
private const val SERVER_MESSAGE = 12
|
private const val SERVER_MESSAGE = 12
|
||||||
private const val CUSTOM_SERVER_URL = 13
|
private const val CUSTOM_SERVER_URL = 13
|
||||||
|
private const val FOREGROUND_APP_QUERY_RANGE = 14
|
||||||
|
|
||||||
val TYPES = listOf(
|
val TYPES = listOf(
|
||||||
ConfigurationItemType.OwnDeviceId,
|
ConfigurationItemType.OwnDeviceId,
|
||||||
|
@ -117,7 +119,8 @@ object ConfigurationItemTypeUtil {
|
||||||
ConfigurationItemType.LastAppVersionWhichSynced,
|
ConfigurationItemType.LastAppVersionWhichSynced,
|
||||||
ConfigurationItemType.LastScreenOnTime,
|
ConfigurationItemType.LastScreenOnTime,
|
||||||
ConfigurationItemType.ServerMessage,
|
ConfigurationItemType.ServerMessage,
|
||||||
ConfigurationItemType.CustomServerUrl
|
ConfigurationItemType.CustomServerUrl,
|
||||||
|
ConfigurationItemType.ForegroundAppQueryRange
|
||||||
)
|
)
|
||||||
|
|
||||||
fun serialize(value: ConfigurationItemType) = when(value) {
|
fun serialize(value: ConfigurationItemType) = when(value) {
|
||||||
|
@ -133,6 +136,7 @@ object ConfigurationItemTypeUtil {
|
||||||
ConfigurationItemType.LastScreenOnTime -> LAST_SCREEN_ON_TIME
|
ConfigurationItemType.LastScreenOnTime -> LAST_SCREEN_ON_TIME
|
||||||
ConfigurationItemType.ServerMessage -> SERVER_MESSAGE
|
ConfigurationItemType.ServerMessage -> SERVER_MESSAGE
|
||||||
ConfigurationItemType.CustomServerUrl -> CUSTOM_SERVER_URL
|
ConfigurationItemType.CustomServerUrl -> CUSTOM_SERVER_URL
|
||||||
|
ConfigurationItemType.ForegroundAppQueryRange -> FOREGROUND_APP_QUERY_RANGE
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parse(value: Int) = when(value) {
|
fun parse(value: Int) = when(value) {
|
||||||
|
@ -148,6 +152,7 @@ object ConfigurationItemTypeUtil {
|
||||||
LAST_SCREEN_ON_TIME -> ConfigurationItemType.LastScreenOnTime
|
LAST_SCREEN_ON_TIME -> ConfigurationItemType.LastScreenOnTime
|
||||||
SERVER_MESSAGE -> ConfigurationItemType.ServerMessage
|
SERVER_MESSAGE -> ConfigurationItemType.ServerMessage
|
||||||
CUSTOM_SERVER_URL -> ConfigurationItemType.CustomServerUrl
|
CUSTOM_SERVER_URL -> ConfigurationItemType.CustomServerUrl
|
||||||
|
FOREGROUND_APP_QUERY_RANGE -> ConfigurationItemType.ForegroundAppQueryRange
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ abstract class PlatformIntegration(
|
||||||
abstract fun showAppLockScreen(currentPackageName: String, currentActivityName: String?)
|
abstract fun showAppLockScreen(currentPackageName: String, currentActivityName: String?)
|
||||||
abstract fun setShowBlockingOverlay(show: Boolean)
|
abstract fun setShowBlockingOverlay(show: Boolean)
|
||||||
// this should throw an SecurityException if the permission is missing
|
// this should throw an SecurityException if the permission is missing
|
||||||
abstract suspend fun getForegroundApp(result: ForegroundAppSpec)
|
abstract suspend fun getForegroundApp(result: ForegroundAppSpec, queryInterval: Long)
|
||||||
abstract fun setAppStatusMessage(message: AppStatusMessage?)
|
abstract fun setAppStatusMessage(message: AppStatusMessage?)
|
||||||
abstract fun isScreenOn(): Boolean
|
abstract fun isScreenOn(): Boolean
|
||||||
abstract fun setShowNotificationToRevokeTemporarilyAllowedApps(show: Boolean)
|
abstract fun setShowNotificationToRevokeTemporarilyAllowedApps(show: Boolean)
|
||||||
|
|
|
@ -105,8 +105,8 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
|
||||||
return AdminStatus.getAdminStatus(context, policyManager)
|
return AdminStatus.getAdminStatus(context, policyManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getForegroundApp(result: ForegroundAppSpec) {
|
override suspend fun getForegroundApp(result: ForegroundAppSpec, queryInterval: Long) {
|
||||||
foregroundAppHelper.getForegroundApp(result)
|
foregroundAppHelper.getForegroundApp(result, queryInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getForegroundAppPermissionStatus(): RuntimePermissionStatus {
|
override fun getForegroundAppPermissionStatus(): RuntimePermissionStatus {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import io.timelimit.android.integration.platform.RuntimePermissionStatus
|
||||||
class CompatForegroundAppHelper(context: Context) : ForegroundAppHelper() {
|
class CompatForegroundAppHelper(context: Context) : ForegroundAppHelper() {
|
||||||
private val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
private val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||||
|
|
||||||
override suspend fun getForegroundApp(result: ForegroundAppSpec) {
|
override suspend fun getForegroundApp(result: ForegroundAppSpec, queryInterval: Long) {
|
||||||
try {
|
try {
|
||||||
val activity = activityManager.getRunningTasks(1)[0].topActivity
|
val activity = activityManager.getRunningTasks(1)[0].topActivity
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import io.timelimit.android.integration.platform.ForegroundAppSpec
|
||||||
import io.timelimit.android.integration.platform.RuntimePermissionStatus
|
import io.timelimit.android.integration.platform.RuntimePermissionStatus
|
||||||
|
|
||||||
abstract class ForegroundAppHelper {
|
abstract class ForegroundAppHelper {
|
||||||
abstract suspend fun getForegroundApp(result: ForegroundAppSpec)
|
abstract suspend fun getForegroundApp(result: ForegroundAppSpec, queryInterval: Long)
|
||||||
abstract fun getPermissionStatus(): RuntimePermissionStatus
|
abstract fun getPermissionStatus(): RuntimePermissionStatus
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -43,7 +43,7 @@ class LollipopForegroundAppHelper(private val context: Context) : ForegroundAppH
|
||||||
private val event = UsageEvents.Event()
|
private val event = UsageEvents.Event()
|
||||||
|
|
||||||
@Throws(SecurityException::class)
|
@Throws(SecurityException::class)
|
||||||
override suspend fun getForegroundApp(result: ForegroundAppSpec) {
|
override suspend fun getForegroundApp(result: ForegroundAppSpec, queryInterval: Long) {
|
||||||
if (getPermissionStatus() == RuntimePermissionStatus.NotGranted) {
|
if (getPermissionStatus() == RuntimePermissionStatus.NotGranted) {
|
||||||
throw SecurityException()
|
throw SecurityException()
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ class LollipopForegroundAppHelper(private val context: Context) : ForegroundAppH
|
||||||
foregroundAppThread.executeAndWait {
|
foregroundAppThread.executeAndWait {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
if (lastQueryTime > now) {
|
if (lastQueryTime > now || queryInterval >= 1000 * 60 * 60 * 24 /* 1 day */) {
|
||||||
// if the time went backwards, forget everything
|
// if the time went backwards, forget everything
|
||||||
lastQueryTime = 0
|
lastQueryTime = 0
|
||||||
lastPackage = null
|
lastPackage = null
|
||||||
|
@ -69,7 +69,7 @@ class LollipopForegroundAppHelper(private val context: Context) : ForegroundAppH
|
||||||
// which seems to provide all data
|
// which seems to provide all data
|
||||||
// update: with 1 second, some App switching events were missed
|
// update: with 1 second, some App switching events were missed
|
||||||
// it seems to always work with 1.5 seconds
|
// it seems to always work with 1.5 seconds
|
||||||
lastQueryTime - 1500
|
lastQueryTime - Math.max(queryInterval, 1500)
|
||||||
}
|
}
|
||||||
|
|
||||||
usageStatsManager.queryEvents(queryStartTime, now)?.let { usageEvents ->
|
usageStatsManager.queryEvents(queryStartTime, now)?.let { usageEvents ->
|
||||||
|
|
|
@ -97,7 +97,7 @@ class DummyIntegration(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getForegroundApp(result: ForegroundAppSpec) {
|
override suspend fun getForegroundApp(result: ForegroundAppSpec, queryInterval: Long) {
|
||||||
if (foregroundAppPermission == RuntimePermissionStatus.NotGranted) {
|
if (foregroundAppPermission == RuntimePermissionStatus.NotGranted) {
|
||||||
throw SecurityException()
|
throw SecurityException()
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ object AppAffectedByPrimaryDeviceUtil {
|
||||||
val currentApp = ForegroundAppSpec.newInstance()
|
val currentApp = ForegroundAppSpec.newInstance()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logic.platformIntegration.getForegroundApp(currentApp)
|
logic.platformIntegration.getForegroundApp(currentApp, logic.getForegroundAppQueryInterval())
|
||||||
} catch (ex: SecurityException) {
|
} catch (ex: SecurityException) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,9 @@ class AppLogic(
|
||||||
}
|
}
|
||||||
}.ignoreUnchanged()
|
}.ignoreUnchanged()
|
||||||
|
|
||||||
|
private val foregroundAppQueryInterval = database.config().getForegroundAppQueryIntervalAsync().apply { observeForever { } }
|
||||||
|
fun getForegroundAppQueryInterval() = foregroundAppQueryInterval.value ?: 0L
|
||||||
|
|
||||||
val serverLogic = ServerLogic(this)
|
val serverLogic = ServerLogic(this)
|
||||||
val defaultUserLogic = DefaultUserLogic(this)
|
val defaultUserLogic = DefaultUserLogic(this)
|
||||||
val realTimeLogic = RealTimeLogic(this)
|
val realTimeLogic = RealTimeLogic(this)
|
||||||
|
|
|
@ -249,7 +249,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appLogic.platformIntegration.getForegroundApp(foregroundAppSpec)
|
appLogic.platformIntegration.getForegroundApp(foregroundAppSpec, appLogic.getForegroundAppQueryInterval())
|
||||||
val foregroundAppPackageName = foregroundAppSpec.packageName
|
val foregroundAppPackageName = foregroundAppSpec.packageName
|
||||||
val foregroundAppActivityName = foregroundAppSpec.activityName
|
val foregroundAppActivityName = foregroundAppSpec.activityName
|
||||||
val activityLevelBlocking = appLogic.deviceEntry.value?.enableActivityLevelBlocking ?: false
|
val activityLevelBlocking = appLogic.deviceEntry.value?.enableActivityLevelBlocking ?: false
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* 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.diagnose
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.RadioButton
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import io.timelimit.android.R
|
||||||
|
import io.timelimit.android.async.Threads
|
||||||
|
import io.timelimit.android.databinding.DiagnoseForegroundAppFragmentBinding
|
||||||
|
import io.timelimit.android.livedata.liveDataFromValue
|
||||||
|
import io.timelimit.android.livedata.map
|
||||||
|
import io.timelimit.android.logic.DefaultAppLogic
|
||||||
|
import io.timelimit.android.ui.main.ActivityViewModelHolder
|
||||||
|
import io.timelimit.android.ui.main.AuthenticationFab
|
||||||
|
import io.timelimit.android.ui.main.getActivityViewModel
|
||||||
|
import io.timelimit.android.util.TimeTextUtil
|
||||||
|
|
||||||
|
class DiagnoseForegroundAppFragment : Fragment() {
|
||||||
|
companion object {
|
||||||
|
private val buttonIntervals = listOf(
|
||||||
|
0,
|
||||||
|
5 * 1000,
|
||||||
|
30 * 1000,
|
||||||
|
60 * 1000,
|
||||||
|
15 * 60 * 1000,
|
||||||
|
60 * 60 * 1000,
|
||||||
|
24 * 60 * 60 * 1000,
|
||||||
|
7 * 24 * 60 * 60 * 1000
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val activity: ActivityViewModelHolder = activity as ActivityViewModelHolder
|
||||||
|
val binding = DiagnoseForegroundAppFragmentBinding.inflate(inflater, container, false)
|
||||||
|
val auth = activity.getActivityViewModel()
|
||||||
|
val logic = DefaultAppLogic.with(context!!)
|
||||||
|
val currentValue = logic.database.config().getForegroundAppQueryIntervalAsync()
|
||||||
|
val currentId = currentValue.map {
|
||||||
|
val res = buttonIntervals.indexOf(it.toInt())
|
||||||
|
|
||||||
|
if (res == -1)
|
||||||
|
0
|
||||||
|
else
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationFab.manageAuthenticationFab(
|
||||||
|
fab = binding.fab,
|
||||||
|
shouldHighlight = auth.shouldHighlightAuthenticationButton,
|
||||||
|
authenticatedUser = auth.authenticatedUser,
|
||||||
|
doesSupportAuth = liveDataFromValue(true),
|
||||||
|
fragment = this
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.fab.setOnClickListener { activity.showAuthenticationScreen() }
|
||||||
|
|
||||||
|
val allButtons = buttonIntervals.mapIndexed { index, interval ->
|
||||||
|
RadioButton(context!!).apply {
|
||||||
|
id = index
|
||||||
|
|
||||||
|
if (interval == 0) {
|
||||||
|
setText(R.string.diagnose_fga_query_range_min)
|
||||||
|
} else if (interval < 60 * 1000) {
|
||||||
|
text = TimeTextUtil.seconds(interval / 1000, context!!)
|
||||||
|
} else {
|
||||||
|
text = TimeTextUtil.time(interval, context!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allButtons.forEach { binding.radioGroup.addView(it) }
|
||||||
|
|
||||||
|
currentId.observe(this, Observer {
|
||||||
|
binding.radioGroup.check(it)
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.radioGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||||
|
val oldId = currentId.value
|
||||||
|
|
||||||
|
if (oldId != null && checkedId != oldId) {
|
||||||
|
if (auth.requestAuthenticationOrReturnTrue()) {
|
||||||
|
val newValue = buttonIntervals[checkedId]
|
||||||
|
|
||||||
|
Threads.database.execute {
|
||||||
|
logic.database.config().setForegroundAppQueryIntervalSync(newValue.toLong())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.radioGroup.check(oldId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,6 +51,13 @@ class DiagnoseMainFragment : Fragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.diagnoseFgaButton.setOnClickListener {
|
||||||
|
navigation.safeNavigate(
|
||||||
|
DiagnoseMainFragmentDirections.actionDiagnoseMainFragmentToDiagnoseForegroundAppFragment(),
|
||||||
|
R.id.diagnoseMainFragment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ object TimeTextUtil {
|
||||||
return context.resources.getQuantityString(R.plurals.util_time_minutes, minutes, minutes)
|
return context.resources.getQuantityString(R.plurals.util_time_minutes, minutes, minutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun seconds(seconds: Int, context: Context): String {
|
||||||
|
return context.resources.getQuantityString(R.plurals.util_time_seconds, seconds, seconds)
|
||||||
|
}
|
||||||
|
|
||||||
fun days(days: Int, context: Context): String {
|
fun days(days: Int, context: Context): String {
|
||||||
return context.resources.getQuantityString(R.plurals.util_time_days, days, days)
|
return context.resources.getQuantityString(R.plurals.util_time_days, days, days)
|
||||||
}
|
}
|
||||||
|
|
70
app/src/main/res/layout/diagnose_foreground_app_fragment.xml
Normal file
70
app/src/main/res/layout/diagnose_foreground_app_fragment.xml
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<!--
|
||||||
|
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:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
tools:context="io.timelimit.android.ui.diagnose.DiagnoseForegroundAppFragment">
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/scroll"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<LinearLayout
|
||||||
|
android:padding="8dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
app:cardUseCompatPadding="true"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<LinearLayout
|
||||||
|
android:padding="8dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:textAppearance="?android:textAppearanceLarge"
|
||||||
|
android:text="@string/diagnose_fga_query_range"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/radio_group"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
app:fabSize="normal"
|
||||||
|
android:src="@drawable/ic_lock_open_white_24dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:layout_gravity="end|bottom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
</layout>
|
|
@ -44,6 +44,12 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/diagnose_fga_button"
|
||||||
|
android:text="@string/diagnose_fga_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|
|
@ -290,6 +290,13 @@
|
||||||
app:exitAnim="@anim/nav_default_exit_anim"
|
app:exitAnim="@anim/nav_default_exit_anim"
|
||||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_diagnoseMainFragment_to_diagnoseForegroundAppFragment"
|
||||||
|
app:destination="@id/diagnoseForegroundAppFragment"
|
||||||
|
app:enterAnim="@anim/nav_default_enter_anim"
|
||||||
|
app:exitAnim="@anim/nav_default_exit_anim"
|
||||||
|
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||||
|
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/diagnoseClockFragment"
|
android:id="@+id/diagnoseClockFragment"
|
||||||
|
@ -311,4 +318,9 @@
|
||||||
android:name="io.timelimit.android.ui.migrate_to_connected.MigrateToConnectedModeFragment"
|
android:name="io.timelimit.android.ui.migrate_to_connected.MigrateToConnectedModeFragment"
|
||||||
android:label="migrate_to_connected_mode_fragment"
|
android:label="migrate_to_connected_mode_fragment"
|
||||||
tools:layout="@layout/migrate_to_connected_mode_fragment" />
|
tools:layout="@layout/migrate_to_connected_mode_fragment" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/diagnoseForegroundAppFragment"
|
||||||
|
android:name="io.timelimit.android.ui.diagnose.DiagnoseForegroundAppFragment"
|
||||||
|
android:label="diagnose_foreground_app_fragment"
|
||||||
|
tools:layout="@layout/diagnose_foreground_app_fragment" />
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
|
@ -43,4 +43,8 @@
|
||||||
<string name="diagnose_sync_btn_clear_cache_toast">Cache geleert</string>
|
<string name="diagnose_sync_btn_clear_cache_toast">Cache geleert</string>
|
||||||
<string name="diagnose_sync_btn_request_sync">Synchronisation anfordern</string>
|
<string name="diagnose_sync_btn_request_sync">Synchronisation anfordern</string>
|
||||||
<string name="diagnose_sync_btn_request_sync_toast">Synchronisation wurde angefordert</string>
|
<string name="diagnose_sync_btn_request_sync_toast">Synchronisation wurde angefordert</string>
|
||||||
|
|
||||||
|
<string name="diagnose_fga_title">Erkennung der aktiven App</string>
|
||||||
|
<string name="diagnose_fga_query_range">Abfragezeitraum</string>
|
||||||
|
<string name="diagnose_fga_query_range_min">Minimal (Standard)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -14,6 +14,11 @@
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<plurals name="util_time_seconds">
|
||||||
|
<item quantity="one">%d Sekunde</item>
|
||||||
|
<item quantity="other">%d Sekunden</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
<plurals name="util_time_minutes">
|
<plurals name="util_time_minutes">
|
||||||
<item quantity="one">%d Minute</item>
|
<item quantity="one">%d Minute</item>
|
||||||
<item quantity="other">%d Minuten</item>
|
<item quantity="other">%d Minuten</item>
|
||||||
|
|
|
@ -43,4 +43,8 @@
|
||||||
<string name="diagnose_sync_btn_clear_cache_toast">Cache cleared</string>
|
<string name="diagnose_sync_btn_clear_cache_toast">Cache cleared</string>
|
||||||
<string name="diagnose_sync_btn_request_sync">Request sync</string>
|
<string name="diagnose_sync_btn_request_sync">Request sync</string>
|
||||||
<string name="diagnose_sync_btn_request_sync_toast">sync was requested</string>
|
<string name="diagnose_sync_btn_request_sync_toast">sync was requested</string>
|
||||||
|
|
||||||
|
<string name="diagnose_fga_title">Foreground-App-Detection</string>
|
||||||
|
<string name="diagnose_fga_query_range">Requested time range</string>
|
||||||
|
<string name="diagnose_fga_query_range_min">Minimum (Default)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -14,6 +14,11 @@
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<plurals name="util_time_seconds">
|
||||||
|
<item quantity="one">%d second</item>
|
||||||
|
<item quantity="other">%d seconds</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
<plurals name="util_time_minutes">
|
<plurals name="util_time_minutes">
|
||||||
<item quantity="one">%d minute</item>
|
<item quantity="one">%d minute</item>
|
||||||
<item quantity="other">%d minutes</item>
|
<item quantity="other">%d minutes</item>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue