mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-24 03:16:00 +00:00
ViewModel refactoring: Users part
Bugfix and improvement (#166, #174, #177, #178)
This commit is contained in:
@@ -20,13 +20,13 @@ class ApiReceiver: BroadcastReceiver() {
|
||||
if (!permission.isNullOrEmpty()) log += "\npermission: $permission"
|
||||
try {
|
||||
@SuppressWarnings("NewApi")
|
||||
val ok = when(intent.action?.removePrefix("com.bintianqi.owndroid.action.")) {
|
||||
when(intent.action?.removePrefix("com.bintianqi.owndroid.action.")) {
|
||||
"HIDE" -> Privilege.DPM.setApplicationHidden(Privilege.DAR, app, true)
|
||||
"UNHIDE" -> Privilege.DPM.setApplicationHidden(Privilege.DAR, app, false)
|
||||
"SUSPEND" -> Privilege.DPM.setPackagesSuspended(Privilege.DAR, arrayOf(app), true).isEmpty()
|
||||
"UNSUSPEND" -> Privilege.DPM.setPackagesSuspended(Privilege.DAR, arrayOf(app), false).isEmpty()
|
||||
"ADD_USER_RESTRICTION" -> { Privilege.DPM.addUserRestriction(Privilege.DAR, restriction); true }
|
||||
"CLEAR_USER_RESTRICTION" -> { Privilege.DPM.clearUserRestriction(Privilege.DAR, restriction); true }
|
||||
"SUSPEND" -> Privilege.DPM.setPackagesSuspended(Privilege.DAR, arrayOf(app), true)
|
||||
"UNSUSPEND" -> Privilege.DPM.setPackagesSuspended(Privilege.DAR, arrayOf(app), false)
|
||||
"ADD_USER_RESTRICTION" -> { Privilege.DPM.addUserRestriction(Privilege.DAR, restriction) }
|
||||
"CLEAR_USER_RESTRICTION" -> { Privilege.DPM.clearUserRestriction(Privilege.DAR, restriction) }
|
||||
"SET_PERMISSION_DEFAULT" -> {
|
||||
Privilege.DPM.setPermissionGrantState(
|
||||
Privilege.DAR, app!!, permission!!,
|
||||
@@ -45,30 +45,31 @@ class ApiReceiver: BroadcastReceiver() {
|
||||
DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
|
||||
)
|
||||
}
|
||||
"LOCK" -> { Privilege.DPM.lockNow(); true }
|
||||
"REBOOT" -> { Privilege.DPM.reboot(Privilege.DAR); true }
|
||||
"LOCK" -> { Privilege.DPM.lockNow() }
|
||||
"REBOOT" -> { Privilege.DPM.reboot(Privilege.DAR) }
|
||||
"SET_CAMERA_DISABLED" -> {
|
||||
Privilege.DPM.setCameraDisabled(Privilege.DAR, true)
|
||||
true
|
||||
}
|
||||
"SET_CAMERA_ENABLED" -> {
|
||||
Privilege.DPM.setCameraDisabled(Privilege.DAR, false)
|
||||
true
|
||||
}
|
||||
"SET_USB_DISABLED" -> {
|
||||
Privilege.DPM.isUsbDataSignalingEnabled = false
|
||||
true
|
||||
}
|
||||
"SET_USB_ENABLED" -> {
|
||||
Privilege.DPM.isUsbDataSignalingEnabled = true
|
||||
true
|
||||
}
|
||||
"SET_SCREEN_CAPTURE_DISABLED" -> {
|
||||
Privilege.DPM.setScreenCaptureDisabled(Privilege.DAR, true)
|
||||
}
|
||||
"SET_SCREEN_CAPTURE_ENABLED" -> {
|
||||
Privilege.DPM.setScreenCaptureDisabled(Privilege.DAR, false)
|
||||
}
|
||||
else -> {
|
||||
log += "\nInvalid action"
|
||||
false
|
||||
}
|
||||
}
|
||||
log += "\nsuccess: $ok"
|
||||
} catch(e: Exception) {
|
||||
e.printStackTrace()
|
||||
val message = (e::class.qualifiedName ?: "Exception") + ": " + (e.message ?: "")
|
||||
|
||||
@@ -547,14 +547,24 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
vm::setUserRestriction, ::navigateUp)
|
||||
}
|
||||
|
||||
composable<Users> { UsersScreen(::navigateUp, ::navigate) }
|
||||
composable<UserInfo> { UserInfoScreen(::navigateUp) }
|
||||
composable<UsersOptions> { UsersOptionsScreen(::navigateUp) }
|
||||
composable<UserOperation> { UserOperationScreen(::navigateUp) }
|
||||
composable<CreateUser> { CreateUserScreen(::navigateUp) }
|
||||
composable<ChangeUsername> { ChangeUsernameScreen(::navigateUp) }
|
||||
composable<UserSessionMessage> { UserSessionMessageScreen(::navigateUp) }
|
||||
composable<AffiliationId> { AffiliationIdScreen(::navigateUp) }
|
||||
composable<Users> { UsersScreen(vm, ::navigateUp, ::navigate) }
|
||||
composable<UserInfo> { UserInfoScreen(vm::getUserInformation, ::navigateUp) }
|
||||
composable<UsersOptions> {
|
||||
UsersOptionsScreen(vm::getLogoutEnabled, vm::setLogoutEnabled, ::navigateUp)
|
||||
}
|
||||
composable<UserOperation> {
|
||||
UserOperationScreen(vm::startUser, vm::switchUser, vm::stopUser, vm::deleteUser, ::navigateUp)
|
||||
}
|
||||
composable<CreateUser> { CreateUserScreen(vm::createUser, ::navigateUp) }
|
||||
composable<ChangeUsername> { ChangeUsernameScreen(vm::setProfileName, ::navigateUp) }
|
||||
composable<UserSessionMessage> {
|
||||
UserSessionMessageScreen(vm::getUserSessionMessages, vm::setStartUserSessionMessage,
|
||||
vm::setEndUserSessionMessage, ::navigateUp)
|
||||
}
|
||||
composable<AffiliationId> {
|
||||
AffiliationIdScreen(vm.affiliationIds, vm::getAffiliationIds, vm::setAffiliationId,
|
||||
::navigateUp)
|
||||
}
|
||||
|
||||
composable<Password> { PasswordScreen(::navigateUp, ::navigate) }
|
||||
composable<PasswordInfo> { PasswordInfoScreen(::navigateUp) }
|
||||
|
||||
@@ -20,9 +20,13 @@ import android.content.IntentFilter
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Build.VERSION
|
||||
import android.os.HardwarePropertiesManager
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
@@ -36,6 +40,7 @@ import com.bintianqi.owndroid.Privilege.DPM
|
||||
import com.bintianqi.owndroid.dpm.ACTIVATE_DEVICE_OWNER_COMMAND
|
||||
import com.bintianqi.owndroid.dpm.AppStatus
|
||||
import com.bintianqi.owndroid.dpm.CaCertInfo
|
||||
import com.bintianqi.owndroid.dpm.CreateUserResult
|
||||
import com.bintianqi.owndroid.dpm.CreateWorkProfileOptions
|
||||
import com.bintianqi.owndroid.dpm.DelegatedAdmin
|
||||
import com.bintianqi.owndroid.dpm.DeviceAdmin
|
||||
@@ -46,6 +51,7 @@ import com.bintianqi.owndroid.dpm.IntentFilterOptions
|
||||
import com.bintianqi.owndroid.dpm.PendingSystemUpdateInfo
|
||||
import com.bintianqi.owndroid.dpm.SystemOptionsStatus
|
||||
import com.bintianqi.owndroid.dpm.SystemUpdatePolicyInfo
|
||||
import com.bintianqi.owndroid.dpm.UserInformation
|
||||
import com.bintianqi.owndroid.dpm.activateOrgProfileCommand
|
||||
import com.bintianqi.owndroid.dpm.delegatedScopesList
|
||||
import com.bintianqi.owndroid.dpm.getPackageInstaller
|
||||
@@ -68,6 +74,8 @@ import java.security.MessageDigest
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
@@ -124,11 +132,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
|
||||
val hiddenPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||
fun getHiddenPackages() {
|
||||
viewModelScope.launch {
|
||||
hiddenPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter {
|
||||
DPM.isApplicationHidden(DAR, it.packageName)
|
||||
}.map { getAppInfo(it) }
|
||||
}
|
||||
hiddenPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter {
|
||||
DPM.isApplicationHidden(DAR, it.packageName)
|
||||
}.map { getAppInfo(it) }
|
||||
}
|
||||
fun setPackageHidden(name: String, status: Boolean): Boolean {
|
||||
val result = DPM.setApplicationHidden(DAR, name, status)
|
||||
@@ -139,11 +145,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
// Uninstall blocked packages
|
||||
val ubPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||
fun getUbPackages() {
|
||||
viewModelScope.launch {
|
||||
ubPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter {
|
||||
DPM.isUninstallBlocked(DAR, it.packageName)
|
||||
}.map { getAppInfo(it) }
|
||||
}
|
||||
ubPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter {
|
||||
DPM.isUninstallBlocked(DAR, it.packageName)
|
||||
}.map { getAppInfo(it) }
|
||||
}
|
||||
fun setPackageUb(name: String, status: Boolean) {
|
||||
DPM.setUninstallBlocked(DAR, name, status)
|
||||
@@ -421,19 +425,33 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
@RequiresApi(24)
|
||||
fun requestBugReport(): Boolean {
|
||||
return DPM.requestBugreport(DAR)
|
||||
return try {
|
||||
DPM.requestBugreport(DAR)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
@RequiresApi(24)
|
||||
fun getOrgName(): String {
|
||||
return DPM.getOrganizationName(DAR).toString()
|
||||
return try {
|
||||
DPM.getOrganizationName(DAR)?.toString() ?: ""
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
@RequiresApi(24)
|
||||
fun setOrgName(name: String) {
|
||||
DPM.setOrganizationName(DAR, name)
|
||||
}
|
||||
@RequiresApi(31)
|
||||
fun setOrgId(id: String) {
|
||||
DPM.setOrganizationId(id)
|
||||
fun setOrgId(id: String): Boolean {
|
||||
return try {
|
||||
DPM.setOrganizationId(id)
|
||||
true
|
||||
} catch (_: IllegalStateException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
@RequiresApi(31)
|
||||
fun getEnrollmentSpecificId(): String {
|
||||
@@ -557,12 +575,16 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
properties.temperatures.isEmpty()) {
|
||||
break
|
||||
}
|
||||
hardwareProperties.value = properties
|
||||
delay(hpRefreshInterval)
|
||||
}
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun setTime(time: Long): Boolean {
|
||||
return DPM.setTime(DAR, time)
|
||||
fun setTime(time: Long, useCurrentTz: Boolean): Boolean {
|
||||
val offset = if (useCurrentTz) {
|
||||
ZonedDateTime.now(ZoneId.systemDefault()).offset.totalSeconds * 1000L
|
||||
} else 0L
|
||||
return DPM.setTime(DAR, time - offset)
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun setTimeZone(tz: String): Boolean {
|
||||
@@ -674,9 +696,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
val installedCaCerts = MutableStateFlow(emptyList<CaCertInfo>())
|
||||
fun getCaCerts() {
|
||||
viewModelScope.launch {
|
||||
installedCaCerts.value = DPM.getInstalledCaCerts(DAR).mapNotNull { parseCaCert(it) }
|
||||
}
|
||||
installedCaCerts.value = DPM.getInstalledCaCerts(DAR).mapNotNull { parseCaCert(it) }
|
||||
}
|
||||
fun parseCaCert(uri: Uri): CaCertInfo? {
|
||||
return try {
|
||||
@@ -696,7 +716,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
CaCertInfo(
|
||||
hash, cert.serialNumber.toString(16),
|
||||
cert.issuerX500Principal.name, cert.subjectX500Principal.name,
|
||||
parseDate(cert.notBefore), parseDate(cert.notAfter), bytes
|
||||
cert.notBefore.time, cert.notAfter.time, bytes
|
||||
)
|
||||
} catch (e: CertificateException) {
|
||||
e.printStackTrace()
|
||||
@@ -809,7 +829,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
return DPM.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
|
||||
}
|
||||
fun activateDoByShizuku(callback: (Boolean, String?) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
useShizuku(application) { service ->
|
||||
try {
|
||||
val result = IUserService.Stub.asInterface(service)
|
||||
@@ -887,7 +907,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
val dhizukuClients = MutableStateFlow(emptyList<Pair<DhizukuClientInfo, AppInfo>>())
|
||||
fun getDhizukuClients() {
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
dhizukuClients.value = myRepo.getDhizukuClients().mapNotNull {
|
||||
val packageName = PM.getNameForUid(it.uid)
|
||||
if (packageName == null) {
|
||||
@@ -985,7 +1005,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
val deviceAdminReceivers = MutableStateFlow(emptyList<DeviceAdmin>())
|
||||
fun getDeviceAdminReceivers() {
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
deviceAdminReceivers.value = PM.queryBroadcastReceivers(
|
||||
Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
|
||||
PackageManager.GET_META_DATA
|
||||
@@ -1064,7 +1084,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
return intent
|
||||
}
|
||||
fun activateOrgProfileByShizuku(callback: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
var succeed = false
|
||||
useShizuku(application) { service ->
|
||||
val result = IUserService.Stub.asInterface(service).execute(activateOrgProfileCommand)
|
||||
@@ -1102,6 +1122,130 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
DPM.addCrossProfileIntentFilter(DAR, filter, flags)
|
||||
}
|
||||
|
||||
val UM = application.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
@RequiresApi(28)
|
||||
fun getLogoutEnabled(): Boolean {
|
||||
return DPM.isLogoutEnabled
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun setLogoutEnabled(enabled: Boolean) {
|
||||
DPM.setLogoutEnabled(DAR, enabled)
|
||||
}
|
||||
fun getUserInformation(): UserInformation {
|
||||
val uh = Binder.getCallingUserHandle()
|
||||
return UserInformation(
|
||||
if (VERSION.SDK_INT >= 24) UserManager.supportsMultipleUsers() else false,
|
||||
if (VERSION.SDK_INT >= 31) UserManager.isHeadlessSystemUserMode() else false,
|
||||
if (VERSION.SDK_INT >= 23) UM.isSystemUser else false,
|
||||
if (VERSION.SDK_INT >= 34) UM.isAdminUser else false,
|
||||
if (VERSION.SDK_INT >= 25) UM.isDemoUser else false,
|
||||
if (VERSION.SDK_INT >= 23) UM.getUserCreationTime(uh) else 0,
|
||||
if (VERSION.SDK_INT >= 28) DPM.isLogoutEnabled else false,
|
||||
if (VERSION.SDK_INT >= 28) DPM.isEphemeralUser(DAR) else false,
|
||||
if (VERSION.SDK_INT >= 28) DPM.isAffiliatedUser else false,
|
||||
UM.getSerialNumberForUser(uh)
|
||||
)
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun startUser(id: Int, isUserId: Boolean): Int {
|
||||
val uh = getUserHandle(id, isUserId)
|
||||
if (uh == null) return R.string.user_not_exist
|
||||
return getUserOperationResultText(DPM.startUserInBackground(DAR, uh))
|
||||
}
|
||||
fun switchUser(id: Int, isUserId: Boolean): Boolean {
|
||||
val uh = getUserHandle(id, isUserId)
|
||||
if (uh == null) return false
|
||||
DPM.switchUser(DAR, uh)
|
||||
return true
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun stopUser(id: Int, isUserId: Boolean): Int {
|
||||
val uh = getUserHandle(id, isUserId)
|
||||
if (uh == null) return R.string.user_not_exist
|
||||
return getUserOperationResultText(DPM.stopUser(DAR, uh))
|
||||
}
|
||||
fun deleteUser(id: Int, isUserId: Boolean): Boolean {
|
||||
val uh = getUserHandle(id, isUserId)
|
||||
if (uh == null) return false
|
||||
return DPM.removeUser(DAR, uh)
|
||||
}
|
||||
fun getUserHandle(id: Int, isUserId: Boolean): UserHandle? {
|
||||
return if (isUserId && VERSION.SDK_INT >= 24) {
|
||||
UserHandle.getUserHandleForUid(id * 100000)
|
||||
} else {
|
||||
UM.getUserForSerialNumber(id.toLong())
|
||||
}
|
||||
}
|
||||
fun getUserOperationResultText(code: Int): Int {
|
||||
return when (code) {
|
||||
UserManager.USER_OPERATION_SUCCESS -> R.string.success
|
||||
UserManager.USER_OPERATION_ERROR_UNKNOWN -> R.string.unknown_error
|
||||
UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE-> R.string.fail_managed_profile
|
||||
UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS -> R.string.limit_reached
|
||||
UserManager.USER_OPERATION_ERROR_CURRENT_USER -> R.string.fail_current_user
|
||||
else -> R.string.unknown
|
||||
}
|
||||
}
|
||||
@RequiresApi(24)
|
||||
fun createUser(name: String, flags: Int, callback: (CreateUserResult) -> Unit) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val uh = DPM.createAndManageUser(DAR, name, DAR, null, flags)
|
||||
if (uh == null) {
|
||||
callback(CreateUserResult(R.string.failed))
|
||||
} else {
|
||||
callback(CreateUserResult(R.string.succeeded, UM.getSerialNumberForUser(uh)))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
if (VERSION.SDK_INT >= 28 && e is UserManager.UserOperationException) {
|
||||
callback(CreateUserResult(getUserOperationResultText(e.userOperationResult)))
|
||||
} else {
|
||||
callback(CreateUserResult(R.string.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val affiliationIds = MutableStateFlow(emptyList<String>())
|
||||
@RequiresApi(26)
|
||||
fun getAffiliationIds() {
|
||||
affiliationIds.value = DPM.getAffiliationIds(DAR).toList()
|
||||
}
|
||||
@RequiresApi(26)
|
||||
fun setAffiliationId(id: String, state: Boolean) {
|
||||
val newList = affiliationIds.value.run { if (state) plus(id) else minus(id) }
|
||||
DPM.setAffiliationIds(DAR, newList.toSet())
|
||||
affiliationIds.value = newList
|
||||
}
|
||||
fun setProfileName(name: String) {
|
||||
DPM.setProfileName(DAR, name)
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun setUserIcon(bitmap: Bitmap) {
|
||||
DPM.setUserIcon(DAR, bitmap)
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun getSecondaryUsers(): List<Long> {
|
||||
return DPM.getSecondaryUsers(DAR).map { UM.getSerialNumberForUser(it) }
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun getUserSessionMessages(): Pair<String, String> {
|
||||
return (DPM.getStartUserSessionMessage(DAR)?.toString() ?: "") to
|
||||
(DPM.getEndUserSessionMessage(DAR)?.toString() ?: "")
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun setStartUserSessionMessage(message: String?) {
|
||||
DPM.setStartUserSessionMessage(DAR, message)
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun setEndUserSessionMessage(message: String?) {
|
||||
DPM.setEndUserSessionMessage(DAR, message)
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun logoutUser(): Int {
|
||||
return getUserOperationResultText(DPM.logoutUser(DAR))
|
||||
}
|
||||
}
|
||||
|
||||
data class ThemeSettings(
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.SaverScope
|
||||
@@ -23,9 +22,6 @@ import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -72,20 +68,12 @@ fun formatFileSize(bytes: Long): String {
|
||||
val Boolean.yesOrNo
|
||||
@StringRes get() = if(this) R.string.yes else R.string.no
|
||||
|
||||
@RequiresApi(26)
|
||||
fun parseTimestamp(timestamp: Long): String {
|
||||
val instant = Instant.ofEpochMilli(timestamp)
|
||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault())
|
||||
return formatter.format(instant)
|
||||
fun formatTime(ms: Long): String {
|
||||
return SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(Date(ms))
|
||||
}
|
||||
fun formatDate(date: Date): String {
|
||||
return SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(date)
|
||||
}
|
||||
|
||||
fun parseDate(date: Date): String = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(date)
|
||||
|
||||
val Long.humanReadableDate: String
|
||||
get() = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(Date(this))
|
||||
|
||||
fun formatDate(pattern: String, value: Long): String
|
||||
= SimpleDateFormat(pattern, Locale.getDefault()).format(Date(value))
|
||||
|
||||
fun Context.showOperationResultToast(success: Boolean) {
|
||||
popToast(if(success) R.string.success else R.string.failed)
|
||||
|
||||
@@ -53,6 +53,7 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
@@ -130,9 +131,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.HorizontalPadding
|
||||
import com.bintianqi.owndroid.Privilege
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.formatDate
|
||||
import com.bintianqi.owndroid.formatFileSize
|
||||
import com.bintianqi.owndroid.humanReadableDate
|
||||
import com.bintianqi.owndroid.formatTime
|
||||
import com.bintianqi.owndroid.popToast
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
import com.bintianqi.owndroid.ui.CheckBoxItem
|
||||
@@ -157,9 +157,6 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.InetAddress
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.reflect.jvm.jvmErasure
|
||||
|
||||
@Serializable object Network
|
||||
@@ -1036,14 +1033,14 @@ fun NetworkStatsScreen(
|
||||
}
|
||||
}
|
||||
OutlinedTextField(
|
||||
value = startTime.let { if(it == -1L) "" else it.humanReadableDate }, onValueChange = {}, readOnly = true,
|
||||
value = startTime.let { if(it == -1L) "" else formatTime(it) }, onValueChange = {}, readOnly = true,
|
||||
label = { Text(stringResource(R.string.start_time)) },
|
||||
interactionSource = startTimeTextFieldInteractionSource,
|
||||
isError = startTime >= endTime,
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = endTime.humanReadableDate, onValueChange = {}, readOnly = true,
|
||||
value = formatTime(endTime), onValueChange = {}, readOnly = true,
|
||||
label = { Text(stringResource(R.string.end_time)) },
|
||||
interactionSource = endTimeTextFieldInteractionSource,
|
||||
isError = startTime >= endTime,
|
||||
@@ -1315,18 +1312,9 @@ fun NetworkStatsViewerScreen(nsv: NetworkStatsViewer, onNavigateUp: () -> Unit)
|
||||
HorizontalPager(ps, Modifier.padding(top = 8.dp)) { page ->
|
||||
val data = nsv.stats[page]
|
||||
Column(Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)) {
|
||||
Row(Modifier.align(Alignment.CenterHorizontally).padding(bottom = 8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
SimpleDateFormat("", Locale.getDefault()).format(Date(data.startTime))
|
||||
Text(
|
||||
formatDate("yyyy/MM/dd", data.startTime) + "\n" + formatDate("HH:mm:ss", data.startTime),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Text("~", Modifier.padding(horizontal = 8.dp))
|
||||
Text(
|
||||
formatDate("yyyy/MM/dd", data.endTime) + "\n" + formatDate("HH:mm:ss", data.endTime),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
Text(formatTime(data.startTime) + "\n~\n" + formatTime(data.endTime),
|
||||
Modifier.align(Alignment.CenterHorizontally), textAlign = TextAlign.Center)
|
||||
Spacer(Modifier.height(5.dp))
|
||||
val txBytes = data.txBytes
|
||||
Text(stringResource(R.string.transmitted), style = typography.titleLarge)
|
||||
Column(modifier = Modifier.padding(start = 8.dp, bottom = 4.dp)) {
|
||||
|
||||
@@ -112,8 +112,7 @@ import com.bintianqi.owndroid.Privilege
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.SP
|
||||
import com.bintianqi.owndroid.formatFileSize
|
||||
import com.bintianqi.owndroid.humanReadableDate
|
||||
import com.bintianqi.owndroid.parseTimestamp
|
||||
import com.bintianqi.owndroid.formatTime
|
||||
import com.bintianqi.owndroid.popToast
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
import com.bintianqi.owndroid.ui.CheckBoxItem
|
||||
@@ -290,7 +289,7 @@ fun SystemManagerScreen(
|
||||
onClick = {
|
||||
if (dialog == 3 && VERSION.SDK_INT >= 24) vm.setOrgName(input)
|
||||
if (dialog == 4 && VERSION.SDK_INT >= 31) {
|
||||
vm.setOrgId(input)
|
||||
context.showOperationResultToast(vm.setOrgId(input))
|
||||
enrollmentSpecificId = vm.getEnrollmentSpecificId()
|
||||
}
|
||||
dialog = 0
|
||||
@@ -368,6 +367,24 @@ fun SystemOptionsScreen(vm: MyViewModel, onNavigateUp: () -> Unit) {
|
||||
SwitchItem(R.string.enable_usb_signal, status.usbSignalEnabled,
|
||||
vm::setUsbSignalEnabled, R.drawable.usb_fill0)
|
||||
}
|
||||
if (VERSION.SDK_INT >= 23 && VERSION.SDK_INT < 34) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(stringResource(R.string.status_bar), style = typography.titleMedium)
|
||||
Button({
|
||||
vm.setStatusBarDisabled(true)
|
||||
}, Modifier.padding(horizontal = 4.dp)) {
|
||||
Text(stringResource(R.string.disable))
|
||||
}
|
||||
Button({
|
||||
vm.setStatusBarDisabled(false)
|
||||
}) {
|
||||
Text(stringResource(R.string.enable))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(dialog != 0) AlertDialog(
|
||||
text = {
|
||||
@@ -520,7 +537,7 @@ fun HardwareMonitorScreen(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
fun ChangeTimeScreen(setTime: (Long) -> Boolean, onNavigateUp: () -> Unit) {
|
||||
fun ChangeTimeScreen(setTime: (Long, Boolean) -> Boolean, onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var tab by remember { mutableIntStateOf(0) }
|
||||
@@ -528,8 +545,9 @@ fun ChangeTimeScreen(setTime: (Long) -> Boolean, onNavigateUp: () -> Unit) {
|
||||
tab = pagerState.currentPage
|
||||
val coroutine = rememberCoroutineScope()
|
||||
var picker by remember { mutableIntStateOf(0) } //0:None, 1:DatePicker, 2:TimePicker
|
||||
var useCurrentTz by remember { mutableStateOf(true) }
|
||||
val datePickerState = rememberDatePickerState()
|
||||
val timePickerState = rememberTimePickerState()
|
||||
val timePickerState = rememberTimePickerState(is24Hour = true)
|
||||
val dateInteractionSource = remember { MutableInteractionSource() }
|
||||
val timeInteractionSource = remember { MutableInteractionSource() }
|
||||
if(dateInteractionSource.collectIsPressedAsState().value) picker = 1
|
||||
@@ -571,14 +589,15 @@ fun ChangeTimeScreen(setTime: (Long) -> Boolean, onNavigateUp: () -> Unit) {
|
||||
) {
|
||||
if(page == 0) {
|
||||
OutlinedTextField(
|
||||
value = datePickerState.selectedDateMillis?.humanReadableDate ?: "",
|
||||
value = datePickerState.selectedDateMillis?.let { formatTime(it) } ?: "",
|
||||
onValueChange = {}, readOnly = true,
|
||||
label = { Text(stringResource(R.string.date)) },
|
||||
interactionSource = dateInteractionSource,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = timePickerState.hour.toString() + ":" + timePickerState.minute.toString(),
|
||||
value = timePickerState.hour.toString().padStart(2, '0') + ":" +
|
||||
timePickerState.minute.toString().padStart(2, '0'),
|
||||
onValueChange = {}, readOnly = true,
|
||||
label = { Text(stringResource(R.string.time)) },
|
||||
interactionSource = timeInteractionSource,
|
||||
@@ -586,11 +605,14 @@ fun ChangeTimeScreen(setTime: (Long) -> Boolean, onNavigateUp: () -> Unit) {
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp)
|
||||
)
|
||||
CheckBoxItem(R.string.use_current_timezone, useCurrentTz) {
|
||||
useCurrentTz = it
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
val timeMillis = datePickerState.selectedDateMillis!! +
|
||||
timePickerState.hour * 3600000 + timePickerState.minute * 60000
|
||||
context.showOperationResultToast(setTime(timeMillis))
|
||||
context.showOperationResultToast(setTime(timeMillis, useCurrentTz))
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = datePickerState.selectedDateMillis != null
|
||||
@@ -609,7 +631,7 @@ fun ChangeTimeScreen(setTime: (Long) -> Boolean, onNavigateUp: () -> Unit) {
|
||||
)
|
||||
Button(
|
||||
onClick = {
|
||||
context.showOperationResultToast(setTime(inputTime.toLong()))
|
||||
context.showOperationResultToast(setTime(inputTime.toLong(), false))
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -653,11 +675,14 @@ fun ChangeTimeZoneScreen(setTimeZone: (String) -> Boolean, onNavigateUp: () -> U
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var inputTimezone by remember { mutableStateOf("") }
|
||||
var dialog by remember { mutableStateOf(false) }
|
||||
MyScaffold(R.string.change_timezone, onNavigateUp) {
|
||||
val availableIds = TimeZone.getAvailableIDs()
|
||||
val validInput = inputTimezone in availableIds
|
||||
MyScaffold(R.string.change_timezone, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = inputTimezone,
|
||||
label = { Text(stringResource(R.string.timezone_id)) },
|
||||
onValueChange = { inputTimezone = it },
|
||||
isError = inputTimezone.isNotEmpty() && !validInput,
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { dialog = true }) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Default.List, contentDescription = null)
|
||||
@@ -673,7 +698,7 @@ fun ChangeTimeZoneScreen(setTimeZone: (String) -> Boolean, onNavigateUp: () -> U
|
||||
context.showOperationResultToast(setTimeZone(inputTimezone))
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = inputTimezone.isNotEmpty()
|
||||
enabled = inputTimezone.isNotEmpty() && validInput
|
||||
) {
|
||||
Text(stringResource(R.string.apply))
|
||||
}
|
||||
@@ -683,7 +708,7 @@ fun ChangeTimeZoneScreen(setTimeZone: (String) -> Boolean, onNavigateUp: () -> U
|
||||
if(dialog) AlertDialog(
|
||||
text = {
|
||||
LazyColumn {
|
||||
items(TimeZone.getAvailableIDs()) {
|
||||
items(availableIds) {
|
||||
Text(
|
||||
text = it,
|
||||
modifier = Modifier
|
||||
@@ -1322,8 +1347,8 @@ data class CaCertInfo(
|
||||
val serialNumber: String,
|
||||
val issuer: String,
|
||||
val subject: String,
|
||||
val issuedTime: String,
|
||||
val expiresTime: String,
|
||||
val issuedTime: Long,
|
||||
val expiresTime: Long,
|
||||
val bytes: ByteArray
|
||||
)
|
||||
|
||||
@@ -1373,8 +1398,7 @@ fun CaCertScreen(
|
||||
}) {
|
||||
Icon(Icons.Default.Add, stringResource(R.string.install))
|
||||
}
|
||||
},
|
||||
contentWindowInsets = WindowInsets.ime
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
Modifier
|
||||
@@ -1388,6 +1412,7 @@ fun CaCertScreen(
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
selectedCaCert = cert
|
||||
dialog = 2
|
||||
}
|
||||
.animateItem()
|
||||
.padding(vertical = 10.dp, horizontal = 8.dp)
|
||||
@@ -1412,9 +1437,9 @@ fun CaCertScreen(
|
||||
Text("Issuer", style = typography.labelLarge)
|
||||
SelectionContainer { Text(cert.issuer) }
|
||||
Text("Issued on", style = typography.labelLarge)
|
||||
SelectionContainer { Text(cert.issuedTime) }
|
||||
SelectionContainer { Text(formatTime(cert.issuedTime)) }
|
||||
Text("Expires on", style = typography.labelLarge)
|
||||
SelectionContainer { Text(cert.expiresTime) }
|
||||
SelectionContainer { Text(formatTime(cert.expiresTime)) }
|
||||
Text("SHA-256 fingerprint", style = typography.labelLarge)
|
||||
SelectionContainer { Text(cert.hash) }
|
||||
if (dialog == 2) Row(
|
||||
@@ -1693,17 +1718,17 @@ fun FrpPolicyScreen(
|
||||
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
focusMgr.clearFocus()
|
||||
setFrpPolicy(FrpPolicyInfo(true, usePolicy, enabled, accountList))
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.apply))
|
||||
Button(
|
||||
onClick = {
|
||||
focusMgr.clearFocus()
|
||||
setFrpPolicy(FrpPolicyInfo(true, usePolicy, enabled, accountList))
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.apply))
|
||||
}
|
||||
}
|
||||
}
|
||||
Notes(R.string.info_frp_policy, HorizontalPadding)
|
||||
@@ -1755,7 +1780,7 @@ fun WipeDataScreen(
|
||||
dialog = 1
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth().padding(HorizontalPadding, 5.dp)
|
||||
) {
|
||||
Text("WipeData")
|
||||
}
|
||||
@@ -1767,7 +1792,7 @@ fun WipeDataScreen(
|
||||
dialog = 2
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth().padding(HorizontalPadding, 5.dp)
|
||||
) {
|
||||
Text("WipeDevice")
|
||||
}
|
||||
@@ -1905,8 +1930,7 @@ fun SystemUpdatePolicyScreen(
|
||||
if (VERSION.SDK_INT >= 26) {
|
||||
Column(Modifier.padding(HorizontalPadding)) {
|
||||
if (pendingUpdate.exists) {
|
||||
Text(stringResource(R.string.update_received_time,
|
||||
parseTimestamp(pendingUpdate.time)))
|
||||
Text(stringResource(R.string.update_received_time, formatTime(pendingUpdate.time)))
|
||||
Text(stringResource(R.string.is_security_patch,
|
||||
stringResource(pendingUpdate.securityPatch.yesOrNo)))
|
||||
} else {
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
package com.bintianqi.owndroid.dpm
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Binder
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Process
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -20,6 +15,7 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -44,11 +40,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -62,9 +55,10 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.HorizontalPadding
|
||||
import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.Privilege
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.parseTimestamp
|
||||
import com.bintianqi.owndroid.formatTime
|
||||
import com.bintianqi.owndroid.popToast
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
import com.bintianqi.owndroid.ui.CircularProgressDialog
|
||||
@@ -77,17 +71,16 @@ import com.bintianqi.owndroid.ui.Notes
|
||||
import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import com.bintianqi.owndroid.uriToStream
|
||||
import com.bintianqi.owndroid.yesOrNo
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable object Users
|
||||
|
||||
@Composable
|
||||
fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
fun UsersScreen(vm: MyViewModel, onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||
/** 1: secondary users, 2: logout*/
|
||||
var dialog by remember { mutableIntStateOf(0) }
|
||||
MyScaffold(R.string.users, onNavigateUp, 0.dp) {
|
||||
if(VERSION.SDK_INT >= 28 && privilege.profile && privilege.affiliated) {
|
||||
@@ -118,7 +111,11 @@ fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
context.popToast(R.string.select_an_image)
|
||||
launcher.launch("image/*")
|
||||
}
|
||||
if(changeUserIconDialog) ChangeUserIconDialog(bitmap!!) { changeUserIconDialog = false }
|
||||
if (changeUserIconDialog) ChangeUserIconDialog(
|
||||
bitmap!!, {
|
||||
vm.setUserIcon(bitmap!!)
|
||||
changeUserIconDialog = false
|
||||
}) { changeUserIconDialog = false }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 28 && privilege.device) {
|
||||
FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) }
|
||||
@@ -127,36 +124,39 @@ fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
FunctionItem(R.string.affiliation_id, icon = R.drawable.id_card_fill0) { onNavigate(AffiliationId) }
|
||||
}
|
||||
}
|
||||
if(dialog != 0 && VERSION.SDK_INT >= 28) AlertDialog(
|
||||
title = { Text(stringResource(if(dialog == 1) R.string.secondary_users else R.string.logout)) },
|
||||
if (VERSION.SDK_INT >= 28 && dialog == 1) AlertDialog(
|
||||
title = { Text(stringResource(R.string.secondary_users)) },
|
||||
text = {
|
||||
if(dialog == 1) {
|
||||
val um = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
val list = Privilege.DPM.getSecondaryUsers(Privilege.DAR)
|
||||
if(list.isEmpty()) {
|
||||
Text(stringResource(R.string.no_secondary_users))
|
||||
} else {
|
||||
Text("(" + stringResource(R.string.serial_number) + ")\n" + list.joinToString("\n") { um.getSerialNumberForUser(it).toString() })
|
||||
}
|
||||
val list = vm.getSecondaryUsers()
|
||||
val text = if (list.isEmpty()) {
|
||||
stringResource(R.string.no_secondary_users)
|
||||
} else {
|
||||
Text(stringResource(R.string.info_logout))
|
||||
"(" + stringResource(R.string.serial_number) + ")\n" + list.joinToString("\n")
|
||||
}
|
||||
Text(text)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
if(dialog == 2) {
|
||||
val result = Privilege.DPM.logoutUser(Privilege.DAR)
|
||||
context.popToast(userOperationResultCode(result))
|
||||
}
|
||||
dialog = 0
|
||||
}
|
||||
) {
|
||||
TextButton({ dialog = 0 }) {
|
||||
Text(stringResource(R.string.confirm))
|
||||
}
|
||||
},
|
||||
onDismissRequest = { dialog = 0 }
|
||||
)
|
||||
if (VERSION.SDK_INT >= 28 && dialog == 2) AlertDialog(
|
||||
title = { Text(stringResource(R.string.logout)) },
|
||||
text = {
|
||||
Text(stringResource(R.string.info_logout))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton({
|
||||
context.popToast(vm.logoutUser())
|
||||
dialog = 0
|
||||
}) {
|
||||
Text(stringResource(R.string.confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
if(dialog != 1) TextButton(onClick = { dialog = 0 }) {
|
||||
TextButton({ dialog = 0 }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
@@ -167,41 +167,53 @@ fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
@Serializable object UsersOptions
|
||||
|
||||
@Composable
|
||||
fun UsersOptionsScreen(onNavigateUp: () -> Unit) {
|
||||
fun UsersOptionsScreen(
|
||||
getLogoutEnabled: () -> Boolean, setLogoutEnabled: (Boolean) -> Unit, onNavigateUp: () -> Unit
|
||||
) {
|
||||
var logoutEnabled by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) { logoutEnabled = getLogoutEnabled() }
|
||||
MyScaffold(R.string.options, onNavigateUp, 0.dp) {
|
||||
if(VERSION.SDK_INT >= 28) {
|
||||
SwitchItem(R.string.enable_logout, getState = { Privilege.DPM.isLogoutEnabled },
|
||||
onCheckedChange = { Privilege.DPM.setLogoutEnabled(Privilege.DAR, it) })
|
||||
SwitchItem(R.string.enable_logout, logoutEnabled, {
|
||||
setLogoutEnabled(it)
|
||||
logoutEnabled = it
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UserInformation(
|
||||
val multiUser: Boolean = false, val headless: Boolean = false, val system: Boolean = false,
|
||||
val admin: Boolean = false, val demo: Boolean = false, val time: Long = 0,
|
||||
val logout: Boolean = false, val ephemeral: Boolean = false, val affiliated: Boolean = false,
|
||||
val serial: Long = 0
|
||||
)
|
||||
|
||||
@Serializable object UserInfo
|
||||
|
||||
@Composable
|
||||
fun UserInfoScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
val user = Process.myUserHandle()
|
||||
fun UserInfoScreen(getInfo: () -> UserInformation, onNavigateUp: () -> Unit) {
|
||||
var info by remember { mutableStateOf(UserInformation()) }
|
||||
var infoDialog by remember { mutableIntStateOf(0) }
|
||||
LaunchedEffect(Unit) {
|
||||
info = getInfo()
|
||||
}
|
||||
MyScaffold(R.string.user_info, onNavigateUp, 0.dp) {
|
||||
if(VERSION.SDK_INT >= 24) InfoItem(R.string.support_multiuser, UserManager.supportsMultipleUsers().yesOrNo)
|
||||
if(VERSION.SDK_INT >= 31) InfoItem(R.string.headless_system_user_mode, UserManager.isHeadlessSystemUserMode().yesOrNo, true) { infoDialog = 1 }
|
||||
Spacer(Modifier.padding(vertical = 8.dp))
|
||||
if(VERSION.SDK_INT >= 23) InfoItem(R.string.system_user, userManager.isSystemUser.yesOrNo)
|
||||
if(VERSION.SDK_INT >= 34) InfoItem(R.string.admin_user, userManager.isAdminUser.yesOrNo)
|
||||
if(VERSION.SDK_INT >= 25) InfoItem(R.string.demo_user, userManager.isDemoUser.yesOrNo)
|
||||
if(VERSION.SDK_INT >= 26) userManager.getUserCreationTime(user).let {
|
||||
if(it != 0L) InfoItem(R.string.creation_time, parseTimestamp(it))
|
||||
}
|
||||
if (VERSION.SDK_INT >= 24) InfoItem(R.string.support_multiuser, info.multiUser.yesOrNo)
|
||||
if (VERSION.SDK_INT >= 31) InfoItem(R.string.headless_system_user_mode, info.headless.yesOrNo, true) { infoDialog = 1 }
|
||||
Spacer(Modifier.height(8.dp))
|
||||
if (VERSION.SDK_INT >= 23) InfoItem(R.string.system_user, info.system.yesOrNo)
|
||||
if (VERSION.SDK_INT >= 34) InfoItem(R.string.admin_user, info.admin.yesOrNo)
|
||||
if (VERSION.SDK_INT >= 25) InfoItem(R.string.demo_user, info.demo.yesOrNo)
|
||||
if (info.time != 0L) InfoItem(R.string.creation_time, formatTime(info.time))
|
||||
|
||||
if (VERSION.SDK_INT >= 28) {
|
||||
InfoItem(R.string.logout_enabled, Privilege.DPM.isLogoutEnabled.yesOrNo)
|
||||
InfoItem(R.string.ephemeral_user, Privilege.DPM.isEphemeralUser(Privilege.DAR).yesOrNo)
|
||||
InfoItem(R.string.affiliated_user, privilege.affiliated.yesOrNo)
|
||||
InfoItem(R.string.logout_enabled, info.logout.yesOrNo)
|
||||
InfoItem(R.string.ephemeral_user, info.ephemeral.yesOrNo)
|
||||
InfoItem(R.string.affiliated_user, info.affiliated.yesOrNo)
|
||||
}
|
||||
InfoItem(R.string.user_id, (Binder.getCallingUid() / 100000).toString())
|
||||
InfoItem(R.string.user_serial_number, userManager.getSerialNumberForUser(Process.myUserHandle()).toString())
|
||||
InfoItem(R.string.user_serial_number, info.serial.toString())
|
||||
}
|
||||
if(infoDialog != 0) AlertDialog(
|
||||
text = { Text(stringResource(R.string.info_headless_system_user_mode)) },
|
||||
@@ -217,24 +229,15 @@ fun UserInfoScreen(onNavigateUp: () -> Unit) {
|
||||
@Serializable object UserOperation
|
||||
|
||||
@Composable
|
||||
fun UserOperationScreen(onNavigateUp: () -> Unit) {
|
||||
fun UserOperationScreen(
|
||||
startUser: (Int, Boolean) -> Int, switchUser: (Int, Boolean) -> Boolean,
|
||||
stopUser: (Int, Boolean) -> Int, deleteUser: (Int, Boolean) -> Boolean, onNavigateUp: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
var input by remember { mutableStateOf("") }
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var useUserId by remember { mutableStateOf(false) }
|
||||
fun withUserHandle(operation: (UserHandle) -> Unit) {
|
||||
val userHandle = if(useUserId && VERSION.SDK_INT >= 24) {
|
||||
UserHandle.getUserHandleForUid(input.toInt() * 100000)
|
||||
} else {
|
||||
userManager.getUserForSerialNumber(input.toLong())
|
||||
}
|
||||
if(userHandle == null) {
|
||||
context.popToast(R.string.user_not_exist)
|
||||
} else {
|
||||
operation(userHandle)
|
||||
}
|
||||
}
|
||||
var dialog by remember { mutableStateOf(false) }
|
||||
val legalInput = input.toIntOrNull() != null
|
||||
MyScaffold(R.string.user_operation, onNavigateUp) {
|
||||
if(VERSION.SDK_INT >= 24) SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
|
||||
@@ -257,10 +260,7 @@ fun UserOperationScreen(onNavigateUp: () -> Unit) {
|
||||
Button(
|
||||
onClick = {
|
||||
focusMgr.clearFocus()
|
||||
withUserHandle {
|
||||
val result = Privilege.DPM.startUserInBackground(Privilege.DAR, it)
|
||||
context.popToast(userOperationResultCode(result))
|
||||
}
|
||||
context.popToast(startUser(input.toInt(), useUserId))
|
||||
},
|
||||
enabled = legalInput,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
@@ -272,7 +272,7 @@ fun UserOperationScreen(onNavigateUp: () -> Unit) {
|
||||
Button(
|
||||
onClick = {
|
||||
focusMgr.clearFocus()
|
||||
withUserHandle { context.showOperationResultToast(Privilege.DPM.switchUser(Privilege.DAR, it)) }
|
||||
if (switchUser(input.toInt(), useUserId)) context.popToast(R.string.user_not_exist)
|
||||
},
|
||||
enabled = legalInput,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
@@ -284,10 +284,7 @@ fun UserOperationScreen(onNavigateUp: () -> Unit) {
|
||||
Button(
|
||||
onClick = {
|
||||
focusMgr.clearFocus()
|
||||
withUserHandle {
|
||||
val result = Privilege.DPM.stopUser(Privilege.DAR, it)
|
||||
context.popToast(userOperationResultCode(result))
|
||||
}
|
||||
context.popToast(stopUser(input.toInt(), useUserId))
|
||||
},
|
||||
enabled = legalInput,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
@@ -299,14 +296,7 @@ fun UserOperationScreen(onNavigateUp: () -> Unit) {
|
||||
Button(
|
||||
onClick = {
|
||||
focusMgr.clearFocus()
|
||||
withUserHandle {
|
||||
if(Privilege.DPM.removeUser(Privilege.DAR, it)) {
|
||||
context.showOperationResultToast(true)
|
||||
input = ""
|
||||
} else {
|
||||
context.showOperationResultToast(false)
|
||||
}
|
||||
}
|
||||
dialog = true
|
||||
},
|
||||
enabled = legalInput,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
@@ -315,21 +305,39 @@ fun UserOperationScreen(onNavigateUp: () -> Unit) {
|
||||
Text(stringResource(R.string.delete))
|
||||
}
|
||||
}
|
||||
if (dialog) AlertDialog(
|
||||
text = {
|
||||
Text(stringResource(R.string.delete_user_confirmation, input))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton({
|
||||
context.showOperationResultToast(deleteUser(input.toInt(), useUserId))
|
||||
dialog = false
|
||||
}) {
|
||||
Text(stringResource(R.string.confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton({ dialog = false }) { Text(stringResource(R.string.cancel)) }
|
||||
},
|
||||
onDismissRequest = { dialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
data class CreateUserResult(val message: Int, val serial: Long = -1)
|
||||
|
||||
@Serializable object CreateUser
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun CreateUserScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
|
||||
fun CreateUserScreen(
|
||||
createUser: (String, Int, (CreateUserResult) -> Unit) -> Unit, onNavigateUp: () -> Unit
|
||||
) {
|
||||
var result by remember { mutableStateOf<CreateUserResult?>(null) }
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var userName by remember { mutableStateOf("") }
|
||||
var creating by remember { mutableStateOf(false) }
|
||||
var createdUserSerialNumber by remember { mutableLongStateOf(-1) }
|
||||
var flag by remember { mutableIntStateOf(0) }
|
||||
val coroutine = rememberCoroutineScope()
|
||||
var flags by remember { mutableIntStateOf(0) }
|
||||
MyScaffold(R.string.create_user, onNavigateUp, 0.dp) {
|
||||
OutlinedTextField(
|
||||
userName, { userName= it }, Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
|
||||
@@ -340,55 +348,47 @@ fun CreateUserScreen(onNavigateUp: () -> Unit) {
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
FullWidthCheckBoxItem(
|
||||
R.string.create_user_skip_wizard,
|
||||
flag and DevicePolicyManager.SKIP_SETUP_WIZARD != 0
|
||||
) { flag = flag xor DevicePolicyManager.SKIP_SETUP_WIZARD }
|
||||
flags and DevicePolicyManager.SKIP_SETUP_WIZARD != 0
|
||||
) { flags = flags xor DevicePolicyManager.SKIP_SETUP_WIZARD }
|
||||
if(VERSION.SDK_INT >= 28) {
|
||||
FullWidthCheckBoxItem(
|
||||
R.string.create_user_ephemeral_user,
|
||||
flag and DevicePolicyManager.MAKE_USER_EPHEMERAL != 0
|
||||
) { flag = flag xor DevicePolicyManager.MAKE_USER_EPHEMERAL }
|
||||
flags and DevicePolicyManager.MAKE_USER_EPHEMERAL != 0
|
||||
) { flags = flags xor DevicePolicyManager.MAKE_USER_EPHEMERAL }
|
||||
FullWidthCheckBoxItem(
|
||||
R.string.create_user_enable_all_system_app,
|
||||
flag and DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED != 0
|
||||
) { flag = flag xor DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED }
|
||||
flags and DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED != 0
|
||||
) { flags = flags xor DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED }
|
||||
}
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
focusMgr.clearFocus()
|
||||
creating = true
|
||||
coroutine.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val uh = Privilege.DPM.createAndManageUser(Privilege.DAR, userName, Privilege.DAR, null, flag)
|
||||
withContext(Dispatchers.Main) {
|
||||
createdUserSerialNumber = userManager.getSerialNumberForUser(uh)
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
e.printStackTrace()
|
||||
withContext(Dispatchers.Main) {
|
||||
if (VERSION.SDK_INT >= 28 && e is UserManager.UserOperationException) {
|
||||
context.popToast(e.message ?: context.getString(R.string.error))
|
||||
} else {
|
||||
context.showOperationResultToast(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) { creating = false }
|
||||
createUser(userName, flags) {
|
||||
creating = false
|
||||
result = it
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)
|
||||
) {
|
||||
Text(stringResource(R.string.create))
|
||||
}
|
||||
if(createdUserSerialNumber != -1L) AlertDialog(
|
||||
title = { Text(stringResource(R.string.success)) },
|
||||
text = { Text(stringResource(R.string.serial_number_of_new_user_is, createdUserSerialNumber)) },
|
||||
confirmButton = {
|
||||
TextButton({ createdUserSerialNumber = -1 }) { Text(stringResource(R.string.confirm)) }
|
||||
if (result != null) AlertDialog(
|
||||
text = {
|
||||
Column {
|
||||
Text(stringResource(result!!.message))
|
||||
if (result?.serial != -1L) {
|
||||
Text(stringResource(R.string.serial_number) + ": " + result!!.serial)
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismissRequest = { createdUserSerialNumber = -1 }
|
||||
confirmButton = {
|
||||
TextButton({ result = null }) { Text(stringResource(R.string.confirm)) }
|
||||
},
|
||||
onDismissRequest = { result = null }
|
||||
)
|
||||
if(creating) CircularProgressDialog { }
|
||||
if (creating) CircularProgressDialog { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,24 +396,21 @@ fun CreateUserScreen(onNavigateUp: () -> Unit) {
|
||||
|
||||
@RequiresApi(26)
|
||||
@Composable
|
||||
fun AffiliationIdScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
fun AffiliationIdScreen(
|
||||
affiliationIds: StateFlow<List<String>>, getIds: () -> Unit, setId: (String, Boolean) -> Unit,
|
||||
onNavigateUp: () -> Unit
|
||||
) {
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var input by remember { mutableStateOf("") }
|
||||
val list = remember { mutableStateListOf<String>() }
|
||||
val refreshIds = {
|
||||
list.clear()
|
||||
list.addAll(Privilege.DPM.getAffiliationIds(Privilege.DAR))
|
||||
}
|
||||
LaunchedEffect(Unit) { refreshIds() }
|
||||
val list by affiliationIds.collectAsStateWithLifecycle()
|
||||
LaunchedEffect(Unit) { getIds() }
|
||||
MyScaffold(R.string.affiliation_id, onNavigateUp) {
|
||||
Column(modifier = Modifier.animateContentSize()) {
|
||||
if(list.isEmpty()) Text(stringResource(R.string.none))
|
||||
for(i in list) {
|
||||
ListItem(i) { list -= i }
|
||||
if (list.isEmpty()) Text(stringResource(R.string.none))
|
||||
for (i in list) {
|
||||
ListItem(i) { setId(i, false) }
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
OutlinedTextField(
|
||||
value = input,
|
||||
onValueChange = { input = it },
|
||||
@@ -421,7 +418,7 @@ fun AffiliationIdScreen(onNavigateUp: () -> Unit) {
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
list += input
|
||||
setId(input, true)
|
||||
input = ""
|
||||
},
|
||||
enabled = input.isNotEmpty()
|
||||
@@ -429,22 +426,10 @@ fun AffiliationIdScreen(onNavigateUp: () -> Unit) {
|
||||
Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(R.string.add))
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() })
|
||||
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() })
|
||||
)
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
list.removeAll(setOf(""))
|
||||
Privilege.DPM.setAffiliationIds(Privilege.DAR, list.toSet())
|
||||
context.showOperationResultToast(true)
|
||||
refreshIds()
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(R.string.apply))
|
||||
}
|
||||
Notes(R.string.info_affiliation_id)
|
||||
}
|
||||
}
|
||||
@@ -452,7 +437,7 @@ fun AffiliationIdScreen(onNavigateUp: () -> Unit) {
|
||||
@Serializable object ChangeUsername
|
||||
|
||||
@Composable
|
||||
fun ChangeUsernameScreen(onNavigateUp: () -> Unit) {
|
||||
fun ChangeUsernameScreen(setName: (String) -> Unit, onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var inputUsername by remember { mutableStateOf("") }
|
||||
@@ -468,19 +453,13 @@ fun ChangeUsernameScreen(onNavigateUp: () -> Unit) {
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
Privilege.DPM.setProfileName(Privilege.DAR, inputUsername)
|
||||
setName(inputUsername)
|
||||
context.showOperationResultToast(true)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(R.string.apply))
|
||||
}
|
||||
Button(
|
||||
onClick = { Privilege.DPM.setProfileName(Privilege.DAR, null) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(R.string.reset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,16 +467,19 @@ fun ChangeUsernameScreen(onNavigateUp: () -> Unit) {
|
||||
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
fun UserSessionMessageScreen(onNavigateUp: () -> Unit) {
|
||||
fun UserSessionMessageScreen(
|
||||
getMessages: () -> Pair<String, String>, setStartMessage: (String?) -> Unit,
|
||||
setEndMessage: (String?) -> Unit, onNavigateUp: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var start by remember { mutableStateOf("") }
|
||||
var end by remember { mutableStateOf("") }
|
||||
val refreshMsg = {
|
||||
start = Privilege.DPM.getStartUserSessionMessage(Privilege.DAR)?.toString() ?: ""
|
||||
end = Privilege.DPM.getEndUserSessionMessage(Privilege.DAR)?.toString() ?: ""
|
||||
LaunchedEffect(Unit) {
|
||||
val messages = getMessages()
|
||||
start = messages.first
|
||||
end = messages.second
|
||||
}
|
||||
LaunchedEffect(Unit) { refreshMsg() }
|
||||
MyScaffold(R.string.user_session_msg, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = start,
|
||||
@@ -510,8 +492,7 @@ fun UserSessionMessageScreen(onNavigateUp: () -> Unit) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Button(
|
||||
onClick = {
|
||||
Privilege.DPM.setStartUserSessionMessage(Privilege.DAR, start)
|
||||
refreshMsg()
|
||||
setStartMessage(start)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.49F)
|
||||
) {
|
||||
@@ -519,8 +500,7 @@ fun UserSessionMessageScreen(onNavigateUp: () -> Unit) {
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
Privilege.DPM.setStartUserSessionMessage(Privilege.DAR, null)
|
||||
refreshMsg()
|
||||
setStartMessage(null)
|
||||
context.showOperationResultToast(true)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.96F)
|
||||
@@ -540,8 +520,7 @@ fun UserSessionMessageScreen(onNavigateUp: () -> Unit) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Button(
|
||||
onClick = {
|
||||
Privilege.DPM.setEndUserSessionMessage(Privilege.DAR, end)
|
||||
refreshMsg()
|
||||
setStartMessage(end)
|
||||
context.showOperationResultToast(true)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.49F)
|
||||
@@ -550,8 +529,7 @@ fun UserSessionMessageScreen(onNavigateUp: () -> Unit) {
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
Privilege.DPM.setEndUserSessionMessage(Privilege.DAR, null)
|
||||
refreshMsg()
|
||||
setEndMessage(null)
|
||||
context.showOperationResultToast(true)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.96F)
|
||||
@@ -564,8 +542,7 @@ fun UserSessionMessageScreen(onNavigateUp: () -> Unit) {
|
||||
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
private fun ChangeUserIconDialog(bitmap: Bitmap, onClose: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
private fun ChangeUserIconDialog(bitmap: Bitmap, onSet: () -> Unit, onClose: () -> Unit) {
|
||||
AlertDialog(
|
||||
title = { Text(stringResource(R.string.change_user_icon)) },
|
||||
text = {
|
||||
@@ -577,11 +554,7 @@ private fun ChangeUserIconDialog(bitmap: Bitmap, onClose: () -> Unit) {
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton({
|
||||
Privilege.DPM.setUserIcon(Privilege.DAR, bitmap)
|
||||
context.showOperationResultToast(true)
|
||||
onClose()
|
||||
}) {
|
||||
TextButton(onSet) {
|
||||
Text(stringResource(R.string.confirm))
|
||||
}
|
||||
},
|
||||
@@ -593,13 +566,3 @@ private fun ChangeUserIconDialog(bitmap: Bitmap, onClose: () -> Unit) {
|
||||
onDismissRequest = onClose
|
||||
)
|
||||
}
|
||||
|
||||
@StringRes
|
||||
private fun userOperationResultCode(result:Int): Int =
|
||||
when(result) {
|
||||
UserManager.USER_OPERATION_SUCCESS -> R.string.success
|
||||
UserManager.USER_OPERATION_ERROR_UNKNOWN -> R.string.unknown_error
|
||||
UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE-> R.string.fail_managed_profile
|
||||
UserManager.USER_OPERATION_ERROR_CURRENT_USER-> R.string.fail_current_user
|
||||
else -> R.string.unknown
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user