Move device owner settings to the device settings

This commit is contained in:
Jonas Lochmann 2023-06-12 02:00:00 +02:00
parent 34f7bab46d
commit a53883e87a
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
10 changed files with 69 additions and 47 deletions

View file

@ -80,10 +80,6 @@ class DiagnoseMainFragment : Fragment(), FragmentWithCustomTitle {
}
})
binding.diagnoseDom.setOnClickListener {
requireActivity().execute(UpdateStateCommand.Diagnose.DeviceOwner)
}
binding.diagnoseExitReasonsButton.setOnClickListener {
requireActivity().execute(UpdateStateCommand.Diagnose.ExitReasons)
}

View file

@ -36,7 +36,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.timelimit.android.R
import io.timelimit.android.ui.model.diagnose.DeviceOwnerHandling
import io.timelimit.android.ui.model.managedevice.DeviceOwnerHandling
import io.timelimit.android.ui.overview.overview.ListCardCommon
import io.timelimit.android.ui.overview.overview.ListCommon

View file

@ -24,7 +24,6 @@ import io.timelimit.android.data.model.UserType
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.model.account.AccountDeletion
import io.timelimit.android.ui.model.diagnose.DeviceOwnerHandling
import io.timelimit.android.ui.model.flow.Case
import io.timelimit.android.ui.model.flow.splitConflated
import io.timelimit.android.ui.model.launch.LaunchHandling
@ -114,7 +113,6 @@ class MainModel(application: Application): AndroidViewModel(application) {
Case.simple<_, _, State.Overview> { OverviewHandling.processState(logic, scope, activityCommandInternal, authenticationModelApi, state) },
Case.simple<_, _, State.ManageChild> { state -> ManageChildHandling.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.Setup> { state -> SetupHandling.handle(logic, activityCommandInternal, permissionsChanged, state, updateMethod(::updateState)) },
Case.simple<_, _, State.DeleteAccount> { AccountDeletion.handle(logic, scope, share(it), updateMethod(::updateState)) },
Case.simple<_, _, FragmentState> { state ->

View file

@ -22,7 +22,7 @@ import androidx.compose.material.icons.outlined.Info
import io.timelimit.android.R
import io.timelimit.android.ui.manage.device.manage.permission.PermissionScreenContent
import io.timelimit.android.ui.model.account.AccountDeletion
import io.timelimit.android.ui.model.diagnose.DeviceOwnerHandling
import io.timelimit.android.ui.model.managedevice.DeviceOwnerHandling
import io.timelimit.android.ui.model.intro.IntroHandling
import io.timelimit.android.ui.model.mailauthentication.MailAuthentication
import io.timelimit.android.ui.model.main.OverviewHandling
@ -222,8 +222,9 @@ sealed class Screen(
class DeviceOwnerScreen(
state: State,
val content: DeviceOwnerHandling.OwnerScreen,
override val backStack: List<BackStackItem>,
override val snackbarHostState: SnackbarHostState
): Screen(state), ScreenWithAuthenticationFab, ScreenWithSnackbar, ScreenWithTitle {
): Screen(state), ScreenWithAuthenticationFab, ScreenWithSnackbar, ScreenWithTitle, ScreenWithBackStack {
override val title = Title.StringResource(R.string.diagnose_dom_title)
}

View file

@ -49,7 +49,7 @@ import io.timelimit.android.ui.manage.parent.password.restore.RestoreParentPassw
import io.timelimit.android.ui.manage.parent.u2fkey.ManageParentU2FKeyFragment
import io.timelimit.android.ui.manage.parent.u2fkey.ManageParentU2FKeyFragmentArgs
import io.timelimit.android.ui.model.account.AccountDeletion
import io.timelimit.android.ui.model.diagnose.DeviceOwnerHandling
import io.timelimit.android.ui.model.managedevice.DeviceOwnerHandling
import io.timelimit.android.ui.model.mailauthentication.MailAuthentication
import io.timelimit.android.ui.model.main.OverviewHandling
import io.timelimit.android.ui.model.managechild.ManageCategoryBlockedTimes
@ -67,11 +67,12 @@ import io.timelimit.android.ui.view.NotifyPermissionCard
import java.io.Serializable
sealed class State (val previous: State?): Serializable {
fun hasPrevious(other: State): Boolean = this.previous == other || this.previous?.hasPrevious(other) ?: false
fun hasPrevious(other: State): Boolean = this.previous != null && (this.previous.matches(other) || this.previous.hasPrevious(other))
fun find(predicate: (State) -> Boolean): State? =
if (predicate(this)) this
else previous?.find(predicate)
fun first(): State = previous?.first() ?: this
open fun matches(other: State) = this == other
object LaunchState: State(previous = null)
data class Overview(
val state: OverviewHandling.OverviewState = OverviewHandling.OverviewState.empty
@ -214,9 +215,10 @@ sealed class State (val previous: State?): Serializable {
sealed class Sub(
val previousManageDeviceMain: Main,
fragmentClass: Class<out Fragment>
fragmentClass: Class<out Fragment>,
previous: State = previousManageDeviceMain
): ManageDevice(
previousManageDeviceMain,
previous,
previousManageDeviceMain.previousOverview,
previousManageDeviceMain.deviceId,
fragmentClass
@ -231,7 +233,12 @@ sealed class State (val previous: State?): Serializable {
object AdjustDefaultUserTimeout: Overlay()
}
}
data class Permissions(val previousMain: Main, val currentDialog: SystemPermission? = null): Sub(previousMain, Fragment::class.java)
data class Permissions(val previousMain: Main, val currentDialog: SystemPermission? = null): Sub(previousMain, Fragment::class.java) {
override fun matches(other: State): Boolean =
if (other is Permissions) this.previousMain.matches(other.previousMain)
else false
}
data class DeviceOwner(val previousPermissions: Permissions, val details: DeviceOwnerHandling.OwnerState = DeviceOwnerHandling.OwnerState()): Sub(previousPermissions.previousMain, Fragment::class.java, previousPermissions)
class Features(previousMain: Main): Sub(previousMain, ManageDeviceFeaturesFragment::class.java) {
override val arguments: Bundle get() = ManageDeviceFeaturesFragmentArgs(deviceId).toBundle()
}
@ -255,7 +262,6 @@ sealed class State (val previous: State?): Serializable {
class Crypto(previous: Main): FragmentStateLegacy(previous, DiagnoseCryptoFragment::class.java)
class ForegroundApp(previous: Main): FragmentStateLegacy(previous, DiagnoseForegroundAppFragment::class.java)
class Sync(previous: Main): FragmentStateLegacy(previous, DiagnoseSyncFragment::class.java)
data class DeviceOwner(val previousMain: Main, val details: DeviceOwnerHandling.OwnerState = DeviceOwnerHandling.OwnerState()): State(previousMain)
}
sealed class Setup(previous: State): State(previous) {
class SetupTerms: FragmentStateLegacy(previous = null, fragmentClass = SetupTermsFragment::class.java)

View file

@ -316,11 +316,6 @@ sealed class UpdateStateCommand {
if (state is State.DiagnoseScreen.Main) State.DiagnoseScreen.Sync(state)
else null
}
object DeviceOwner: UpdateStateCommand() {
override fun transform(state: State): State? =
if (state is State.DiagnoseScreen.Main) State.DiagnoseScreen.DeviceOwner(state)
else null
}
}
object Setup {
object Help: UpdateStateCommand() {

View file

@ -13,22 +13,24 @@
* 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.diagnose
package io.timelimit.android.ui.model.managedevice
import android.graphics.drawable.Drawable
import android.util.Log
import androidx.compose.material.SnackbarHostState
import androidx.lifecycle.asFlow
import io.timelimit.android.BuildConfig
import io.timelimit.android.R
import io.timelimit.android.async.Threads
import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.data.IdGenerator
import io.timelimit.android.data.model.App
import io.timelimit.android.extensions.whileTrue
import io.timelimit.android.data.model.Device
import io.timelimit.android.integration.platform.DeviceOwnerApi
import io.timelimit.android.integration.platform.PlatformIntegration
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.ui.model.AuthenticationModelApi
import io.timelimit.android.ui.model.BackStackItem
import io.timelimit.android.ui.model.Screen
import io.timelimit.android.ui.model.State
import kotlinx.coroutines.CoroutineScope
@ -96,39 +98,40 @@ object DeviceOwnerHandling {
logic: AppLogic,
scope: CoroutineScope,
authentication: AuthenticationModelApi,
stateLive: MutableStateFlow<State>
deviceLive: SharedFlow<Device>,
ownerStateLive: SharedFlow<State.ManageDevice.DeviceOwner>,
backStackLive: Flow<List<BackStackItem>>,
updateState: ((State.ManageDevice.DeviceOwner) -> State) -> Unit
): Flow<Screen> {
val snackbarHostState = SnackbarHostState()
val hasMatchingState = stateLive.map { it is State.DiagnoseScreen.DeviceOwner }
val ownerStateLive = stateLive.transform { if (it is State.DiagnoseScreen.DeviceOwner) emit (it) }
val isMatchingDeviceLive = combine(deviceLive, logic.deviceId.asFlow()) { device, id ->
device.id == id
}.distinctUntilChanged()
val screenLive = getScreen(
logic,
ownerStateLive.map { it.details },
isMatchingDeviceLive,
scope,
authentication,
snackbarHostState,
updateState = { transformState ->
stateLive.update { oldState ->
if (oldState is State.DiagnoseScreen.DeviceOwner)
oldState.copy(details = transformState(oldState.details))
else
oldState
updateState {
it.copy(details = transformState(it.details))
}
}
)
return hasMatchingState.whileTrue {
ownerStateLive.combine(screenLive) { state, screen ->
Screen.DeviceOwnerScreen(state, screen, snackbarHostState) as Screen
}
return combine(ownerStateLive, screenLive, backStackLive) { state, screen, backStack ->
Screen.DeviceOwnerScreen(state, screen, backStack, snackbarHostState) as Screen
}
}
private fun getScreen(
logic: AppLogic,
state: Flow<OwnerState>,
isMatchingDeviceLive: Flow<Boolean>,
scope: CoroutineScope,
authentication: AuthenticationModelApi,
snackbarHostState: SnackbarHostState,
@ -233,9 +236,13 @@ object DeviceOwnerHandling {
emitAll(
combine(
appsLive, dialogLive, hadUpdateOrganizationNameErrorLive, isParentAuthenticatedLive, organizationNameLive
) { apps, dialog, hadUpdateOrganizationNameError, isParentAuthenticated, organizationName ->
OwnerScreen.Normal(
combine(appsLive, dialogLive) { a, b -> Pair(a, b) },
hadUpdateOrganizationNameErrorLive, isParentAuthenticatedLive, organizationNameLive,
isMatchingDeviceLive
) { appsAndDialog, hadUpdateOrganizationNameError, isParentAuthenticated, organizationName, isMatchingDevice ->
val (apps, dialog) = appsAndDialog
if (isMatchingDevice) OwnerScreen.Normal(
isParentAuthenticated = isParentAuthenticated,
organizationName = organizationName,
appListDialog = dialog,
@ -246,7 +253,7 @@ object DeviceOwnerHandling {
if (hadUpdateOrganizationNameError || !isParentAuthenticated) null
else actions.updateOrganizationName
)
)
) else OwnerScreen.Error
}
)
}.catch {

View file

@ -15,6 +15,7 @@
*/
package io.timelimit.android.ui.model.managedevice
import androidx.lifecycle.asFlow
import io.timelimit.android.R
import io.timelimit.android.data.model.Device
import io.timelimit.android.logic.AppLogic
@ -129,6 +130,23 @@ object ManageDeviceHandling {
updateMethod(updateState)
)
},
Case.simple<_, _, State.ManageDevice.DeviceOwner> {
val subUpdateState = updateMethod<State, State.ManageDevice.DeviceOwner>(updateState)
DeviceOwnerHandling.processState(
logic,
scope,
authentication,
deviceLive,
share(it),
subBackStackLive.map {
it + BackStackItem(Title.StringResource(R.string.manage_device_card_permission_title)) {
subUpdateState { it.previousPermissions }
}
},
subUpdateState
)
},
Case.simple<_, _, State.ManageDevice.Features> {
processFeaturesState(
it,

View file

@ -17,6 +17,8 @@ package io.timelimit.android.ui.model.managedevice
import io.timelimit.android.data.model.Device
import io.timelimit.android.data.model.DevicePlatform
import io.timelimit.android.integration.platform.ProtectionLevel
import io.timelimit.android.integration.platform.SystemPermission
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.ui.manage.device.manage.permission.PermissionScreenContent
import io.timelimit.android.ui.model.ActivityCommand
@ -55,9 +57,15 @@ object ManageDevicePermissions {
PermissionScreenContent.Dialog(
permission = dialog,
launchSystemSettings = if (isCurrentDevice) ({
activityCommand.trySend(ActivityCommand.LaunchSystemSettings(dialog))
if (dialog == SystemPermission.DeviceAdmin && deviceStatus.protectionLevel == ProtectionLevel.DeviceOwner) {
updateState {
State.ManageDevice.DeviceOwner(it.copy(currentDialog = null))
}
} else {
activityCommand.trySend(ActivityCommand.LaunchSystemSettings(dialog))
updateState { it.copy(currentDialog = null) }
updateState { it.copy(currentDialog = null) }
}
}) else null,
close = { updateState { it.copy(currentDialog = null) } }
)

View file

@ -87,13 +87,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
style="?materialButtonOutlinedStyle"
android:id="@+id/diagnose_dom"
android:text="@string/diagnose_dom_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
style="?materialButtonOutlinedStyle"
android:id="@+id/diagnose_exit_reasons_button"