diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9263655..e961458 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -38,6 +38,10 @@ android {
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("defaultSignature")
+ composeCompiler {
+ includeSourceInformation = false
+ includeTraceMarkers = false
+ }
}
debug {
signingConfig = signingConfigs.getByName("defaultSignature")
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt
index 3de39c4..a954ff2 100644
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt
@@ -31,21 +31,30 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.SegmentedButton
+import androidx.compose.material3.SegmentedButtonDefaults
+import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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.Alignment
import androidx.compose.ui.Modifier
@@ -53,10 +62,13 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.parseTimestamp
@@ -70,6 +82,9 @@ import com.bintianqi.owndroid.ui.MyScaffold
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
@Composable
fun Users(navCtrl: NavHostController) {
@@ -202,14 +217,14 @@ fun UserOperation(navCtrl: NavHostController) {
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
val dpm = context.getDPM()
val receiver = context.getReceiver()
- var idInput by remember { mutableStateOf("") }
- var useUid by remember { mutableStateOf(false) }
+ var input by remember { mutableStateOf("") }
val focusMgr = LocalFocusManager.current
+ var useUserId by remember { mutableStateOf(false) }
fun withUserHandle(operation: (UserHandle) -> Unit) {
- val userHandle = if(useUid && VERSION.SDK_INT >= 24) {
- UserHandle.getUserHandleForUid(idInput.toInt())
+ val userHandle = if(useUserId && VERSION.SDK_INT >= 24) {
+ UserHandle.getUserHandleForUid(input.toInt() * 100000)
} else {
- userManager.getUserForSerialNumber(idInput.toLong())
+ userManager.getUserForSerialNumber(input.toLong())
}
if(userHandle == null) {
Toast.makeText(context, R.string.user_not_exist, Toast.LENGTH_SHORT).show()
@@ -217,27 +232,24 @@ fun UserOperation(navCtrl: NavHostController) {
operation(userHandle)
}
}
- val legalInput = try {
- idInput.toInt()
- true
- } catch(_: Exception) {
- false
- }
+ val legalInput = input.toIntOrNull() != null
MyScaffold(R.string.user_operation, 8.dp, navCtrl) {
+ if(VERSION.SDK_INT >= 24) SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
+ SegmentedButton(!useUserId, { useUserId = false }, SegmentedButtonDefaults.itemShape(0, 2)) {
+ Text(stringResource(R.string.serial_number))
+ }
+ SegmentedButton(useUserId, { useUserId = true }, SegmentedButtonDefaults.itemShape(1, 2)) {
+ Text(stringResource(R.string.user_id))
+ }
+ }
OutlinedTextField(
- value = idInput,
- onValueChange = {
- idInput = it
- },
- label = { Text(if(useUid) "UID" else stringResource(R.string.serial_number)) },
- modifier = Modifier.fillMaxWidth(),
+ value = input,
+ onValueChange = { input = it },
+ label = { Text(stringResource(if(useUserId) R.string.user_id else R.string.serial_number)) },
+ modifier = Modifier.fillMaxWidth().padding(top = 4.dp, bottom = 8.dp),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() })
)
- Spacer(Modifier.padding(vertical = 3.dp))
- if(VERSION.SDK_INT >= 24) {
- CheckBoxItem(text = R.string.use_uid, checked = useUid, operation = { idInput=""; useUid = it })
- }
if(VERSION.SDK_INT >= 28) {
Button(
onClick = {
@@ -250,6 +262,7 @@ fun UserOperation(navCtrl: NavHostController) {
enabled = legalInput,
modifier = Modifier.fillMaxWidth()
) {
+ Icon(Icons.Default.PlayArrow, null, Modifier.padding(end = 4.dp))
Text(stringResource(R.string.start_in_background))
}
}
@@ -261,6 +274,7 @@ fun UserOperation(navCtrl: NavHostController) {
enabled = legalInput,
modifier = Modifier.fillMaxWidth()
) {
+ Icon(painterResource(R.drawable.sync_alt_fill0), null, Modifier.padding(end = 4.dp))
Text(stringResource(R.string.user_operation_switch))
}
if(VERSION.SDK_INT >= 28) {
@@ -275,6 +289,7 @@ fun UserOperation(navCtrl: NavHostController) {
enabled = legalInput,
modifier = Modifier.fillMaxWidth()
) {
+ Icon(Icons.Default.Close, null, Modifier.padding(end = 4.dp))
Text(stringResource(R.string.stop))
}
}
@@ -284,7 +299,7 @@ fun UserOperation(navCtrl: NavHostController) {
withUserHandle {
if(dpm.removeUser(receiver, it)) {
context.showOperationResultToast(true)
- idInput = ""
+ input = ""
} else {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
@@ -293,9 +308,9 @@ fun UserOperation(navCtrl: NavHostController) {
enabled = legalInput,
modifier = Modifier.fillMaxWidth()
) {
+ Icon(Icons.Default.Delete, null, Modifier.padding(end = 4.dp))
Text(stringResource(R.string.delete))
}
- InfoCard(R.string.info_user_operation)
}
}
@@ -308,7 +323,10 @@ fun CreateUser(navCtrl: NavHostController) {
val receiver = context.getReceiver()
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()
MyScaffold(R.string.create_user, 8.dp, navCtrl) {
OutlinedTextField(
value = userName,
@@ -333,20 +351,39 @@ fun CreateUser(navCtrl: NavHostController) {
flag and DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED != 0
) { flag = flag xor DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED }
}
- var newUserHandle: UserHandle? by remember { mutableStateOf(null) }
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
focusMgr.clearFocus()
- newUserHandle = dpm.createAndManageUser(receiver, userName, receiver, null, flag)
- context.showOperationResultToast(newUserHandle != null)
+ creating = true
+ coroutine.launch(Dispatchers.IO) {
+ println(Thread.currentThread().name)
+ try {
+ val uh = dpm.createAndManageUser(receiver, userName, receiver, null, flag)
+ withContext(Dispatchers.Main) {
+ createdUserSerialNumber = userManager.getSerialNumberForUser(uh)
+ }
+ } catch(_: Exception) {
+ context.showOperationResultToast(false)
+ }
+ withContext(Dispatchers.Main) { creating = false }
+ }
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.create))
}
- Spacer(Modifier.padding(vertical = 5.dp))
- if(newUserHandle != null) { Text(text = stringResource(R.string.serial_number_of_new_user_is, userManager.getSerialNumberForUser(newUserHandle))) }
+ 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)) }
+ },
+ onDismissRequest = { createdUserSerialNumber = -1 }
+ )
+ if(creating) Dialog({}, DialogProperties(false, false)) {
+ CircularProgressIndicator()
+ }
}
}
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index f7b7f91..1e1182d 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -508,14 +508,12 @@
Выход из системы разрешен
Временный пользователь
Аффилированный пользователь
- Идентификатор пользователя
Серийный номер пользователя
Операция с пользователем
Пользователь не существует
Серийный номер
Вторичные пользователи
- Нет вторичных пользователей
- Использовать UID
+ Нет вторичных пользователей
Выйти из текущего пользователя
Запустить в фоновом режиме
Переключиться
@@ -524,7 +522,7 @@
Пропустить мастер настройки
Временный пользователь
Включить все системные приложения
- Серийный номер этого пользователя: %1$s
+ Серийный номер этого пользователя: %1$d
Аффилированный идентификатор
Изменить значок пользователя
Использовать выборщик файлов вместо галереи
@@ -702,7 +700,6 @@
Пользователь не сможет очищать данные приложений или принудительно останавливать пакеты.
Установить список приложений, которые нужно сохранить в виде APK-файлов, даже если ни у одного пользователя в данный момент они не установлены.
Режим "безголового" системного пользователя означает, что системный пользователь запускает системные службы и некоторый системный интерфейс, но он не связан с каким-либо реальным человеком, и для связи с реальными людьми должны быть созданы дополнительные пользователи.
- Рекомендуется указать пользователя по серийному номеру, также можно использовать UID, UID должен принадлежать любому из приложений целевого пользователя.
Когда владелец устройства создает управляемого пользователя, управляемый пользователь не является аффилированным. Чтобы сделать управляемого пользователя аффилированным с владельцем устройства, вам следует установить одинаковые аффилированные идентификаторы в основном и управляемом пользователях.
Установить новый пароль блокировки экрана. Длина этого пароля должна быть не менее 4 цифр. Оставьте поле пустым, чтобы удалить пароль.\nЕсли вы установите цифровой пароль длиной 6 символов или меньше, он будет установлен как PIN-код.
Установить максимальное время бездействия пользователя, по истечении которого устройство будет заблокировано. Это ограничивает время, которое может установить пользователь.\nЗначение 0 означает отсутствие ограничений.
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 1c026db..ea1dcf1 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -512,14 +512,12 @@
Logout enabled
Geçici kullanıcı
Bağlı kullanıcı
- Kullanıcı ID
Kullanıcı seri numarası
Secondary users
No secondary users
Kullanıcı işlemi
User does not exist
Seri numarası
- UID kullan
Mevcut kullanıcıyı çıkış yap
Arka planda başlat
Değiştir
@@ -528,7 +526,7 @@
Sihirbazı atla
Geçici kullanıcı
Tüm sistem uygulamalarını etkinleştir
- Bu kullanıcının seri numarası: %1$s
+ Bu kullanıcının seri numarası: %1$d
Bağlılık ID
Kullanıcı simgesini değiştir
Galeri yerine dosya seçici kullan
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 06c5014..3dbc933 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -499,14 +499,12 @@
用户可登出
临时用户
附属用户
- 当前UserID
当前用户序列号
次要用户
无次要用户
用户操作
用户不存在
序列号
- 使用UID
登出当前用户
在后台启动
切换
@@ -515,7 +513,7 @@
跳过创建用户向导
临时用户
启用所有系统应用
- 新用户的序列号:%1$s
+ 新用户的序列号:%1$d
附属用户ID
更换用户头像
使用文件选择器而不是相册
@@ -689,7 +687,6 @@
用户无法清除这些应用的存储空间,也无法强制停止应用
这个列表中的应用的APK将会一直保留,即使没有任何用户安装这个应用
无头系统用户模式意味着系统用户运行系统服务和一些系统UI,但它不与任何真实的人相关联,必须创建额外的用户才能与真实的人相关联。
- 推荐使用用户序列号来标识用户,如果要使用UID,UID可以是运行在目标用户中任意应用的UID
当Device owner创建并管理用户时,新的用户不是附属用户。Device owner设置和受管理用户完全相同的附属用户ID后,受管理用户成为附属于Device owner的用户
设置一个新的密码,密码的长度需要4位或以上,不输入密码将会清除现有的密码。长度在6位或以下的纯数字密码将会设置为PIN码。
设置设备锁定前用户活动的最大时间。这限制了用户可以设置的时间长度。\n值为0表示不做限制。
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 115c9d6..5b99103 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -537,14 +537,13 @@
Logout enabled
Ephemeral user
Affiliated user
- UserID
+ User ID
User serial number
Secondary users
No secondary users
User operation
User does not exist
Serial number
- Use UID
Logout current user
Start in background
Switch
@@ -553,7 +552,7 @@
Skip wizard
Ephemeral user
Enable all system app
- Serial number of this user: %1$s
+ Serial number of this user: %1$d
Affiliation ID
Change user icon
Use file picker instead of gallery
@@ -727,7 +726,6 @@
User will not be able to clear app data or force-stop packages.
Set a list of apps to keep around as APKs even if no user has currently installed it.
Headless system user mode means the system user runs system services and some system UI, but it is not associated with any real person and additional users must be created to be associated with real persons.
- It is recommended to specify a user with serial number, you can also use UID, the UID should be any of the apps in the target user.
When Device owner create a managed user, the managed user isn\'t affiliated. In order to make the managed user affiliated with the Device owner, you should set same affiliated IDs in main user and managed user
Set a new lockscreen password. The length of this password must be at least 4 digits. Keep it empty to remove password.\nIf you set a numeric password that length is 6 or lower, it will set as PIN
Set the maximum time for user activity until the device will lock. This limits the length that the user can set.\nA value of 0 means there is no restriction.