new permission UI

This commit is contained in:
Jonas Lochmann 2023-04-03 02:00:00 +02:00
parent 130b3c4b4c
commit fbd501c3e1
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
25 changed files with 889 additions and 1255 deletions

View file

@ -177,6 +177,7 @@ dependencies {
implementation "com.google.android.material:material:1.8.0" implementation "com.google.android.material:material:1.8.0"
implementation 'androidx.compose.material:material:1.3.1' implementation 'androidx.compose.material:material:1.3.1'
implementation 'androidx.activity:activity-compose:1.6.1' implementation 'androidx.activity:activity-compose:1.6.1'
implementation "com.google.accompanist:accompanist-flowlayout:0.30.0"
implementation 'androidx.compose.material:material-icons-extended:1.3.1' implementation 'androidx.compose.material:material-icons-extended:1.3.1'
implementation 'androidx.fragment:fragment-ktx:1.5.5' implementation 'androidx.fragment:fragment-ktx:1.5.5'

View file

@ -45,6 +45,7 @@ import io.timelimit.android.R
import io.timelimit.android.data.IdGenerator import io.timelimit.android.data.IdGenerator
import io.timelimit.android.data.model.UserType import io.timelimit.android.data.model.UserType
import io.timelimit.android.extensions.showSafe import io.timelimit.android.extensions.showSafe
import io.timelimit.android.integration.platform.SystemPermissionConfirmationLevel
import io.timelimit.android.integration.platform.android.NotificationChannels import io.timelimit.android.integration.platform.android.NotificationChannels
import io.timelimit.android.livedata.ignoreUnchanged import io.timelimit.android.livedata.ignoreUnchanged
import io.timelimit.android.livedata.liveDataFromNullableValue import io.timelimit.android.livedata.liveDataFromNullableValue
@ -141,6 +142,9 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
ActivityCommand.ShowCanNotAddDevicesInLocalModeDialogFragment -> CanNotAddDevicesInLocalModeDialogFragment().show(supportFragmentManager) ActivityCommand.ShowCanNotAddDevicesInLocalModeDialogFragment -> CanNotAddDevicesInLocalModeDialogFragment().show(supportFragmentManager)
ActivityCommand.ShowAuthenticationScreen -> showAuthenticationScreen() ActivityCommand.ShowAuthenticationScreen -> showAuthenticationScreen()
ActivityCommand.ShowMissingPremiumDialog -> RequiresPurchaseDialogFragment().show(supportFragmentManager) ActivityCommand.ShowMissingPremiumDialog -> RequiresPurchaseDialogFragment().show(supportFragmentManager)
is ActivityCommand.LaunchSystemSettings -> mainModel.logic.platformIntegration.openSystemPermissionScren(
this@MainActivity, message.permission, SystemPermissionConfirmationLevel.Suggestion
)
} }
} }
} }

View file

@ -19,9 +19,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import io.timelimit.android.ui.diagnose.deviceowner.DeviceOwnerScreen import io.timelimit.android.ui.diagnose.deviceowner.DeviceOwnerScreen
import io.timelimit.android.ui.manage.device.manage.permission.ManageDevicePermissionScreen
import io.timelimit.android.ui.manage.device.manage.user.ManageDeviceUserScreen import io.timelimit.android.ui.manage.device.manage.user.ManageDeviceUserScreen
import io.timelimit.android.ui.model.Screen import io.timelimit.android.ui.model.Screen
import io.timelimit.android.ui.overview.overview.OverviewScreen import io.timelimit.android.ui.overview.overview.OverviewScreen
import io.timelimit.android.ui.setup.SetupDevicePermissionsScreen
@Composable @Composable
fun ScreenMultiplexer( fun ScreenMultiplexer(
@ -36,5 +38,7 @@ fun ScreenMultiplexer(
is Screen.OverviewScreen -> OverviewScreen(screen.content, modifier = modifier) is Screen.OverviewScreen -> OverviewScreen(screen.content, modifier = modifier)
is Screen.ManageDeviceUserScreen -> ManageDeviceUserScreen(screen.items, screen.actions, screen.overlay, modifier) is Screen.ManageDeviceUserScreen -> ManageDeviceUserScreen(screen.items, screen.actions, screen.overlay, modifier)
is Screen.DeviceOwnerScreen -> DeviceOwnerScreen(screen.content, modifier = modifier) is Screen.DeviceOwnerScreen -> DeviceOwnerScreen(screen.content, modifier = modifier)
is Screen.SetupDevicePermissionsScreen -> SetupDevicePermissionsScreen(screen.content, screen.next, modifier)
is Screen.ManageDevicePermissions -> ManageDevicePermissionScreen(screen.content, modifier)
} }
} }

View file

@ -0,0 +1,36 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.permission
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun ManageDevicePermissionScreen(
content: PermissionScreenContent,
modifier: Modifier = Modifier
) {
PermissionScreen(
content,
modifier
.verticalScroll(rememberScrollState())
.padding(8.dp)
)
}

View file

@ -16,202 +16,40 @@
package io.timelimit.android.ui.manage.device.manage.permission package io.timelimit.android.ui.manage.device.manage.permission
import android.content.Context import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.map
import io.timelimit.android.R import io.timelimit.android.R
import io.timelimit.android.data.model.Device import io.timelimit.android.data.model.Device
import io.timelimit.android.data.model.UserType
import io.timelimit.android.databinding.ManageDevicePermissionsFragmentBinding
import io.timelimit.android.integration.platform.NewPermissionStatus import io.timelimit.android.integration.platform.NewPermissionStatus
import io.timelimit.android.integration.platform.ProtectionLevel import io.timelimit.android.integration.platform.ProtectionLevel
import io.timelimit.android.integration.platform.RuntimePermissionStatus import io.timelimit.android.integration.platform.RuntimePermissionStatus
import io.timelimit.android.integration.platform.SystemPermission
import io.timelimit.android.livedata.ignoreUnchanged
import io.timelimit.android.livedata.liveDataFromNonNullValue
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.main.ActivityViewModelHolder
import io.timelimit.android.ui.main.AuthenticationFab
import io.timelimit.android.ui.main.FragmentWithCustomTitle
import io.timelimit.android.ui.model.UpdateStateCommand
import io.timelimit.android.ui.model.execute
class ManageDevicePermissionsFragment : Fragment(), FragmentWithCustomTitle { object ManageDevicePermissionsFragment {
companion object { fun getPreviewText(device: Device, context: Context): String {
fun getPreviewText(device: Device, context: Context): String { val permissionLabels = mutableListOf<String>()
val permissionLabels = mutableListOf<String>()
if (device.currentUsageStatsPermission == RuntimePermissionStatus.Granted) { if (device.currentUsageStatsPermission == RuntimePermissionStatus.Granted) {
permissionLabels.add(context.getString(R.string.manage_device_permissions_usagestats_title_short)) permissionLabels.add(context.getString(R.string.manage_device_permissions_usagestats_title_short))
}
if (device.currentNotificationAccessPermission == NewPermissionStatus.Granted) {
permissionLabels.add(context.getString(R.string.manage_device_permission_notification_access_title))
}
if (device.currentProtectionLevel != ProtectionLevel.None) {
permissionLabels.add(context.getString(R.string.manage_device_permission_device_admin_title))
}
if (device.currentOverlayPermission == RuntimePermissionStatus.Granted) {
permissionLabels.add(context.getString(R.string.manage_device_permissions_overlay_title))
}
if (device.accessibilityServiceEnabled) {
permissionLabels.add(context.getString(R.string.manage_device_permission_accessibility_title))
}
return if (permissionLabels.isEmpty()) {
context.getString(R.string.manage_device_permissions_summary_none)
} else {
permissionLabels.joinToString(", ")
}
}
}
private val activity: ActivityViewModelHolder by lazy { getActivity() as ActivityViewModelHolder }
private val logic: AppLogic by lazy { DefaultAppLogic.with(requireContext()) }
private val auth: ActivityViewModel by lazy { activity.getActivityViewModel() }
private val args: ManageDevicePermissionsFragmentArgs by lazy { ManageDevicePermissionsFragmentArgs.fromBundle(requireArguments()) }
private val deviceEntry: LiveData<Device?> by lazy {
logic.database.device().getDeviceById(args.deviceId)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = ManageDevicePermissionsFragmentBinding.inflate(inflater, container, false)
// auth
AuthenticationFab.manageAuthenticationFab(
fab = binding.fab,
shouldHighlight = auth.shouldHighlightAuthenticationButton,
authenticatedUser = auth.authenticatedUser,
fragment = this,
doesSupportAuth = liveDataFromNonNullValue(true)
)
auth.authenticatedUser.map { it?.second?.type == UserType.Parent }.observe(this, Observer {
binding.isUserSignedIn = it
})
// handlers
binding.handlers = object: ManageDevicePermissionsFragmentHandlers {
override fun openUsageStatsSettings() {
if (binding.isThisDevice == true) {
logic.platformIntegration.openSystemPermissionScren(
requireActivity(),
SystemPermission.UsageStats
)
}
}
override fun openNotificationAccessSettings() {
if (binding.isThisDevice == true) {
logic.platformIntegration.openSystemPermissionScren(
requireActivity(),
SystemPermission.Notification
)
}
}
override fun openDrawOverOtherAppsScreen() {
if (binding.isThisDevice == true) {
logic.platformIntegration.openSystemPermissionScren(
requireActivity(),
SystemPermission.Overlay
)
}
}
override fun openAccessibilitySettings() {
if (binding.isThisDevice == true) {
logic.platformIntegration.openSystemPermissionScren(
requireActivity(),
SystemPermission.AccessibilityService
)
}
}
override fun manageDeviceAdmin() {
if (binding.isThisDevice == true) {
logic.platformIntegration.openSystemPermissionScren(
requireActivity(),
SystemPermission.DeviceAdmin
)
}
}
override fun showAuthenticationScreen() {
activity.showAuthenticationScreen()
}
override fun helpUsageStatsAccess() {
PermissionInfoHelpDialog.show(requireActivity(), SystemPermission.UsageStats)
}
override fun helpNotificationAccess() {
PermissionInfoHelpDialog.show(requireActivity(), SystemPermission.Notification)
}
override fun helpDrawOverOtherApps() {
PermissionInfoHelpDialog.show(requireActivity(), SystemPermission.Overlay)
}
override fun helpAccesibility() {
PermissionInfoHelpDialog.show(requireActivity(), SystemPermission.AccessibilityService)
}
} }
// is this device if (device.currentNotificationAccessPermission == NewPermissionStatus.Granted) {
val isThisDevice = logic.deviceId.map { ownDeviceId -> ownDeviceId == args.deviceId }.ignoreUnchanged() permissionLabels.add(context.getString(R.string.manage_device_permission_notification_access_title))
}
isThisDevice.observe(this, Observer { if (device.currentProtectionLevel != ProtectionLevel.None) {
binding.isThisDevice = it permissionLabels.add(context.getString(R.string.manage_device_permission_device_admin_title))
}) }
// permissions if (device.currentOverlayPermission == RuntimePermissionStatus.Granted) {
deviceEntry.observe(this, Observer { permissionLabels.add(context.getString(R.string.manage_device_permissions_overlay_title))
device -> }
if (device == null) { if (device.accessibilityServiceEnabled) {
requireActivity().execute(UpdateStateCommand.ManageDevice.Leave) permissionLabels.add(context.getString(R.string.manage_device_permission_accessibility_title))
} else { }
binding.usageStatsAccess = device.currentUsageStatsPermission
binding.notificationAccessPermission = device.currentNotificationAccessPermission
binding.protectionLevel = device.currentProtectionLevel
binding.overlayPermission = device.currentOverlayPermission
binding.accessibilityServiceEnabled = device.accessibilityServiceEnabled
}
})
return if (permissionLabels.isEmpty()) {
return binding.root context.getString(R.string.manage_device_permissions_summary_none)
} else {
permissionLabels.joinToString(", ")
}
} }
override fun onResume() {
super.onResume()
logic.backgroundTaskLogic.syncDeviceStatusAsync()
}
override fun getCustomTitle(): LiveData<String?> = deviceEntry.map { "${getString(R.string.manage_device_card_permission_title)} < ${it?.name} < ${getString(R.string.main_tab_overview)}" }
}
interface ManageDevicePermissionsFragmentHandlers {
fun openUsageStatsSettings()
fun openNotificationAccessSettings()
fun openDrawOverOtherAppsScreen()
fun openAccessibilitySettings()
fun manageDeviceAdmin()
fun showAuthenticationScreen()
fun helpUsageStatsAccess()
fun helpNotificationAccess()
fun helpDrawOverOtherApps()
fun helpAccesibility()
} }

View file

@ -0,0 +1,150 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.permission
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckBox
import androidx.compose.material.icons.filled.CheckBoxOutlineBlank
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.timelimit.android.R
import io.timelimit.android.integration.platform.NewPermissionStatus
import io.timelimit.android.integration.platform.ProtectionLevel
import io.timelimit.android.integration.platform.RuntimePermissionStatus
import io.timelimit.android.integration.platform.SystemPermission
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun PermissionGoals(status: PermissionScreenContent.Status) {
PermissionGoal(
stringResource(R.string.manage_device_permission_goal_limit_title),
status.usageStats != RuntimePermissionStatus.NotGranted &&
(!status.isQOrLater || status.overlay == RuntimePermissionStatus.Granted || status.accessibility),
) {
if (status.usageStats != RuntimePermissionStatus.NotRequired) FlowRow {
Text(stringResource(R.string.manage_device_permission_goal_needs))
PermissionIcon(SystemPermission.UsageStats, status.usageStats == RuntimePermissionStatus.Granted)
}
if (status.isQOrLater) FlowRow {
Text(stringResource(R.string.manage_device_permission_goal_needs))
PermissionIcon(SystemPermission.Overlay, status.overlay == RuntimePermissionStatus.Granted)
Text(stringResource(R.string.manage_device_permission_goal_or))
PermissionIcon(SystemPermission.AccessibilityService, status.accessibility)
}
if (status.usageStats == RuntimePermissionStatus.NotRequired && !status.isQOrLater)
Text(stringResource(R.string.manage_device_permission_goal_reached_by_old_android))
}
PermissionGoal(
stringResource(R.string.manage_device_permission_goal_floating_window),
status.accessibility
) {
FlowRow {
Text(stringResource(R.string.manage_device_permission_goal_eventually_needs))
PermissionIcon(SystemPermission.AccessibilityService, status.accessibility)
}
}
PermissionGoal(
stringResource(R.string.manage_device_permission_goal_background_audio),
status.notificationAccess == NewPermissionStatus.Granted
) {
FlowRow {
Text(stringResource(R.string.manage_device_permission_goal_needs))
PermissionIcon(
SystemPermission.Notification,
status.notificationAccess == NewPermissionStatus.Granted
)
}
}
PermissionGoal(
stringResource(R.string.manage_device_permission_goal_manipulation_protection),
status.protectionLevel == ProtectionLevel.DeviceOwner
) {
Text(stringResource(
if (status.maxProtectionLevel == null)
R.string.manage_device_permission_goal_manipulation_protection_check_remotely
else if (status.maxProtectionLevel == ProtectionLevel.DeviceOwner)
R.string.manage_device_permission_goal_needs_device_owner
else
R.string.manage_device_permission_goal_manipulation_protection_unavailable
))
}
}
@Composable
fun PermissionGoal(
title: String,
checked: Boolean,
content: @Composable () -> Unit
) {
Card (
elevation = 4.dp
) {
Row (
verticalAlignment = Alignment.CenterVertically
) {
Column (
Modifier
.weight(1f)
.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(title, style = MaterialTheme.typography.h6)
content()
}
Spacer(modifier = Modifier.width(8.dp))
if (checked) Icon(
Icons.Default.CheckBox,
stringResource(R.string.manage_device_permission_goal_reached),
tint = MaterialTheme.colors.primary
) else Icon(
Icons.Default.CheckBoxOutlineBlank,
stringResource(R.string.manage_device_permission_goal_missed)
)
Spacer(modifier = Modifier.width(8.dp))
}
}
}
@Composable
fun PermissionIcon(permission: SystemPermission, checked: Boolean) {
val icon = PermissionVisualization.getIcon(permission)
val label = PermissionVisualization.getLabel(LocalContext.current, false, permission)
val tint =
if (checked) MaterialTheme.colors.primary
else LocalContentColor.current
Icon(
icon,
label,
tint = tint
)
}

View file

@ -0,0 +1,38 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.permission
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun PermissionScreen(
content: PermissionScreenContent,
modifier: Modifier = Modifier
) {
Column (
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier
) {
PermissionScreenPermissionList(content.status, content.showDetails)
PermissionGoals(content.status)
}
if (content.dialog != null) PermissionScreenDialog(content.dialog, content.status)
}

View file

@ -0,0 +1,43 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.permission
import io.timelimit.android.integration.platform.NewPermissionStatus
import io.timelimit.android.integration.platform.ProtectionLevel
import io.timelimit.android.integration.platform.RuntimePermissionStatus
import io.timelimit.android.integration.platform.SystemPermission
data class PermissionScreenContent(
val status: Status,
val dialog: Dialog?,
val showDetails: (SystemPermission) -> Unit
) {
data class Status(
val notificationAccess: NewPermissionStatus,
val protectionLevel: ProtectionLevel,
val maxProtectionLevel: ProtectionLevel?,
val usageStats: RuntimePermissionStatus,
val overlay: RuntimePermissionStatus,
val accessibility: Boolean,
val isQOrLater: Boolean
)
data class Dialog(
val permission: SystemPermission,
val launchSystemSettings: (() -> Unit)?,
val close: () -> Unit
)
}

View file

@ -0,0 +1,85 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.permission
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.timelimit.android.R
import io.timelimit.android.integration.platform.ProtectionLevel
import io.timelimit.android.integration.platform.SystemPermission
@Composable
fun PermissionScreenDialog(
dialog: PermissionScreenContent.Dialog,
status: PermissionScreenContent.Status
) {
val permissionIcon = PermissionVisualization.getIcon(dialog.permission)
val permissionTitle = PermissionVisualization.getLabel(LocalContext.current, false, dialog.permission)
val permissionDescription = PermissionVisualization.getDescription(LocalContext.current, dialog.permission)
val permissionStatus = PermissionVisualization.getStatusText(LocalContext.current, dialog.permission, status)
AlertDialog(
onDismissRequest = dialog.close,
title = {
Row (
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(permissionIcon, permissionTitle)
Text(permissionTitle)
}
},
text = {
Column (
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(permissionDescription, style = MaterialTheme.typography.body1)
Text(permissionStatus, style = MaterialTheme.typography.body1)
if (dialog.permission == SystemPermission.DeviceAdmin) {
val message =
if (status.maxProtectionLevel != ProtectionLevel.DeviceOwner && status.maxProtectionLevel != null) R.string.manage_device_permission_device_owner_unsupported
else if (status.protectionLevel != ProtectionLevel.DeviceOwner) R.string.manage_device_permission_device_owner_not_granted
else null
if (message != null) Text(stringResource(message), style = MaterialTheme.typography.body1)
}
Text(stringResource(R.string.manage_device_permission_link_only_info))
}
},
dismissButton = {
TextButton(onClick = dialog.close) {
Text(stringResource(R.string.generic_cancel))
}
},
confirmButton = {
if (dialog.launchSystemSettings != null) {
TextButton(onClick = dialog.launchSystemSettings) {
Text(stringResource(R.string.manage_device_permission_btn_modify))
}
}
}
)
}

View file

@ -0,0 +1,79 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.permission
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import io.timelimit.android.integration.platform.SystemPermission
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun PermissionScreenPermissionList(
status: PermissionScreenContent.Status,
showDetails: (SystemPermission) -> Unit
) {
val permissions = listOf(
SystemPermission.UsageStats,
SystemPermission.DeviceAdmin,
SystemPermission.Notification,
SystemPermission.Overlay,
SystemPermission.AccessibilityService
)
FlowRow (
horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally),
modifier = Modifier.fillMaxWidth(),
maxItemsInEachRow = 3
) {
for (permission in permissions) {
val permissionIcon = PermissionVisualization.getIcon(permission)
val permissionStatus = PermissionVisualization.getStatusColor(status, permission)
val permissionLabel = PermissionVisualization.getLabel(LocalContext.current, false, permission)
Box (
Modifier.padding(vertical = 8.dp)
) {
Card(
modifier = Modifier.clickable(
onClick = { showDetails(permission) },
onClickLabel = permissionLabel
),
backgroundColor = when (permissionStatus) {
PermissionVisualization.Status.Neutral -> MaterialTheme.colors.surface
PermissionVisualization.Status.Good -> MaterialTheme.colors.primary
},
elevation = 8.dp
) {
Icon(
permissionIcon,
permissionLabel,
Modifier
.padding(16.dp)
.size(64.dp)
)
}
}
}
}
}

View file

@ -0,0 +1,110 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.permission
import android.content.Context
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.ui.graphics.vector.ImageVector
import io.timelimit.android.R
import io.timelimit.android.integration.platform.NewPermissionStatus
import io.timelimit.android.integration.platform.ProtectionLevel
import io.timelimit.android.integration.platform.RuntimePermissionStatus
import io.timelimit.android.integration.platform.SystemPermission
object PermissionVisualization {
fun getIcon(permission: SystemPermission): ImageVector = when (permission) {
SystemPermission.UsageStats -> Icons.Default.BarChart
SystemPermission.DeviceAdmin -> Icons.Default.Shield
SystemPermission.Notification -> Icons.Default.Notifications
SystemPermission.Overlay -> Icons.Default.Layers
SystemPermission.AccessibilityService -> Icons.Default.AccessibilityNew
}
fun getLabel(context: Context, short: Boolean, permission: SystemPermission): String = context.getString(when (permission) {
SystemPermission.UsageStats ->
if (short) R.string.manage_device_permissions_usagestats_title_short
else R.string.manage_device_permissions_usagestats_title
SystemPermission.DeviceAdmin -> R.string.manage_device_permission_device_admin_title
SystemPermission.Notification -> R.string.manage_device_permission_notification_access_title
SystemPermission.Overlay -> R.string.manage_device_permissions_overlay_title
SystemPermission.AccessibilityService -> R.string.manage_device_permission_accessibility_title
})
fun getDescription(context: Context, permission: SystemPermission): String = context.getString(when (permission) {
SystemPermission.UsageStats -> R.string.manage_device_permissions_usagestats_text
SystemPermission.DeviceAdmin -> R.string.manage_device_permission_device_admin_text
SystemPermission.Notification -> R.string.manage_device_permission_notification_access_text
SystemPermission.Overlay -> R.string.manage_device_permissions_overlay_text
SystemPermission.AccessibilityService -> R.string.manage_device_permission_accessibility_text
})
fun getStatusColor(status: PermissionScreenContent.Status, permission: SystemPermission): Status = when (permission) {
SystemPermission.UsageStats ->
if (status.usageStats == RuntimePermissionStatus.Granted) Status.Good
else Status.Neutral
SystemPermission.DeviceAdmin ->
if (status.protectionLevel != ProtectionLevel.None) Status.Good
else Status.Neutral
SystemPermission.Notification ->
if (status.notificationAccess == NewPermissionStatus.Granted) Status.Good
else Status.Neutral
SystemPermission.Overlay ->
if (status.overlay == RuntimePermissionStatus.Granted) Status.Good
else Status.Neutral
SystemPermission.AccessibilityService ->
if (status.accessibility) Status.Good
else Status.Neutral
}
fun getStatusText(context: Context, permission: SystemPermission, status: PermissionScreenContent.Status): String = when (permission) {
SystemPermission.UsageStats ->
context.getString(when (status.usageStats) {
RuntimePermissionStatus.Granted -> R.string.manage_device_permission_status_granted
RuntimePermissionStatus.NotGranted -> R.string.manage_device_permission_status_not_granted
RuntimePermissionStatus.NotRequired -> R.string.manage_device_permission_status_not_required
})
SystemPermission.DeviceAdmin ->
context.getString(when (status.protectionLevel) {
ProtectionLevel.None -> R.string.manage_device_permission_device_admin_text_disabled
ProtectionLevel.SimpleDeviceAdmin -> R.string.manage_device_permission_device_admin_text_simple
ProtectionLevel.PasswordDeviceAdmin -> R.string.manage_device_permission_device_admin_text_password
ProtectionLevel.DeviceOwner -> R.string.manage_device_permission_device_admin_text_owner
})
SystemPermission.Notification ->
context.getString(when (status.notificationAccess) {
NewPermissionStatus.Granted -> R.string.manage_device_permission_status_granted
NewPermissionStatus.NotGranted -> R.string.manage_device_permission_status_not_granted
NewPermissionStatus.NotSupported -> R.string.manage_device_permission_status_not_supported
})
SystemPermission.Overlay ->
context.getString(when (status.overlay) {
RuntimePermissionStatus.Granted -> R.string.manage_device_permission_status_granted
RuntimePermissionStatus.NotGranted -> R.string.manage_device_permission_status_not_granted
RuntimePermissionStatus.NotRequired -> R.string.manage_device_permission_status_not_required
})
SystemPermission.AccessibilityService ->
context.getString(when (status.accessibility) {
true -> R.string.manage_device_permission_status_granted
false -> R.string.manage_device_permission_status_not_granted
})
}
enum class Status {
Neutral,
Good,
}
}

View file

@ -15,9 +15,12 @@
*/ */
package io.timelimit.android.ui.model package io.timelimit.android.ui.model
import io.timelimit.android.integration.platform.SystemPermission
sealed class ActivityCommand { sealed class ActivityCommand {
object ShowCanNotAddDevicesInLocalModeDialogFragment: ActivityCommand() object ShowCanNotAddDevicesInLocalModeDialogFragment: ActivityCommand()
object ShowAddDeviceFragment: ActivityCommand() object ShowAddDeviceFragment: ActivityCommand()
object ShowAuthenticationScreen: ActivityCommand() object ShowAuthenticationScreen: ActivityCommand()
object ShowMissingPremiumDialog: ActivityCommand() object ShowMissingPremiumDialog: ActivityCommand()
class LaunchSystemSettings(val permission: SystemPermission): ActivityCommand()
} }

View file

@ -30,6 +30,7 @@ import io.timelimit.android.ui.model.launch.LaunchHandling
import io.timelimit.android.ui.model.main.OverviewHandling import io.timelimit.android.ui.model.main.OverviewHandling
import io.timelimit.android.ui.model.managechild.ManageChildHandling import io.timelimit.android.ui.model.managechild.ManageChildHandling
import io.timelimit.android.ui.model.managedevice.ManageDeviceHandling import io.timelimit.android.ui.model.managedevice.ManageDeviceHandling
import io.timelimit.android.ui.model.setup.SetupLocalModePermissions
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -57,8 +58,8 @@ class MainModel(application: Application): AndroidViewModel(application) {
} }
val activityModel = ActivityViewModel(application) val activityModel = ActivityViewModel(application)
val logic = DefaultAppLogic.with(application)
private val logic = DefaultAppLogic.with(application)
private val activityCommandInternal = Channel<ActivityCommand>() private val activityCommandInternal = Channel<ActivityCommand>()
private val authenticationScreenClosed = MutableSharedFlow<Unit>(extraBufferCapacity = 1) private val authenticationScreenClosed = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
@ -112,6 +113,7 @@ class MainModel(application: Application): AndroidViewModel(application) {
Case.simple<_, _, State.ManageChild> { state -> ManageChildHandling.processState(logic, state, updateMethod(::updateState)) }, Case.simple<_, _, State.ManageChild> { state -> ManageChildHandling.processState(logic, state, updateMethod(::updateState)) },
Case.simple<_, _, State.ManageDevice> { state -> ManageDeviceHandling.processState(logic, activityCommandInternal, authenticationModelApi, state, updateMethod(::updateState)) }, Case.simple<_, _, State.ManageDevice> { state -> ManageDeviceHandling.processState(logic, activityCommandInternal, authenticationModelApi, state, updateMethod(::updateState)) },
Case.simple<_, _, State.DiagnoseScreen.DeviceOwner> { DeviceOwnerHandling.processState(logic, scope, authenticationModelApi, state) }, Case.simple<_, _, State.DiagnoseScreen.DeviceOwner> { DeviceOwnerHandling.processState(logic, scope, authenticationModelApi, state) },
Case.simple<_, _, State.Setup.DevicePermissions> { state -> SetupLocalModePermissions.handle(logic, activityCommandInternal, state, updateMethod(::updateState)) },
Case.simple<_, _, FragmentState> { state -> Case.simple<_, _, FragmentState> { state ->
state.transform { state.transform {
val containerId = it.containerId ?: run { val containerId = it.containerId ?: run {

View file

@ -19,6 +19,7 @@ import androidx.compose.material.SnackbarHostState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Info
import io.timelimit.android.R import io.timelimit.android.R
import io.timelimit.android.ui.manage.device.manage.permission.PermissionScreenContent
import io.timelimit.android.ui.model.diagnose.DeviceOwnerHandling import io.timelimit.android.ui.model.diagnose.DeviceOwnerHandling
import io.timelimit.android.ui.model.main.OverviewHandling import io.timelimit.android.ui.model.main.OverviewHandling
import io.timelimit.android.ui.model.managedevice.ManageDeviceUser import io.timelimit.android.ui.model.managedevice.ManageDeviceUser
@ -177,12 +178,9 @@ sealed class Screen(
class ManageDevicePermissions( class ManageDevicePermissions(
state: State, state: State,
toolbarIcons: List<Menu.Icon>, val content: PermissionScreenContent,
toolbarOptions: List<Menu.Dropdown>,
fragment: FragmentState,
containerId: Int,
override val backStack: List<BackStackItem> override val backStack: List<BackStackItem>
): FragmentScreen(state, toolbarIcons, toolbarOptions, fragment, containerId), ScreenWithBackStack, ScreenWithTitle { ): Screen(state), ScreenWithBackStack, ScreenWithTitle {
override val title = Title.StringResource(R.string.manage_device_card_permission_title) override val title = Title.StringResource(R.string.manage_device_card_permission_title)
} }
@ -215,6 +213,12 @@ sealed class Screen(
): Screen(state), ScreenWithAuthenticationFab, ScreenWithSnackbar, ScreenWithTitle { ): Screen(state), ScreenWithAuthenticationFab, ScreenWithSnackbar, ScreenWithTitle {
override val title = Title.StringResource(R.string.diagnose_dom_title) override val title = Title.StringResource(R.string.diagnose_dom_title)
} }
class SetupDevicePermissionsScreen(
state: State,
val content: PermissionScreenContent,
val next: () -> Unit
): Screen(state)
} }
interface ScreenWithAuthenticationFab interface ScreenWithAuthenticationFab

View file

@ -21,6 +21,7 @@ import androidx.compose.material.icons.filled.DirectionsBike
import androidx.compose.material.icons.filled.Phone import androidx.compose.material.icons.filled.Phone
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import io.timelimit.android.R import io.timelimit.android.R
import io.timelimit.android.integration.platform.SystemPermission
import io.timelimit.android.ui.contacts.ContactsFragment import io.timelimit.android.ui.contacts.ContactsFragment
import io.timelimit.android.ui.diagnose.* import io.timelimit.android.ui.diagnose.*
import io.timelimit.android.ui.diagnose.exitreason.DiagnoseExitReasonFragment import io.timelimit.android.ui.diagnose.exitreason.DiagnoseExitReasonFragment
@ -35,8 +36,6 @@ import io.timelimit.android.ui.manage.device.manage.advanced.ManageDeviceAdvance
import io.timelimit.android.ui.manage.device.manage.advanced.ManageDeviceAdvancedFragmentArgs import io.timelimit.android.ui.manage.device.manage.advanced.ManageDeviceAdvancedFragmentArgs
import io.timelimit.android.ui.manage.device.manage.feature.ManageDeviceFeaturesFragment import io.timelimit.android.ui.manage.device.manage.feature.ManageDeviceFeaturesFragment
import io.timelimit.android.ui.manage.device.manage.feature.ManageDeviceFeaturesFragmentArgs import io.timelimit.android.ui.manage.device.manage.feature.ManageDeviceFeaturesFragmentArgs
import io.timelimit.android.ui.manage.device.manage.permission.ManageDevicePermissionsFragment
import io.timelimit.android.ui.manage.device.manage.permission.ManageDevicePermissionsFragmentArgs
import io.timelimit.android.ui.manage.parent.ManageParentFragment import io.timelimit.android.ui.manage.parent.ManageParentFragment
import io.timelimit.android.ui.manage.parent.ManageParentFragmentArgs import io.timelimit.android.ui.manage.parent.ManageParentFragmentArgs
import io.timelimit.android.ui.manage.parent.link.LinkParentMailFragment import io.timelimit.android.ui.manage.parent.link.LinkParentMailFragment
@ -228,9 +227,7 @@ sealed class State (val previous: State?): Serializable {
object AdjustDefaultUserTimeout: Overlay() object AdjustDefaultUserTimeout: Overlay()
} }
} }
class Permissions(previousMain: Main): Sub(previousMain, ManageDevicePermissionsFragment::class.java) { data class Permissions(val previousMain: Main, val currentDialog: SystemPermission? = null): Sub(previousMain, Fragment::class.java)
override val arguments: Bundle get() = ManageDevicePermissionsFragmentArgs(deviceId).toBundle()
}
class Features(previousMain: Main): Sub(previousMain, ManageDeviceFeaturesFragment::class.java) { class Features(previousMain: Main): Sub(previousMain, ManageDeviceFeaturesFragment::class.java) {
override val arguments: Bundle get() = ManageDeviceFeaturesFragmentArgs(deviceId).toBundle() override val arguments: Bundle get() = ManageDeviceFeaturesFragmentArgs(deviceId).toBundle()
} }
@ -256,7 +253,10 @@ sealed class State (val previous: State?): Serializable {
class SetupTerms: FragmentStateLegacy(previous = null, fragmentClass = SetupTermsFragment::class.java) class SetupTerms: FragmentStateLegacy(previous = null, fragmentClass = SetupTermsFragment::class.java)
class SetupHelpInfo(previous: SetupTerms): FragmentStateLegacy(previous = previous, fragmentClass = SetupHelpInfoFragment::class.java) class SetupHelpInfo(previous: SetupTerms): FragmentStateLegacy(previous = previous, fragmentClass = SetupHelpInfoFragment::class.java)
class SelectMode(previous: SetupHelpInfo): FragmentStateLegacy(previous = previous, fragmentClass = SetupSelectModeFragment::class.java) class SelectMode(previous: SetupHelpInfo): FragmentStateLegacy(previous = previous, fragmentClass = SetupSelectModeFragment::class.java)
class DevicePermissions(previous: SelectMode): FragmentStateLegacy(previous = previous, fragmentClass = SetupDevicePermissionsFragment::class.java) data class DevicePermissions(
val previousSelectMode: SelectMode,
val currentDialog: SystemPermission? = null
): FragmentStateLegacy(previous = previousSelectMode, fragmentClass = Fragment::class.java)
class LocalMode(previous: DevicePermissions): FragmentStateLegacy(previous = previous, fragmentClass = SetupLocalModeFragment::class.java) class LocalMode(previous: DevicePermissions): FragmentStateLegacy(previous = previous, fragmentClass = SetupLocalModeFragment::class.java)
class RemoteChild(previous: SelectMode): FragmentStateLegacy(previous = previous, fragmentClass = SetupRemoteChildFragment::class.java) class RemoteChild(previous: SelectMode): FragmentStateLegacy(previous = previous, fragmentClass = SetupRemoteChildFragment::class.java)
class ParentMode(previous: SelectMode): FragmentStateLegacy(previous = previous, fragmentClass = SetupParentModeFragment::class.java) class ParentMode(previous: SelectMode): FragmentStateLegacy(previous = previous, fragmentClass = SetupParentModeFragment::class.java)

View file

@ -118,11 +118,15 @@ object ManageDeviceHandling {
updateMethod(updateState) updateMethod(updateState)
) )
}, },
Case.simple<_, _, State.ManageDevice.Permissions> { Case.simple<_, _, State.ManageDevice.Permissions> {state ->
processPermissionsState( ManageDevicePermissions.processState(
it, scope,
logic,
activityCommand,
state,
subBackStackLive, subBackStackLive,
deviceLive deviceLive,
updateMethod(updateState)
) )
}, },
Case.simple<_, _, State.ManageDevice.Features> { Case.simple<_, _, State.ManageDevice.Features> {
@ -142,21 +146,6 @@ object ManageDeviceHandling {
) )
} }
private fun processPermissionsState(
stateLive: Flow<State.ManageDevice.Permissions>,
parentBackStackLive: Flow<List<BackStackItem>>,
deviceLive: Flow<Device>
): Flow<Screen> = combine(stateLive, deviceLive, parentBackStackLive) { state, device, backStack ->
Screen.ManageDevicePermissions(
state,
state.toolbarIcons,
state.toolbarOptions,
state,
R.id.fragment_manage_device_permissions,
backStack
)
}
private fun processFeaturesState( private fun processFeaturesState(
stateLive: Flow<State.ManageDevice.Features>, stateLive: Flow<State.ManageDevice.Features>,
parentBackStackLive: Flow<List<BackStackItem>>, parentBackStackLive: Flow<List<BackStackItem>>,

View file

@ -0,0 +1,88 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.model.managedevice
import io.timelimit.android.data.model.Device
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.ui.manage.device.manage.permission.PermissionScreenContent
import io.timelimit.android.ui.model.ActivityCommand
import io.timelimit.android.ui.model.BackStackItem
import io.timelimit.android.ui.model.Screen
import io.timelimit.android.ui.model.State
import io.timelimit.android.ui.model.setup.SetupLocalModePermissions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.*
object ManageDevicePermissions {
fun processState(
scope: CoroutineScope,
logic: AppLogic,
activityCommand: SendChannel<ActivityCommand>,
stateLive: Flow<State.ManageDevice.Permissions>,
parentBackStackLive: Flow<List<BackStackItem>>,
deviceLive: SharedFlow<Device>,
updateState: ((State.ManageDevice.Permissions) -> State) -> Unit
): Flow<Screen> {
val isCurrentDeviceLive = isCurrentDevice(logic, deviceLive).shareIn(scope, SharingStarted.Lazily, 1)
val deviceStatusLive = isCurrentDeviceLive.transformLatest { isCurrentDevice ->
if (isCurrentDevice) emitAll(SetupLocalModePermissions.deviceStatus(logic.platformIntegration))
else emitAll(statusFromDevice(deviceLive))
}
return combine(
stateLive, deviceStatusLive, isCurrentDeviceLive, parentBackStackLive
) { state, deviceStatus, isCurrentDevice, parentBackStack ->
Screen.ManageDevicePermissions(
state,
PermissionScreenContent(
status = deviceStatus,
dialog = state.currentDialog?.let { dialog ->
PermissionScreenContent.Dialog(
permission = dialog,
launchSystemSettings = if (isCurrentDevice) ({
activityCommand.trySend(ActivityCommand.LaunchSystemSettings(dialog))
updateState { it.copy(currentDialog = null) }
}) else null,
close = { updateState { it.copy(currentDialog = null) } }
)
},
showDetails = { permission -> updateState { it.copy(currentDialog = permission) } }
),
parentBackStack
)
}
}
private fun isCurrentDevice(logic: AppLogic, deviceLive: Flow<Device>): Flow<Boolean> {
val ownDeviceIdLive = logic.database.config().getOwnDeviceIdFlow()
return combine(ownDeviceIdLive, deviceLive) { deviceId, device -> device.id == deviceId }
}
private fun statusFromDevice(deviceLive: Flow<Device>): Flow<PermissionScreenContent.Status> = deviceLive.map { device ->
PermissionScreenContent.Status(
notificationAccess = device.currentNotificationAccessPermission,
protectionLevel = device.currentProtectionLevel,
maxProtectionLevel = null,
usageStats = device.currentUsageStatsPermission,
overlay = device.currentOverlayPermission,
accessibility = device.accessibilityServiceEnabled,
isQOrLater = device.qOrLater
)
}.distinctUntilChanged()
}

View file

@ -0,0 +1,80 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.model.setup
import io.timelimit.android.integration.platform.PlatformIntegration
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.ui.manage.device.manage.permission.PermissionScreenContent
import io.timelimit.android.ui.model.ActivityCommand
import io.timelimit.android.ui.model.Screen
import io.timelimit.android.ui.model.State
import io.timelimit.android.util.AndroidVersion
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
object SetupLocalModePermissions {
fun handle(
logic: AppLogic,
activityCommand: SendChannel<ActivityCommand>,
stateLive: Flow<State.Setup.DevicePermissions>,
updateState: ((State.Setup.DevicePermissions) -> State) -> Unit
): Flow<Screen> {
val deviceStatusLive = deviceStatus(logic.platformIntegration)
return combine(stateLive, deviceStatusLive) { state, deviceStatus ->
Screen.SetupDevicePermissionsScreen(
state,
PermissionScreenContent(
status = deviceStatus,
dialog = state.currentDialog?.let { dialog ->
PermissionScreenContent.Dialog(
permission = dialog,
launchSystemSettings = {
activityCommand.trySend(ActivityCommand.LaunchSystemSettings(dialog))
updateState { it.copy(currentDialog = null) }
},
close = { updateState { it.copy(currentDialog = null) } }
)
},
showDetails = { permission -> updateState { it.copy(currentDialog = permission) } }
)
) { updateState { State.Setup.LocalMode(it) } }
}
}
fun deviceStatus(platformIntegration: PlatformIntegration): Flow<PermissionScreenContent.Status> = flow {
while (true) {
emit(
PermissionScreenContent.Status(
notificationAccess = platformIntegration.getNotificationAccessPermissionStatus(),
protectionLevel = platformIntegration.getCurrentProtectionLevel(),
maxProtectionLevel = platformIntegration.maximumProtectionLevel,
usageStats = platformIntegration.getForegroundAppPermissionStatus(),
overlay = platformIntegration.getDrawOverOtherAppsPermissionStatus(true),
accessibility = platformIntegration.isAccessibilityServiceEnabled(),
isQOrLater = AndroidVersion.qOrLater
)
)
delay(2000)
}
}.distinctUntilChanged()
}

View file

@ -1,131 +0,0 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.setup
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import io.timelimit.android.async.Threads
import io.timelimit.android.databinding.FragmentSetupDevicePermissionsBinding
import io.timelimit.android.integration.platform.SystemPermission
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.manage.device.manage.permission.PermissionInfoHelpDialog
import io.timelimit.android.ui.model.UpdateStateCommand
import io.timelimit.android.ui.model.execute
class SetupDevicePermissionsFragment : Fragment() {
private val logic: AppLogic by lazy { DefaultAppLogic.with(context!!) }
private lateinit var binding: FragmentSetupDevicePermissionsBinding
lateinit var refreshStatusRunnable: Runnable
init {
refreshStatusRunnable = Runnable {
refreshStatus()
Threads.mainThreadHandler.postDelayed(refreshStatusRunnable, 2000L)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentSetupDevicePermissionsBinding.inflate(inflater, container, false)
binding.handlers = object: SetupDevicePermissionsHandlers {
override fun manageDeviceAdmin() {
logic.platformIntegration.openSystemPermissionScren(requireActivity(), SystemPermission.DeviceAdmin)
}
override fun openUsageStatsSettings() {
logic.platformIntegration.openSystemPermissionScren(requireActivity(), SystemPermission.UsageStats)
}
override fun openNotificationAccessSettings() {
logic.platformIntegration.openSystemPermissionScren(requireActivity(), SystemPermission.Notification)
}
override fun openDrawOverOtherAppsScreen() {
logic.platformIntegration.openSystemPermissionScren(requireActivity(), SystemPermission.Overlay)
}
override fun openAccessibilitySettings() {
logic.platformIntegration.openSystemPermissionScren(requireActivity(), SystemPermission.AccessibilityService)
}
override fun gotoNextStep() {
requireActivity().execute(UpdateStateCommand.Setup.LocalMode)
}
override fun helpUsageStatsAccess() {
PermissionInfoHelpDialog.show(requireActivity(), SystemPermission.UsageStats)
}
override fun helpNotificationAccess() {
PermissionInfoHelpDialog.show(requireActivity(), SystemPermission.Notification)
}
override fun helpDrawOverOtherApps() {
PermissionInfoHelpDialog.show(requireActivity(), SystemPermission.Overlay)
}
override fun helpAccesibility() {
PermissionInfoHelpDialog.show(requireActivity(), SystemPermission.AccessibilityService)
}
}
refreshStatus()
return binding.root
}
private fun refreshStatus() {
val platform = logic.platformIntegration
binding.notificationAccessPermission = platform.getNotificationAccessPermissionStatus()
binding.protectionLevel = platform.getCurrentProtectionLevel()
binding.usageStatsAccess = platform.getForegroundAppPermissionStatus()
binding.overlayPermission = platform.getDrawOverOtherAppsPermissionStatus(true)
binding.accessibilityServiceEnabled = platform.isAccessibilityServiceEnabled()
}
override fun onResume() {
super.onResume()
// this additionally schedules it
refreshStatusRunnable.run()
}
override fun onPause() {
super.onPause()
Threads.mainThreadHandler.removeCallbacks(refreshStatusRunnable)
}
}
interface SetupDevicePermissionsHandlers {
fun manageDeviceAdmin()
fun openUsageStatsSettings()
fun openNotificationAccessSettings()
fun openDrawOverOtherAppsScreen()
fun openAccessibilitySettings()
fun gotoNextStep()
fun helpUsageStatsAccess()
fun helpNotificationAccess()
fun helpDrawOverOtherApps()
fun helpAccesibility()
}

View file

@ -0,0 +1,65 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 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.setup
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import io.timelimit.android.R
import io.timelimit.android.integration.platform.RuntimePermissionStatus
import io.timelimit.android.ui.manage.device.manage.permission.PermissionScreen
import io.timelimit.android.ui.manage.device.manage.permission.PermissionScreenContent
@Composable
fun SetupDevicePermissionsScreen(
content: PermissionScreenContent,
next: () -> Unit,
modifier: Modifier
) {
Column(
Modifier
.verticalScroll(rememberScrollState())
.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
stringResource(R.string.setup_device_permissions_text_short),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
PermissionScreen(content)
Button(
onClick = next,
modifier = Modifier.align(Alignment.End),
enabled = content.status.usageStats != RuntimePermissionStatus.NotGranted
) {
Text(stringResource(R.string.wiazrd_next))
}
}
}

View file

@ -1,410 +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/>.
-->
<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.setup.SetupDevicePermissionsFragment">
<data>
<variable
name="usageStatsAccess"
type="io.timelimit.android.integration.platform.RuntimePermissionStatus" />
<variable
name="notificationAccessPermission"
type="io.timelimit.android.integration.platform.NewPermissionStatus" />
<variable
name="protectionLevel"
type="io.timelimit.android.integration.platform.ProtectionLevel" />
<variable
name="overlayPermission"
type="RuntimePermissionStatus" />
<variable
name="accessibilityServiceEnabled"
type="boolean" />
<variable
name="handlers"
type="io.timelimit.android.ui.setup.SetupDevicePermissionsHandlers" />
<import type="android.view.View" />
<import type="io.timelimit.android.integration.platform.RuntimePermissionStatus" />
<import type="io.timelimit.android.integration.platform.NewPermissionStatus" />
<import type="io.timelimit.android.integration.platform.ProtectionLevel" />
</data>
<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">
<TextView
android:text="@string/setup_device_permissions_text"
android:textAppearance="?android:textAppearanceMedium"
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:orientation="vertical"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/usage_stats_access_title"
android:nextFocusDown="@id/usage_stats_access_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
android:drawableEnd="@drawable/ic_info_outline_black_24dp"
android:onClick="@{() -> handlers.helpUsageStatsAccess()}"
android:textAppearance="?android:textAppearanceLarge"
android:text="@string/manage_device_permissions_usagestats_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{usageStatsAccess == RuntimePermissionStatus.Granted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{usageStatsAccess == RuntimePermissionStatus.NotGranted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_red"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{usageStatsAccess == RuntimePermissionStatus.NotRequired ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_required"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/usage_stats_access_btn"
android:nextFocusUp="@id/usage_stats_access_title"
android:nextFocusDown="@id/device_admin_btn"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:text="@string/manage_device_permission_btn_modify"
android:layout_gravity="end"
android:onClick="@{() -> handlers.openUsageStatsSettings()}"
android:enabled="@{usageStatsAccess != RuntimePermissionStatus.NotRequired}"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<requestFocus />
</Button>
</LinearLayout>
</androidx.cardview.widget.CardView>
<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_permission_device_admin_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{protectionLevel == ProtectionLevel.None ? View.VISIBLE : View.GONE}"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_device_admin_text_disabled"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{protectionLevel == ProtectionLevel.SimpleDeviceAdmin ? View.VISIBLE : View.GONE}"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_device_admin_text_simple"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{protectionLevel == ProtectionLevel.PasswordDeviceAdmin ? View.VISIBLE : View.GONE}"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_device_admin_text_password"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{protectionLevel == ProtectionLevel.DeviceOwner ? View.VISIBLE : View.GONE}"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_device_admin_text_owner"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/device_admin_btn"
android:nextFocusUp="@id/usage_stats_access_btn"
android:nextFocusDown="@id/notification_access_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:text="@string/manage_device_permission_btn_modify"
android:layout_gravity="end"
android:onClick="@{() -> handlers.manageDeviceAdmin()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<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:id="@+id/notification_access_title"
android:nextFocusUp="@id/device_admin_btn"
android:nextFocusDown="@id/notification_access_button"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
android:drawableEnd="@drawable/ic_info_outline_black_24dp"
android:onClick="@{() -> handlers.helpNotificationAccess()}"
android:textAppearance="?android:textAppearanceLarge"
android:text="@string/manage_device_permission_notification_access_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{notificationAccessPermission == NewPermissionStatus.Granted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{notificationAccessPermission == NewPermissionStatus.NotGranted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_red"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{notificationAccessPermission == NewPermissionStatus.NotSupported ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_red"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_supported"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/notification_access_button"
android:nextFocusUp="@id/notification_access_title"
android:nextFocusDown="@id/overlay_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:text="@string/manage_device_permission_btn_modify"
android:layout_gravity="end"
android:onClick="@{() -> handlers.openNotificationAccessSettings()}"
android:enabled="@{notificationAccessPermission != NewPermissionStatus.NotSupported}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<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:id="@+id/overlay_title"
android:nextFocusUp="@id/notification_access_button"
android:nextFocusDown="@id/overlay_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
android:drawableEnd="@drawable/ic_info_outline_black_24dp"
android:onClick="@{() -> handlers.helpDrawOverOtherApps()}"
android:textAppearance="?android:textAppearanceLarge"
android:text="@string/manage_device_permissions_overlay_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{overlayPermission == RuntimePermissionStatus.Granted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{overlayPermission == RuntimePermissionStatus.NotGranted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_red"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{overlayPermission == RuntimePermissionStatus.NotRequired ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_required"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/overlay_btn"
android:nextFocusUp="@id/overlay_title"
android:nextFocusDown="@id/accessibility_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:text="@string/manage_device_permission_btn_modify"
android:layout_gravity="end"
android:onClick="@{() -> handlers.openDrawOverOtherAppsScreen()}"
android:enabled="@{overlayPermission != RuntimePermissionStatus.NotRequired}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<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:id="@+id/accessibility_title"
android:nextFocusUp="@id/overlay_btn"
android:nextFocusDown="@id/accessibility_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
android:drawableEnd="@drawable/ic_info_outline_black_24dp"
android:onClick="@{() -> handlers.helpAccesibility()}"
android:textAppearance="?android:textAppearanceLarge"
android:text="@string/manage_device_permission_accessibility_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{accessibilityServiceEnabled == true ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{accessibilityServiceEnabled == false ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_red"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/accessibility_btn"
android:nextFocusUp="@id/accessibility_title"
android:nextFocusDown="@id/next_btn"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:text="@string/manage_device_permission_btn_modify"
android:layout_gravity="end"
android:onClick="@{() -> handlers.openAccessibilitySettings()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<Button
android:nextFocusUp="@id/accessibility_btn"
android:id="@+id/next_btn"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="4dp"
android:enabled="@{usageStatsAccess != RuntimePermissionStatus.NotGranted}"
android:onClick="@{() -> handlers.gotoNextStep()}"
android:text="@string/wiazrd_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
</layout>

View file

@ -1,471 +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/>.
-->
<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.manage.device.manage.permission.ManageDevicePermissionsFragment">
<data>
<variable
name="isThisDevice"
type="Boolean" />
<variable
name="usageStatsAccess"
type="io.timelimit.android.integration.platform.RuntimePermissionStatus" />
<variable
name="notificationAccessPermission"
type="io.timelimit.android.integration.platform.NewPermissionStatus" />
<variable
name="protectionLevel"
type="io.timelimit.android.integration.platform.ProtectionLevel" />
<variable
name="overlayPermission"
type="RuntimePermissionStatus" />
<variable
name="accessibilityServiceEnabled"
type="boolean" />
<variable
name="handlers"
type="io.timelimit.android.ui.manage.device.manage.permission.ManageDevicePermissionsFragmentHandlers" />
<variable
name="isUserSignedIn"
type="boolean" />
<import type="android.view.View" />
<import type="io.timelimit.android.data.model.NetworkTime" />
<import type="io.timelimit.android.integration.platform.RuntimePermissionStatus" />
<import type="io.timelimit.android.integration.platform.NewPermissionStatus" />
<import type="io.timelimit.android.integration.platform.ProtectionLevel" />
<import type="io.timelimit.android.BuildConfig" />
</data>
<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">
<TextView
android:text="@string/setup_device_permissions_text"
android:textAppearance="?android:textAppearanceMedium"
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: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_permission_device_admin_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{protectionLevel == ProtectionLevel.None ? View.VISIBLE : View.GONE}"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_device_admin_text_disabled"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{protectionLevel == ProtectionLevel.SimpleDeviceAdmin ? View.VISIBLE : View.GONE}"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_device_admin_text_simple"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{protectionLevel == ProtectionLevel.PasswordDeviceAdmin ? View.VISIBLE : View.GONE}"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_device_admin_text_password"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{protectionLevel == ProtectionLevel.DeviceOwner ? View.VISIBLE : View.GONE}"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_device_admin_text_owner"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/device_admin_btn"
android:nextFocusDown="@id/usage_stats_access_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:text="@string/manage_device_permission_btn_modify"
android:layout_gravity="end"
android:enabled="@{safeUnbox(isThisDevice)}"
android:onClick="@{() -> handlers.manageDeviceAdmin()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<requestFocus />
</Button>
<TextView
android:visibility="@{safeUnbox(isThisDevice) ? View.GONE : View.VISIBLE}"
android:text="@string/manage_device_permission_open_at_target_device"
android:textAppearance="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<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:id="@+id/usage_stats_access_title"
android:nextFocusUp="@id/device_admin_btn"
android:nextFocusDown="@id/usage_stats_access_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
android:drawableEnd="@drawable/ic_info_outline_black_24dp"
android:onClick="@{() -> handlers.helpUsageStatsAccess()}"
android:textAppearance="?android:textAppearanceLarge"
android:text="@string/manage_device_permissions_usagestats_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{usageStatsAccess == RuntimePermissionStatus.Granted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{usageStatsAccess == RuntimePermissionStatus.NotGranted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_red"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{usageStatsAccess == RuntimePermissionStatus.NotRequired ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_required"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/usage_stats_access_btn"
android:nextFocusUp="@id/usage_stats_access_title"
android:nextFocusDown="@id/notification_access_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:text="@string/manage_device_permission_btn_modify"
android:layout_gravity="end"
android:onClick="@{() -> handlers.openUsageStatsSettings()}"
android:enabled="@{safeUnbox(isThisDevice) &amp;&amp; (usageStatsAccess != RuntimePermissionStatus.NotRequired)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{((safeUnbox(isThisDevice) || (usageStatsAccess == RuntimePermissionStatus.NotRequired))) ? View.GONE : View.VISIBLE}"
android:text="@string/manage_device_permission_open_at_target_device"
android:textAppearance="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<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:id="@+id/notification_access_title"
android:nextFocusUp="@id/usage_stats_access_btn"
android:nextFocusDown="@id/notification_access_button"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
android:drawableEnd="@drawable/ic_info_outline_black_24dp"
android:onClick="@{() -> handlers.helpNotificationAccess()}"
android:textAppearance="?android:textAppearanceLarge"
android:text="@string/manage_device_permission_notification_access_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{notificationAccessPermission == NewPermissionStatus.Granted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{notificationAccessPermission == NewPermissionStatus.NotGranted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_red"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{notificationAccessPermission == NewPermissionStatus.NotSupported ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_red"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_supported"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/notification_access_button"
android:nextFocusUp="@id/notification_access_title"
android:nextFocusDown="@id/overlay_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:text="@string/manage_device_permission_btn_modify"
android:layout_gravity="end"
android:onClick="@{() -> handlers.openNotificationAccessSettings()}"
android:enabled="@{safeUnbox(isThisDevice) &amp;&amp; (notificationAccessPermission != NewPermissionStatus.NotSupported)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{((safeUnbox(isThisDevice) || (notificationAccessPermission == NewPermissionStatus.NotSupported))) ? View.GONE : View.VISIBLE}"
android:text="@string/manage_device_permission_open_at_target_device"
android:textAppearance="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<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:id="@+id/overlay_title"
android:nextFocusUp="@id/notification_access_button"
android:nextFocusDown="@id/overlay_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
android:drawableEnd="@drawable/ic_info_outline_black_24dp"
android:onClick="@{() -> handlers.helpDrawOverOtherApps()}"
android:textAppearance="?android:textAppearanceLarge"
android:text="@string/manage_device_permissions_overlay_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{overlayPermission == RuntimePermissionStatus.Granted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{overlayPermission == RuntimePermissionStatus.NotGranted ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_red"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{overlayPermission == RuntimePermissionStatus.NotRequired ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_required"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/overlay_btn"
android:nextFocusUp="@id/overlay_title"
android:nextFocusDown="@id/accessibility_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:text="@string/manage_device_permission_btn_modify"
android:layout_gravity="end"
android:onClick="@{() -> handlers.openDrawOverOtherAppsScreen()}"
android:enabled="@{safeUnbox(isThisDevice) &amp;&amp; (overlayPermission != RuntimePermissionStatus.NotRequired)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{((safeUnbox(isThisDevice) || (overlayPermission == RuntimePermissionStatus.NotRequired))) ? View.GONE : View.VISIBLE}"
android:text="@string/manage_device_permission_open_at_target_device"
android:textAppearance="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<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:id="@+id/accessibility_title"
android:nextFocusUp="@id/overlay_btn"
android:nextFocusDown="@id/accessibility_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
android:drawableEnd="@drawable/ic_info_outline_black_24dp"
android:onClick="@{() -> handlers.helpAccesibility()}"
android:textAppearance="?android:textAppearanceLarge"
android:text="@string/manage_device_permission_accessibility_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{accessibilityServiceEnabled == true ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_green"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:paddingTop="8dp"
android:gravity="center_horizontal"
android:visibility="@{accessibilityServiceEnabled == false ? View.VISIBLE : View.GONE}"
android:textColor="@color/text_red"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/manage_device_permission_status_not_granted"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/accessibility_btn"
android:nextFocusUp="@id/accessibility_title"
android:nextFocusDown="@id/fab"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:text="@string/manage_device_permission_btn_modify"
android:layout_gravity="end"
android:onClick="@{() -> handlers.openAccessibilitySettings()}"
android:enabled="@{safeUnbox(isThisDevice)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{safeUnbox(isThisDevice) ? View.GONE : View.VISIBLE}"
android:text="@string/manage_device_permission_open_at_target_device"
android:textAppearance="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<View
android:visibility="@{isUserSignedIn ? View.GONE : View.VISIBLE}"
android:layout_width="match_parent"
android:layout_height="76dp" />
</LinearLayout>
</ScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:nextFocusUp="@id/accessibility_btn"
android:onClick="@{() -> handlers.showAuthenticationScreen()}"
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>

View file

@ -84,15 +84,6 @@
android:name="parentId" android:name="parentId"
app:argType="string" /> app:argType="string" />
</fragment> </fragment>
<fragment
android:id="@+id/manageDevicePermissionsFragment"
android:name="io.timelimit.android.ui.manage.device.manage.permission.ManageDevicePermissionsFragment"
android:label="manage_device_permissions_fragment"
tools:layout="@layout/manage_device_permissions_fragment" >
<argument
android:name="deviceId"
app:argType="string" />
</fragment>
<fragment <fragment
android:id="@+id/manageDeviceFeaturesFragment" android:id="@+id/manageDeviceFeaturesFragment"
android:name="io.timelimit.android.ui.manage.device.manage.feature.ManageDeviceFeaturesFragment" android:name="io.timelimit.android.ui.manage.device.manage.feature.ManageDeviceFeaturesFragment"

View file

@ -981,9 +981,10 @@
Diese Berechtigung kann nur durch TimeLimit bzw. ein Elternteil, das TimeLimit deinstalliert, deaktiviert werden. Diese Berechtigung kann nur durch TimeLimit bzw. ein Elternteil, das TimeLimit deinstalliert, deaktiviert werden.
</string> </string>
<string name="manage_device_permission_open_at_target_device"> <string name="manage_device_permission_device_owner_unsupported">Die Geräte-Besitzer-Berechtigung
Öffnen Sie diesen Bildschirm am entsprechenden Gerät, um die Berechtigung festzulegen ist etwas anderes und ist nicht verfügbar.</string>
</string> <string name="manage_device_permission_device_owner_not_granted">Die Geräte-Besitzer-Berechtigung
ist etwas anderes und wurde nicht erteilt.</string>
<string name="manage_device_permission_status_granted">Die Berechtigung wurde erteilt</string> <string name="manage_device_permission_status_granted">Die Berechtigung wurde erteilt</string>
<string name="manage_device_permission_status_not_granted">Die Berechtigung wurde nicht erteilt</string> <string name="manage_device_permission_status_not_granted">Die Berechtigung wurde nicht erteilt</string>
@ -992,6 +993,12 @@
<string name="manage_device_permission_btn_modify">Ändern</string> <string name="manage_device_permission_btn_modify">Ändern</string>
<string name="manage_device_permission_link_only_info">Apps können sich keine Berechtigungen selbst erteilen.
Sie werden stets zu einer Systemapp weitergeleitet, um die Änderung durchzuführen.
Dadurch können die Berechtigungen nicht mittels Fernsteuerung geändert werden.
Um Änderungen ohne Passwort zu verhindern müssen Sie die entsprechende Systemapp sperren.
</string>
<string name="manage_device_permission_no_ui_usage_stats_text"> <string name="manage_device_permission_no_ui_usage_stats_text">
Dieses Gerät hat keine grafische Oberfläche zum Erteilen dieser Berechtigung - diese kann Dieses Gerät hat keine grafische Oberfläche zum Erteilen dieser Berechtigung - diese kann
nur von einem Computer mittels ADB geändert werden. nur von einem Computer mittels ADB geändert werden.
@ -1005,6 +1012,20 @@
\n\nDie Berechtigung kann über die Deinstallations-Funktion von TimeLimit deaktiviert werden. \n\nDie Berechtigung kann über die Deinstallations-Funktion von TimeLimit deaktiviert werden.
</string> </string>
<string name="manage_device_permission_goal_limit_title">Zeitbeschränkungen ermöglichen</string>
<string name="manage_device_permission_goal_floating_window">Sperren von Apps in Fenstern ermöglichen</string>
<string name="manage_device_permission_goal_background_audio">Einschränkung von Audiowiedergaben im Hintergrund ermöglichen</string>
<string name="manage_device_permission_goal_manipulation_protection">Manipulationen verhindern</string>
<string name="manage_device_permission_goal_manipulation_protection_unavailable">in der aktuellen Umgebung nicht verfügbar</string>
<string name="manage_device_permission_goal_manipulation_protection_check_remotely">öffnen Sie diese Ansicht am entsprechenden Gerät</string>
<string name="manage_device_permission_goal_eventually_needs">benötigt evtl.</string>
<string name="manage_device_permission_goal_needs">benötigt</string>
<string name="manage_device_permission_goal_or">oder</string>
<string name="manage_device_permission_goal_reached_by_old_android">benötigt wegen alter Android-Version Nichts weiter</string>
<string name="manage_device_permission_goal_needs_device_owner">benötigt Geräte-Besitzer-Berechtigung</string>
<string name="manage_device_permission_goal_reached">Ziel erreicht</string>
<string name="manage_device_permission_goal_missed">Ziel nicht erreicht</string>
<string name="manage_device_downgrade_title">Downgrade</string> <string name="manage_device_downgrade_title">Downgrade</string>
<string name="manage_device_downgrade_text">Auf diesem Gerät wurde eine neuere Version von TimeLimit durch eine ältere ersetzt</string> <string name="manage_device_downgrade_text">Auf diesem Gerät wurde eine neuere Version von TimeLimit durch eine ältere ersetzt</string>
@ -1387,12 +1408,8 @@
<string name="set_password_view_label_password_repeat">Neues Passwort wiederholen</string> <string name="set_password_view_label_password_repeat">Neues Passwort wiederholen</string>
<string name="set_password_view_checkbox_no_password">Passwortschutz nicht verwenden</string> <string name="set_password_view_checkbox_no_password">Passwortschutz nicht verwenden</string>
<string name="setup_device_permissions_text"> <string name="setup_device_permissions_text_short">
TimeLimit benötigt einige Berechtigungen, um funktionieren zu können. TimeLimit benötigt einige Berechtigungen, um funktionieren zu können
Wirklich erforderlich ist nur der Nutzungsdatenzugriff.
Ab Android 10 wird zum Öffnen des Sperrbildschirms die Berechtigung
\"Über anderen Apps anzeigen\" oder \"Bedienhilfe\" benötigt.
Die weiteren Berechtigungen ermöglichen Zusatzfunktionen.
</string> </string>
<string name="setup_device_needs_parent">Ein Elternteil muss sich anmelden, um die Einrichtung abzuschließen</string> <string name="setup_device_needs_parent">Ein Elternteil muss sich anmelden, um die Einrichtung abzuschließen</string>

View file

@ -80,6 +80,8 @@
(<a href="https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt">Apache License, Version 2.0</a>) (<a href="https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt">Apache License, Version 2.0</a>)
\nAndroid X \nAndroid X
(<a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>) (<a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>)
\n<a href="https://github.com/google/accompanist">Google Accompanist</a>
(<a href="https://github.com/google/accompanist/blob/main/LICENSE">Apache License, Version 2.0</a>)
\nAndroid Navigation Component \nAndroid Navigation Component
(<a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>) (<a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>)
\nAndroid Room \nAndroid Room
@ -1029,9 +1031,10 @@
This permission can only be disabled by TimeLimit/ a parent which uninstalls TimeLimit. This permission can only be disabled by TimeLimit/ a parent which uninstalls TimeLimit.
</string> </string>
<string name="manage_device_permission_open_at_target_device"> <string name="manage_device_permission_device_owner_unsupported">The device owner permission
Open the screen at the other device to modify the permissions is something different and not available.</string>
</string> <string name="manage_device_permission_device_owner_not_granted">The device
owner permission is something different and was not granted.</string>
<string name="manage_device_permission_status_granted">The permission was granted</string> <string name="manage_device_permission_status_granted">The permission was granted</string>
<string name="manage_device_permission_status_not_granted">The permission was not granted</string> <string name="manage_device_permission_status_not_granted">The permission was not granted</string>
@ -1040,6 +1043,12 @@
<string name="manage_device_permission_btn_modify">Modify</string> <string name="manage_device_permission_btn_modify">Modify</string>
<string name="manage_device_permission_link_only_info">Apps can not grant permissions themself.
You are redirected to a system App to change the permission.
Due to that, you can not change the permissions remotely.
To protect the setting, you have to block the corresponding system App.
</string>
<string name="manage_device_permission_no_ui_usage_stats_text"> <string name="manage_device_permission_no_ui_usage_stats_text">
This device does not allow changing this permission graphically. You can only This device does not allow changing this permission graphically. You can only
change it from a PC using ADB. change it from a PC using ADB.
@ -1053,6 +1062,20 @@
\n\nYou can disable it by using the uninstall feature of TimeLimit. \n\nYou can disable it by using the uninstall feature of TimeLimit.
</string> </string>
<string name="manage_device_permission_goal_limit_title">Allow applying limits</string>
<string name="manage_device_permission_goal_floating_window">Allow blocking floating windows</string>
<string name="manage_device_permission_goal_background_audio">Allow limiting background audio</string>
<string name="manage_device_permission_goal_manipulation_protection">Protect against manipulation</string>
<string name="manage_device_permission_goal_manipulation_protection_unavailable">unavailable in the current environment</string>
<string name="manage_device_permission_goal_manipulation_protection_check_remotely">open this screen at the device</string>
<string name="manage_device_permission_goal_eventually_needs">eventually needs</string>
<string name="manage_device_permission_goal_needs">needs</string>
<string name="manage_device_permission_goal_or">or</string>
<string name="manage_device_permission_goal_reached_by_old_android">needs nothing due to old Android version</string>
<string name="manage_device_permission_goal_needs_device_owner">needs device owner</string>
<string name="manage_device_permission_goal_reached">goal reached</string>
<string name="manage_device_permission_goal_missed">goal not reached</string>
<string name="manage_device_downgrade_title">Downgrade</string> <string name="manage_device_downgrade_title">Downgrade</string>
<string name="manage_device_downgrade_text">On this device, a newer version of TimeLimit was replaced by an older one</string> <string name="manage_device_downgrade_text">On this device, a newer version of TimeLimit was replaced by an older one</string>
@ -1432,12 +1455,8 @@
<string name="set_password_view_label_password_repeat">Repeat new password</string> <string name="set_password_view_label_password_repeat">Repeat new password</string>
<string name="set_password_view_checkbox_no_password">Disable password protection</string> <string name="set_password_view_checkbox_no_password">Disable password protection</string>
<string name="setup_device_permissions_text"> <string name="setup_device_permissions_text_short">
TimeLimit needs some permissions to work. TimeLimit needs some permissions to work
Only the usage stats access permission is absolutely necessary.
Starting Android 10, the permission \"Draw over other Apps\" or \"Accessibility service\"
is required to open the lockscreen.
The other permissions are required for some extra features.
</string> </string>
<string name="setup_device_needs_parent">A parent must authenticate to finish the setup</string> <string name="setup_device_needs_parent">A parent must authenticate to finish the setup</string>