mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 09:49:25 +02:00
Move device owner organization name to the device owner screen
This commit is contained in:
parent
8fb102390f
commit
34f7bab46d
13 changed files with 104 additions and 151 deletions
|
@ -333,7 +333,7 @@ abstract class ConfigDao {
|
|||
}
|
||||
)
|
||||
|
||||
suspend fun getCustomOrganizationName(): String = getValueOfKeyCoroutine(ConfigurationItemType.CustomOrganizationName) ?: ""
|
||||
suspend fun getCustomOrganizationName(): String? = getValueOfKeyCoroutine(ConfigurationItemType.CustomOrganizationName)
|
||||
fun setCustomOrganizationName(value: String) = updateValueSync(ConfigurationItemType.CustomOrganizationName, value)
|
||||
|
||||
fun getServerApiLevelSync() = getValueOfKeySync(ConfigurationItemType.ServerApiLevel).let { it?.toInt() ?: 0 }
|
||||
|
|
|
@ -22,4 +22,5 @@ interface DeviceOwnerApi {
|
|||
|
||||
fun setDelegations(packageName: String, scopes: List<DelegationScope>)
|
||||
fun getDelegations(): Map<String, List<DelegationScope>>
|
||||
fun setOrganizationName(name: String)
|
||||
}
|
|
@ -75,9 +75,6 @@ abstract class PlatformIntegration(
|
|||
|
||||
// this function requires the device owner permission and a recent android version
|
||||
abstract fun setForceNetworkTime(enable: Boolean)
|
||||
abstract fun canSetOrganizationName(): Boolean
|
||||
abstract fun setOrganizationName(name: String): Boolean
|
||||
|
||||
abstract fun restartApp()
|
||||
|
||||
abstract fun getCurrentNetworkId(): NetworkId
|
||||
|
|
|
@ -94,4 +94,12 @@ class AndroidDeviceOwnerApi(
|
|||
entry.value.map { it.second }
|
||||
}
|
||||
}
|
||||
|
||||
override fun setOrganizationName(name: String) {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.O && !BuildConfig.storeCompilant) {
|
||||
if (devicePolicyManager.isDeviceOwnerApp(componentName.packageName)) {
|
||||
devicePolicyManager.setOrganizationName(componentName, name)
|
||||
} else throw SecurityException()
|
||||
} else throw SecurityException()
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||
* 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
|
||||
|
@ -629,28 +629,6 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
|
|||
session.playbackState?.state == PlaybackState.STATE_REWINDING
|
||||
}
|
||||
|
||||
override fun canSetOrganizationName(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !BuildConfig.storeCompilant) {
|
||||
policyManager.isDeviceOwnerApp(context.packageName)
|
||||
} else false
|
||||
}
|
||||
|
||||
override fun setOrganizationName(name: String): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !BuildConfig.storeCompilant) {
|
||||
if (policyManager.isDeviceOwnerApp(context.packageName)) {
|
||||
return try {
|
||||
policyManager.setOrganizationName(deviceAdmin, name)
|
||||
|
||||
true
|
||||
} catch (ex: SecurityException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun openSystemPermissionScren(
|
||||
activity: FragmentActivity,
|
||||
permission: SystemPermission,
|
||||
|
|
|
@ -181,9 +181,6 @@ class DummyIntegration(
|
|||
|
||||
override fun getCurrentNetworkId(): NetworkId = NetworkId.NoNetworkConnected
|
||||
|
||||
override fun canSetOrganizationName(): Boolean = false
|
||||
override fun setOrganizationName(name: String): Boolean = false
|
||||
|
||||
override fun openSystemPermissionScren(
|
||||
activity: FragmentActivity,
|
||||
permission: SystemPermission,
|
||||
|
@ -202,5 +199,7 @@ class DummyIntegration(
|
|||
}
|
||||
|
||||
override fun getDelegations(): Map<String, List<DeviceOwnerApi.DelegationScope>> = emptyMap()
|
||||
|
||||
override fun setOrganizationName(name: String) = throw SecurityException()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,12 +80,6 @@ class DiagnoseMainFragment : Fragment(), FragmentWithCustomTitle {
|
|||
}
|
||||
})
|
||||
|
||||
binding.diagnoseOrganizationNameButton.setOnClickListener {
|
||||
if (auth.requestAuthenticationOrReturnTrue()) {
|
||||
DiagnoseOrganizationNameDialogFragment.newInstance().show(parentFragmentManager)
|
||||
}
|
||||
}
|
||||
|
||||
binding.diagnoseDom.setOnClickListener {
|
||||
requireActivity().execute(UpdateStateCommand.Diagnose.DeviceOwner)
|
||||
}
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.timelimit.android.ui.diagnose
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import io.timelimit.android.R
|
||||
import io.timelimit.android.async.Threads
|
||||
import io.timelimit.android.coroutines.runAsync
|
||||
import io.timelimit.android.data.model.UserType
|
||||
import io.timelimit.android.extensions.showSafe
|
||||
import io.timelimit.android.integration.platform.ProtectionLevel
|
||||
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.getActivityViewModel
|
||||
import io.timelimit.android.ui.util.EditTextBottomSheetDialog
|
||||
|
||||
class DiagnoseOrganizationNameDialogFragment: EditTextBottomSheetDialog() {
|
||||
companion object {
|
||||
private const val DIALOG_TAG = "DiagnoseOrganizationNameDialogFragment"
|
||||
|
||||
fun newInstance() = DiagnoseOrganizationNameDialogFragment()
|
||||
}
|
||||
|
||||
private val appLogic: AppLogic by lazy { DefaultAppLogic.with(requireContext()) }
|
||||
private val auth: ActivityViewModel by lazy { getActivityViewModel(requireActivity()) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
auth.authenticatedUser.observe(this) {
|
||||
if (it?.second?.type != UserType.Parent) {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.title = getString(R.string.diagnose_don_title)
|
||||
|
||||
if (appLogic.platformIntegration.getCurrentProtectionLevel() != ProtectionLevel.DeviceOwner) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.diagnose_don_no_owner_toast,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
} else if (!appLogic.platformIntegration.canSetOrganizationName()) {
|
||||
Toast.makeText(requireContext(), R.string.diagnose_don_not_supported_toast, Toast.LENGTH_SHORT).show()
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
} else {
|
||||
if (savedInstanceState == null) {
|
||||
runAsync {
|
||||
val name = appLogic.database.config().getCustomOrganizationName()
|
||||
|
||||
binding.editText.setText(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun go() {
|
||||
val value = binding.editText.text.toString()
|
||||
|
||||
if (appLogic.platformIntegration.setOrganizationName(value)) {
|
||||
val database = appLogic.database
|
||||
|
||||
Threads.database.execute { database.config().setCustomOrganizationName(value) }
|
||||
} else {
|
||||
Toast.makeText(requireContext(), R.string.diagnose_don_not_supported_toast, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)
|
||||
}
|
|
@ -15,11 +15,13 @@
|
|||
*/
|
||||
package io.timelimit.android.ui.diagnose.deviceowner
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -29,6 +31,7 @@ import androidx.compose.runtime.key
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -58,7 +61,22 @@ fun DeviceOwnerScreen(
|
|||
modifier = modifier.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
TextField(
|
||||
value = screen.organizationName,
|
||||
onValueChange = screen.actions.updateOrganizationName ?: {},
|
||||
enabled = screen.actions.updateOrganizationName != null,
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||
singleLine = true,
|
||||
label = { Text(stringResource(R.string.diagnose_don_title)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
AnimatedVisibility(visible = !screen.isParentAuthenticated) {
|
||||
Text(
|
||||
stringResource(R.string.diagnose_don_auth),
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
stringResource(
|
||||
|
|
|
@ -16,8 +16,12 @@
|
|||
package io.timelimit.android.ui.model.diagnose
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.Log
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
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
|
||||
|
@ -33,21 +37,33 @@ import kotlinx.coroutines.channels.Channel
|
|||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.Serializable
|
||||
|
||||
object DeviceOwnerHandling {
|
||||
private const val LOG_TAG = "DeviceOwnerHandling"
|
||||
|
||||
data class OwnerState(
|
||||
val appListDialog: AppListDialog? = null,
|
||||
val apps: List<String> = emptyList()
|
||||
): java.io.Serializable {
|
||||
val apps: List<String> = emptyList(),
|
||||
val organizationName: OrganizationName = OrganizationName.Original
|
||||
): Serializable {
|
||||
data class AppListDialog(
|
||||
val filter: String = ""
|
||||
): java.io.Serializable
|
||||
): Serializable
|
||||
}
|
||||
|
||||
sealed class OrganizationName: Serializable {
|
||||
object Original: OrganizationName()
|
||||
object Error: OrganizationName()
|
||||
class Modified(val value: String): OrganizationName()
|
||||
}
|
||||
|
||||
sealed class OwnerScreen {
|
||||
object Error: OwnerScreen()
|
||||
|
||||
data class Normal(
|
||||
val isParentAuthenticated: Boolean,
|
||||
val organizationName: String,
|
||||
val appListDialog: AppListDialog?,
|
||||
val scopes: List<DeviceOwnerApi.DelegationScope>,
|
||||
val apps: List<AppInfo>,
|
||||
|
@ -66,6 +82,7 @@ object DeviceOwnerHandling {
|
|||
)
|
||||
|
||||
data class Actions(
|
||||
val updateOrganizationName: ((String) -> Unit)?,
|
||||
val showAppListDialog: () -> Unit,
|
||||
val dismissAppListDialog: () -> Unit,
|
||||
val addApp: (String) -> Unit,
|
||||
|
@ -132,7 +149,40 @@ object DeviceOwnerHandling {
|
|||
|
||||
val refreshSignal = Channel<Unit>(Channel.CONFLATED)
|
||||
|
||||
val hadUpdateOrganizationNameErrorLive = state.map { it.organizationName == OrganizationName.Error }.distinctUntilChanged()
|
||||
val isParentAuthenticatedLive = authentication.authenticatedParentOnly.map { it != null }.distinctUntilChanged()
|
||||
|
||||
val organizationNameLive = state.map { it.organizationName }.distinctUntilChanged().map {
|
||||
when (it) {
|
||||
is OrganizationName.Modified -> it.value
|
||||
is OrganizationName.Error, OrganizationName.Original -> logic.database.config().getCustomOrganizationName() ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
val actions = OwnerScreen.Normal.Actions(
|
||||
updateOrganizationName = { organizationName ->
|
||||
updateState { state ->
|
||||
if (state.organizationName == OrganizationName.Error) state
|
||||
else state.copy(organizationName = OrganizationName.Modified(organizationName))
|
||||
}
|
||||
|
||||
launch {
|
||||
if (isParentAuthenticatedLive.first()) try {
|
||||
owner.setOrganizationName(organizationName)
|
||||
|
||||
Threads.database.executeAndWait {
|
||||
logic.database.config().setCustomOrganizationName(organizationName)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
updateState { it.copy(organizationName = OrganizationName.Error) }
|
||||
|
||||
throw ex
|
||||
} else updateState { state ->
|
||||
if (state.organizationName == OrganizationName.Error) state
|
||||
else state.copy(organizationName = OrganizationName.Original)
|
||||
}
|
||||
}
|
||||
},
|
||||
addApp = { packageName ->
|
||||
launch {
|
||||
if (authentication.authenticatedParentOnly.first() != null) updateState { state ->
|
||||
|
@ -182,16 +232,30 @@ object DeviceOwnerHandling {
|
|||
)
|
||||
|
||||
emitAll(
|
||||
appsLive.combine(dialogLive) { apps, dialog ->
|
||||
combine(
|
||||
appsLive, dialogLive, hadUpdateOrganizationNameErrorLive, isParentAuthenticatedLive, organizationNameLive
|
||||
) { apps, dialog, hadUpdateOrganizationNameError, isParentAuthenticated, organizationName ->
|
||||
OwnerScreen.Normal(
|
||||
isParentAuthenticated = isParentAuthenticated,
|
||||
organizationName = organizationName,
|
||||
appListDialog = dialog,
|
||||
scopes = scopes,
|
||||
apps = apps,
|
||||
actions = actions
|
||||
actions = actions.copy(
|
||||
updateOrganizationName =
|
||||
if (hadUpdateOrganizationNameError || !isParentAuthenticated) null
|
||||
else actions.updateOrganizationName
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}.catch { emit(OwnerScreen.Error) }
|
||||
}.catch {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOG_TAG, "error during generating screen", it)
|
||||
}
|
||||
|
||||
emit(OwnerScreen.Error)
|
||||
}
|
||||
|
||||
private fun getApps(
|
||||
integration: PlatformIntegration,
|
||||
|
|
|
@ -87,13 +87,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
style="?materialButtonOutlinedStyle"
|
||||
android:id="@+id/diagnose_organization_name_button"
|
||||
android:text="@string/diagnose_don_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
style="?materialButtonOutlinedStyle"
|
||||
android:id="@+id/diagnose_dom"
|
||||
|
|
|
@ -561,8 +561,7 @@
|
|||
<string name="diagnose_bat_level">Akkustand: %d\%%</string>
|
||||
|
||||
<string name="diagnose_don_title">Gerätebesitzerorganisationsname</string>
|
||||
<string name="diagnose_don_no_owner_toast">TimeLimit ist KEIN Geräte-Besitzer</string>
|
||||
<string name="diagnose_don_not_supported_toast">Ihr System ermöglicht es nicht, einen Organisationsnamen zu hinterlegen</string>
|
||||
<string name="diagnose_don_auth">Melden Sie sich an, um den Namen zu ändern</string>
|
||||
|
||||
<string name="diagnose_dom_title">Geräte-Besitzer</string>
|
||||
<string name="diagnose_dom_error">Diese Funktion erfordert eine ausreichend neue Android-Version und die Geräte-Besitzer-Berechtigung</string>
|
||||
|
|
|
@ -614,8 +614,7 @@
|
|||
<string name="diagnose_bat_level">Level: %d\%%</string>
|
||||
|
||||
<string name="diagnose_don_title">Device Owner Organization Name</string>
|
||||
<string name="diagnose_don_no_owner_toast">TimeLimit is NOT set as device owner</string>
|
||||
<string name="diagnose_don_not_supported_toast">Your system does not support setting the organization name</string>
|
||||
<string name="diagnose_don_auth">Sign in to change the name</string>
|
||||
|
||||
<string name="diagnose_dom_title">Device Owner</string>
|
||||
<string name="diagnose_dom_error">You need a recent Android version and the device owner permission to use this</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue