Merge branch 'dev'

This commit is contained in:
BinTianqi
2026-02-04 12:57:27 +08:00
35 changed files with 1389 additions and 403 deletions

View File

@@ -72,12 +72,6 @@ jobs:
with: with:
path: artifacts path: artifacts
- name: Download telegram-bot-api
run: |
mkdir ./binaries
wget "https://github.com/jakbin/telegram-bot-api-binary/releases/download/latest/telegram-bot-api" -O ./binaries/telegram-bot-api
chmod +x ./binaries/telegram-bot-api
- name: Start API Server & Upload - name: Start API Server & Upload
env: env:
COMMIT_MESSAGE: |+ COMMIT_MESSAGE: |+
@@ -91,8 +85,7 @@ jobs:
mv ./$RELEASE_TEST_PWD/app-release.apk ./$RELEASE_TEST_PWD.apk && rm -rf ./$RELEASE_TEST_PWD mv ./$RELEASE_TEST_PWD/app-release.apk ./$RELEASE_TEST_PWD.apk && rm -rf ./$RELEASE_TEST_PWD
export RELEASE_SIGNED_PWD=$(find . -name "*release-signed*") export RELEASE_SIGNED_PWD=$(find . -name "*release-signed*")
mv ./$RELEASE_SIGNED_PWD/app-release.apk ./$RELEASE_SIGNED_PWD.apk && rm -rf ./$RELEASE_SIGNED_PWD mv ./$RELEASE_SIGNED_PWD/app-release.apk ./$RELEASE_SIGNED_PWD.apk && rm -rf ./$RELEASE_SIGNED_PWD
../binaries/telegram-bot-api --api-id=${{ secrets.TELEGRAM_API_APP_ID }} --api-hash=${{ secrets.TELEGRAM_API_HASH }} --local 2>&1 > /dev/null &
export token=${{ secrets.TELEGRAM_BOT_KEY }} export token=${{ secrets.TELEGRAM_BOT_KEY }}
curl -v "http://127.0.0.1:8081/bot$token/sendMediaGroup?chat_id=-1002203528169&media=%5B%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseTest%22%7D%2C%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseSigned%22%2C%22parse_mode%22%3A%22HTML%22%2C%22caption%22%3A${ESCAPED}%7D%5D" \ curl -v "http://api.telegram.org/bot$token/sendMediaGroup?chat_id=-1002203528169&media=%5B%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseTest%22%7D%2C%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseSigned%22%2C%22parse_mode%22%3A%22HTML%22%2C%22caption%22%3A${ESCAPED}%7D%5D" \
-F releaseTest="@$RELEASE_TEST_PWD.apk" \ -F releaseTest="@$RELEASE_TEST_PWD.apk" \
-F releaseSigned="@$RELEASE_SIGNED_PWD.apk" -F releaseSigned="@$RELEASE_SIGNED_PWD.apk"

View File

@@ -57,7 +57,7 @@ java.lang.IllegalStateException: Not allowed to set the device owner because the
> [!NOTE] > [!NOTE]
> 一些系统有应用克隆、儿童空间等功能,它们通常是用户。 > 一些系统有应用克隆、儿童空间等功能,它们通常是用户。
#### Device owner 已存在 ### Device owner 已存在
```text ```text
java.lang.IllegalStateException: Trying to set the device owner (com.bintianqi.owndroid/.Receiver), but device owner (xxx) is already set. java.lang.IllegalStateException: Trying to set the device owner (com.bintianqi.owndroid/.Receiver), but device owner (xxx) is already set.
@@ -142,7 +142,7 @@ context.sendBroadcast(intent)
[License.md](LICENSE.md) [License.md](LICENSE.md)
> Copyright (C) 2024 BinTianqi > Copyright (C) 2026 BinTianqi
> >
> 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, either version 3 of the License, or (at your option) any later version. > 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, either version 3 of the License, or (at your option) any later version.
> >

View File

@@ -57,7 +57,7 @@ Solutions:
> [!NOTE] > [!NOTE]
> Some systems have features such as app cloning and children space, which are usually users. > Some systems have features such as app cloning and children space, which are usually users.
#### Device owner is already set ### Device owner is already set
```text ```text
java.lang.IllegalStateException: Trying to set the device owner (com.bintianqi.owndroid/.Receiver), but device owner (xxx) is already set. java.lang.IllegalStateException: Trying to set the device owner (com.bintianqi.owndroid/.Receiver), but device owner (xxx) is already set.
@@ -144,7 +144,7 @@ You can use Gradle in command line to build OwnDroid.
[License.md](LICENSE.md) [License.md](LICENSE.md)
> Copyright (C) 2024 BinTianqi > Copyright (C) 2026 BinTianqi
> >
> 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, either version 3 of the License, or (at your option) any later version. > 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, either version 3 of the License, or (at your option) any later version.
> >

View File

@@ -24,10 +24,10 @@ android {
defaultConfig { defaultConfig {
applicationId = "com.bintianqi.owndroid" applicationId = "com.bintianqi.owndroid"
minSdk = 21 minSdk = 23
targetSdk = 36 targetSdk = 36
versionCode = 41 versionCode = 42
versionName = "7.2" versionName = "7.3"
multiDexEnabled = false multiDexEnabled = false
} }
@@ -103,6 +103,7 @@ dependencies {
implementation(libs.androidx.fragment) implementation(libs.androidx.fragment)
implementation(libs.hiddenApiBypass) implementation(libs.hiddenApiBypass)
implementation(libs.libsu) implementation(libs.libsu)
implementation(libs.reoderable)
implementation(libs.serialization) implementation(libs.serialization)
implementation(kotlin("reflect")) implementation(kotlin("reflect"))
} }

View File

@@ -12,6 +12,8 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC"/> <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
<uses-sdk tools:overrideLibrary="rikka.shizuku.provider,rikka.shizuku.api,rikka.shizuku.shared,rikka.shizuku.aidl,com.rosan.dhizuku.server_api,com.rosan.dhizuku.api"/> <uses-sdk tools:overrideLibrary="rikka.shizuku.provider,rikka.shizuku.api,rikka.shizuku.shared,rikka.shizuku.aidl,com.rosan.dhizuku.server_api,com.rosan.dhizuku.api"/>
<uses-feature android:name="android.software.device_admin"/> <uses-feature android:name="android.software.device_admin"/>
<application <application
@@ -81,7 +83,7 @@
android:name=".Receiver" android:name=".Receiver"
android:description="@string/app_name" android:description="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN" android:permission="android.permission.BIND_DEVICE_ADMIN"
android:exported="true"> android:exported="false">
<meta-data <meta-data
android:name="android.app.device_admin" android:name="android.app.device_admin"
android:resource="@xml/device_admin"/> android:resource="@xml/device_admin"/>
@@ -99,6 +101,12 @@
android:name=".ApiReceiver" android:name=".ApiReceiver"
android:exported="true"> android:exported="true">
</receiver> </receiver>
<service
android:name=".LockTaskService"
android:exported="false"
android:foregroundServiceType="specialUse" />
<provider <provider
android:name="rikka.shizuku.ShizukuProvider" android:name="rikka.shizuku.ShizukuProvider"
android:authorities="${applicationId}.shizuku" android:authorities="${applicationId}.shizuku"

View File

@@ -16,6 +16,7 @@ class AppInstallerActivity:FragmentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val vm by viewModels<AppInstallerViewModel>() val vm by viewModels<AppInstallerViewModel>()
vm.initialize(intent) vm.initialize(intent)
vm.registerInstallerReceiver(this)
val theme = ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme) val theme = ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme)
setContent { setContent {
OwnDroidTheme(theme) { OwnDroidTheme(theme) {

View File

@@ -36,13 +36,19 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let { list += it } intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let { list += it }
intent.getParcelableArrayExtra(Intent.EXTRA_STREAM)?.forEach { list += it as Uri } intent.getParcelableArrayExtra(Intent.EXTRA_STREAM)?.forEach { list += it as Uri }
intent.clipData?.let { clipData -> intent.clipData?.let { clipData ->
for(i in 0..clipData.itemCount - 1) { for(i in 0..<clipData.itemCount) {
list += clipData.getItemAt(i).uri list += clipData.getItemAt(i).uri
} }
} }
uiState.update { it.copy(it.packages + list.distinct()) } uiState.update { it.copy(it.packages + list.distinct()) }
} }
fun registerInstallerReceiver(context: Context) {
ContextCompat.registerReceiver(
context, Receiver(), IntentFilter(ACTION), ContextCompat.RECEIVER_NOT_EXPORTED
)
}
fun onPackagesAdd(packages: List<Uri>) { fun onPackagesAdd(packages: List<Uri>) {
uiState.update { uiState.update {
it.copy(packages = it.packages.plus(packages).distinct()) it.copy(packages = it.packages.plus(packages).distinct())
@@ -93,17 +99,14 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
uiState.update { it.copy(installing = false, packageWriting = -1) } uiState.update { it.copy(installing = false, packageWriting = -1) }
return return
} }
ContextCompat.registerReceiver( val intent = Intent(ACTION).setPackage(application.packageName)
application, Receiver(), IntentFilter(ACTION), null,
null, ContextCompat.RECEIVER_EXPORTED
)
val pi = if(Build.VERSION.SDK_INT >= 34) { val pi = if(Build.VERSION.SDK_INT >= 34) {
PendingIntent.getBroadcast( PendingIntent.getBroadcast(
application, sessionId, Intent(ACTION), application, sessionId, intent,
PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE
).intentSender ).intentSender
} else { } else {
PendingIntent.getBroadcast(application, sessionId, Intent(ACTION), PendingIntent.FLAG_MUTABLE).intentSender PendingIntent.getBroadcast(application, sessionId, intent, PendingIntent.FLAG_MUTABLE).intentSender
} }
session.commit(pi) session.commit(pi)
} }
@@ -119,7 +122,6 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
) )
} else { } else {
uiState.update { it.copy(result = intent) } uiState.update { it.copy(result = intent) }
context.unregisterReceiver(this)
} }
} }
} }

View File

@@ -3,6 +3,7 @@ package com.bintianqi.owndroid
import android.content.Context import android.content.Context
import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
import android.hardware.fingerprint.FingerprintManager
import android.os.Build import android.os.Build
import android.os.CancellationSignal import android.os.CancellationSignal
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
@@ -19,6 +20,7 @@ import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -39,6 +41,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
@@ -50,6 +54,7 @@ fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismi
val fr = remember { FocusRequester() } val fr = remember { FocusRequester() }
var input by rememberSaveable { mutableStateOf("") } var input by rememberSaveable { mutableStateOf("") }
var isError by rememberSaveable { mutableStateOf(false) } var isError by rememberSaveable { mutableStateOf(false) }
var showPassword by remember { mutableStateOf(false) }
fun unlock() { fun unlock() {
if(input.hash() == SP.lockPasswordHash) { if(input.hash() == SP.lockPasswordHash) {
fm.clearFocus() fm.clearFocus()
@@ -75,7 +80,18 @@ fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismi
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password, imeAction = if(input.length >= 4) ImeAction.Go else ImeAction.Done keyboardType = KeyboardType.Password, imeAction = if(input.length >= 4) ImeAction.Go else ImeAction.Done
), ),
keyboardActions = KeyboardActions({ fm.clearFocus() }, { unlock() }) keyboardActions = KeyboardActions({ fm.clearFocus() }, { unlock() }),
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { showPassword = !showPassword }) {
Icon(
painter = painterResource(
id = if (showPassword) R.drawable.visibility_fill0 else R.drawable.visibility_off_fill0
),
contentDescription = if (showPassword) "Hide password" else "Show password"
)
}
}
) )
if(Build.VERSION.SDK_INT >= 28 && SP.biometricsUnlock) { if(Build.VERSION.SDK_INT >= 28 && SP.biometricsUnlock) {
FilledTonalIconButton({ startBiometricsUnlock(context, onSucceed) }, Modifier.padding(start = 4.dp)) { FilledTonalIconButton({ startBiometricsUnlock(context, onSucceed) }, Modifier.padding(start = 4.dp)) {
@@ -83,7 +99,7 @@ fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismi
} }
} }
} }
Button(::unlock, Modifier.align(Alignment.End).padding(top = 8.dp), input.length >= 4) { Button(::unlock, Modifier.align(Alignment.End).padding(top = 8.dp)) {
Text(stringResource(R.string.unlock)) Text(stringResource(R.string.unlock))
} }
} }
@@ -92,6 +108,7 @@ fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismi
@RequiresApi(28) @RequiresApi(28)
fun startBiometricsUnlock(context: Context, onSucceed: () -> Unit) { fun startBiometricsUnlock(context: Context, onSucceed: () -> Unit) {
context.getSystemService(FingerprintManager::class.java) ?: return
val callback = object : AuthenticationCallback() { val callback = object : AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
super.onAuthenticationSucceeded(result) super.onAuthenticationSucceeded(result)

View File

@@ -0,0 +1,84 @@
package com.bintianqi.owndroid
import android.app.ActivityManager
import android.app.PendingIntent
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@RequiresApi(28)
class LockTaskService: Service() {
val coroutineScope = CoroutineScope(Dispatchers.IO)
override fun onBind(intent: Intent?): IBinder? = null
val stopReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
coroutineScope.cancel()
stopLockTask()
stop()
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val filter = IntentFilter(STOP_ACTION)
ContextCompat.registerReceiver(
this, stopReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED
)
val pendingIntent = PendingIntent.getBroadcast(
this, 0, Intent(STOP_ACTION).setPackage(this.packageName), PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, MyNotificationChannel.LockTaskMode.id)
.setContentTitle(getText(R.string.lock_task_mode))
.setSmallIcon(R.drawable.lock_fill0)
.addAction(NotificationCompat.Action.Builder(null, getString(R.string.stop), pendingIntent).build())
.setOngoing(true)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.build()
ServiceCompat.startForeground(
this, NotificationType.LockTaskMode.id, notification,
if (Build.VERSION.SDK_INT < 34) 0 else ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
)
coroutineScope.launch {
val am = getSystemService(ActivityManager::class.java)
delay(3000)
while (am.lockTaskModeState == ActivityManager.LOCK_TASK_MODE_LOCKED) {
delay(1000)
}
stop()
}
return START_NOT_STICKY
}
fun stop() {
unregisterReceiver(stopReceiver)
stopSelf()
}
fun stopLockTask() {
val features = Privilege.DPM.getLockTaskFeatures(Privilege.DAR)
val packages = Privilege.DPM.getLockTaskPackages(Privilege.DAR)
Privilege.DPM.setLockTaskPackages(Privilege.DAR, arrayOf())
Privilege.DPM.setLockTaskPackages(Privilege.DAR, packages)
Privilege.DPM.setLockTaskFeatures(Privilege.DAR, features)
}
companion object {
const val STOP_ACTION = "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE"
}
}

View File

@@ -137,6 +137,8 @@ import com.bintianqi.owndroid.dpm.LockTaskMode
import com.bintianqi.owndroid.dpm.LockTaskModeScreen import com.bintianqi.owndroid.dpm.LockTaskModeScreen
import com.bintianqi.owndroid.dpm.ManageAppGroups import com.bintianqi.owndroid.dpm.ManageAppGroups
import com.bintianqi.owndroid.dpm.ManageAppGroupsScreen import com.bintianqi.owndroid.dpm.ManageAppGroupsScreen
import com.bintianqi.owndroid.dpm.ManagedConfiguration
import com.bintianqi.owndroid.dpm.ManagedConfigurationScreen
import com.bintianqi.owndroid.dpm.MtePolicy import com.bintianqi.owndroid.dpm.MtePolicy
import com.bintianqi.owndroid.dpm.MtePolicyScreen import com.bintianqi.owndroid.dpm.MtePolicyScreen
import com.bintianqi.owndroid.dpm.NearbyStreamingPolicy import com.bintianqi.owndroid.dpm.NearbyStreamingPolicy
@@ -155,7 +157,6 @@ import com.bintianqi.owndroid.dpm.OrganizationOwnedProfileScreen
import com.bintianqi.owndroid.dpm.OverrideApn import com.bintianqi.owndroid.dpm.OverrideApn
import com.bintianqi.owndroid.dpm.OverrideApnScreen import com.bintianqi.owndroid.dpm.OverrideApnScreen
import com.bintianqi.owndroid.dpm.PackageFunctionScreen import com.bintianqi.owndroid.dpm.PackageFunctionScreen
import com.bintianqi.owndroid.dpm.PackageFunctionScreenWithoutResult
import com.bintianqi.owndroid.dpm.Password import com.bintianqi.owndroid.dpm.Password
import com.bintianqi.owndroid.dpm.PasswordInfo import com.bintianqi.owndroid.dpm.PasswordInfo
import com.bintianqi.owndroid.dpm.PasswordInfoScreen import com.bintianqi.owndroid.dpm.PasswordInfoScreen
@@ -253,6 +254,9 @@ class MainActivity : FragmentActivity() {
val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {} val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
launcher.launch(Manifest.permission.POST_NOTIFICATIONS) launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
} }
registerPackageRemovedReceiver(this) {
vm.onPackageRemoved(it)
}
setContent { setContent {
var appLockDialog by rememberSaveable { mutableStateOf(false) } var appLockDialog by rememberSaveable { mutableStateOf(false) }
val theme by vm.theme.collectAsStateWithLifecycle() val theme by vm.theme.collectAsStateWithLifecycle()
@@ -265,10 +269,6 @@ class MainActivity : FragmentActivity() {
} }
} }
override fun onResume() {
super.onResume()
}
} }
@ExperimentalMaterial3Api @ExperimentalMaterial3Api
@@ -285,7 +285,10 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
} }
} }
fun choosePackage() { fun choosePackage() {
navController.navigate(ApplicationsList(false)) navController.navigate(ApplicationsList(false, true))
}
fun chooseSinglePackage() {
navController.navigate(ApplicationsList(false, false))
} }
fun navigateToAppGroups() { fun navigateToAppGroups() {
navController.navigate(ManageAppGroups) navController.navigate(ManageAppGroups)
@@ -331,7 +334,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
DelegatedAdminsScreen(vm.delegatedAdmins, vm::getDelegatedAdmins, ::navigateUp, ::navigate) DelegatedAdminsScreen(vm.delegatedAdmins, vm::getDelegatedAdmins, ::navigateUp, ::navigate)
} }
composable<AddDelegatedAdmin>{ composable<AddDelegatedAdmin>{
AddDelegatedAdminScreen(vm.chosenPackage, ::choosePackage, it.toRoute(), AddDelegatedAdminScreen(vm.chosenPackage, ::chooseSinglePackage, it.toRoute(),
vm::setDelegatedAdmin, ::navigateUp) vm::setDelegatedAdmin, ::navigateUp)
} }
composable<DeviceInfo> { DeviceInfoScreen(vm, ::navigateUp) } composable<DeviceInfo> { DeviceInfoScreen(vm, ::navigateUp) }
@@ -385,9 +388,11 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
vm::getNsNotificationPolicy, vm::setNsNotificationPolicy, ::navigateUp) vm::getNsNotificationPolicy, vm::setNsNotificationPolicy, ::navigateUp)
} }
composable<LockTaskMode> { composable<LockTaskMode> {
LockTaskModeScreen(vm.chosenPackage, ::choosePackage, vm.lockTaskPackages, LockTaskModeScreen(
vm.chosenPackage, ::chooseSinglePackage, ::choosePackage, vm.lockTaskPackages,
vm::getLockTaskPackages, vm::setLockTaskPackage, vm::startLockTaskMode, vm::getLockTaskPackages, vm::setLockTaskPackage, vm::startLockTaskMode,
vm:: getLockTaskFeatures, vm::setLockTaskFeatures, ::navigateUp) vm:: getLockTaskFeatures, vm::setLockTaskFeatures, ::navigateUp
)
} }
composable<CaCert> { composable<CaCert> {
CaCertScreen(vm.installedCaCerts, vm::getCaCerts, vm.selectedCaCert, vm::selectCaCert, vm::installCaCert, vm::parseCaCert, CaCertScreen(vm.installedCaCerts, vm::getCaCerts, vm.selectedCaCert, vm::selectCaCert, vm::installCaCert, vm::parseCaCert,
@@ -435,7 +440,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
WifiSsidPolicyScreen(vm::getSsidPolicy, vm::setSsidPolicy, ::navigateUp) WifiSsidPolicyScreen(vm::getSsidPolicy, vm::setSsidPolicy, ::navigateUp)
} }
composable<QueryNetworkStats> { composable<QueryNetworkStats> {
NetworkStatsScreen(vm.chosenPackage, ::choosePackage, vm::getPackageUid, NetworkStatsScreen(vm.chosenPackage, ::chooseSinglePackage, vm::getPackageUid,
vm::queryNetworkStats, ::navigateUp) { navController.navigate(NetworkStatsViewer) } vm::queryNetworkStats, ::navigateUp) { navController.navigate(NetworkStatsViewer) }
} }
composable<NetworkStatsViewer> { composable<NetworkStatsViewer> {
@@ -446,7 +451,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
} }
composable<AlwaysOnVpnPackage> { composable<AlwaysOnVpnPackage> {
AlwaysOnVpnPackageScreen(vm::getAlwaysOnVpnPackage, vm::getAlwaysOnVpnLockdown, AlwaysOnVpnPackageScreen(vm::getAlwaysOnVpnPackage, vm::getAlwaysOnVpnLockdown,
vm::setAlwaysOnVpn, vm.chosenPackage, ::choosePackage, ::navigateUp) vm::setAlwaysOnVpn, vm.chosenPackage, ::chooseSinglePackage, ::navigateUp)
} }
composable<RecommendedGlobalProxy> { composable<RecommendedGlobalProxy> {
RecommendedGlobalProxyScreen(vm::setRecommendedGlobalProxy, ::navigateUp) RecommendedGlobalProxyScreen(vm::setRecommendedGlobalProxy, ::navigateUp)
@@ -494,10 +499,10 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
composable<DeleteWorkProfile> { DeleteWorkProfileScreen(vm::wipeData, ::navigateUp) } composable<DeleteWorkProfile> { DeleteWorkProfileScreen(vm::wipeData, ::navigateUp) }
composable<ApplicationsList> { composable<ApplicationsList> {
val canSwitchView = (it.toRoute() as ApplicationsList).canSwitchView val params = it.toRoute<ApplicationsList>()
AppChooserScreen( AppChooserScreen(
canSwitchView, vm.installedPackages, vm.refreshPackagesProgress, { name -> params, vm.installedPackages, vm.refreshPackagesProgress, { name ->
if (canSwitchView) { if (params.canSwitchView) {
if (name == null) { if (name == null) {
navigateUp() navigateUp()
} else { } else {
@@ -512,12 +517,12 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
navController.navigate(ApplicationsFeatures) { navController.navigate(ApplicationsFeatures) {
popUpTo(Home) popUpTo(Home)
} }
}, vm::refreshPackageList) }, vm::refreshPackageList, vm::setPackageSuspended, vm::setPackageHidden)
} }
composable<ApplicationsFeatures> { composable<ApplicationsFeatures> {
ApplicationsFeaturesScreen(::navigateUp, ::navigate) { ApplicationsFeaturesScreen(::navigateUp, ::navigate) {
SP.applicationsListView = true SP.applicationsListView = true
navController.navigate(ApplicationsList(true)) { navController.navigate(ApplicationsList(true, true)) {
popUpTo(Home) popUpTo(Home)
} }
} }
@@ -526,52 +531,72 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
ApplicationDetailsScreen(it.toRoute(), vm, ::navigateUp, ::navigate) ApplicationDetailsScreen(it.toRoute(), vm, ::navigateUp, ::navigate)
} }
composable<Suspend> { composable<Suspend> {
PackageFunctionScreen(R.string.suspend, vm.suspendedPackages, vm::getSuspendedPackaged, PackageFunctionScreen(
R.string.suspend, vm.suspendedPackages, vm::getSuspendedPackaged,
vm::setPackageSuspended, ::navigateUp, vm.chosenPackage, ::choosePackage, vm::setPackageSuspended, ::navigateUp, vm.chosenPackage, ::choosePackage,
::navigateToAppGroups, vm.appGroups, R.string.info_suspend_app) ::navigateToAppGroups, vm.appGroups, R.string.info_suspend_app
)
} }
composable<Hide> { composable<Hide> {
PackageFunctionScreen(R.string.hide, vm.hiddenPackages, vm::getHiddenPackages, PackageFunctionScreen(
vm::setPackageHidden, ::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups) R.string.hide, vm.hiddenPackages, vm::getHiddenPackages, vm::setPackageHidden,
::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups
)
} }
composable<BlockUninstall> { composable<BlockUninstall> {
PackageFunctionScreenWithoutResult(R.string.block_uninstall, vm.ubPackages, PackageFunctionScreen(
vm::getUbPackages, vm::setPackageUb, ::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups) R.string.block_uninstall, vm.ubPackages, vm::getUbPackages, vm::setPackageUb,
::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups
)
} }
composable<DisableUserControl> { composable<DisableUserControl> {
PackageFunctionScreenWithoutResult(R.string.disable_user_control, vm.ucdPackages, PackageFunctionScreen(
vm::getUcdPackages, vm::setPackageUcd, ::navigateUp, vm.chosenPackage, R.string.disable_user_control, vm.ucdPackages, vm::getUcdPackages,
::choosePackage, ::navigateToAppGroups, vm.appGroups, R.string.info_disable_user_control) vm::setPackageUcd, ::navigateUp, vm.chosenPackage, ::choosePackage,
::navigateToAppGroups, vm.appGroups, R.string.info_disable_user_control
)
} }
composable<PermissionsManager> { composable<PermissionsManager> {
PermissionsManagerScreen(vm.packagePermissions, vm::getPackagePermissions, PermissionsManagerScreen(
vm::setPackagePermission, ::navigateUp, it.toRoute(), vm.chosenPackage, ::choosePackage) vm.packagePermissions, vm::getPackagePermissions, vm::setPackagePermission,
::navigateUp, it.toRoute(), vm.chosenPackage, ::chooseSinglePackage
)
} }
composable<DisableMeteredData> { composable<DisableMeteredData> {
PackageFunctionScreen(R.string.disable_metered_data, vm.mddPackages, PackageFunctionScreen(
vm::getMddPackages, vm::setPackageMdd, ::navigateUp, vm.chosenPackage, R.string.disable_metered_data, vm.mddPackages, vm::getMddPackages,
::choosePackage, ::navigateToAppGroups, vm.appGroups) vm::setPackageMdd, ::navigateUp, vm.chosenPackage, ::choosePackage,
::navigateToAppGroups, vm.appGroups
)
} }
composable<ClearAppStorage> { composable<ClearAppStorage> {
ClearAppStorageScreen(vm.chosenPackage, ::choosePackage, vm::clearAppData, ::navigateUp) ClearAppStorageScreen(
vm.chosenPackage, ::chooseSinglePackage, vm::clearAppData, ::navigateUp
)
} }
composable<UninstallApp> { composable<UninstallApp> {
UninstallAppScreen(vm.chosenPackage, ::choosePackage, vm::uninstallPackage, ::navigateUp) UninstallAppScreen(
vm.chosenPackage, ::chooseSinglePackage, vm::uninstallPackage, ::navigateUp
)
} }
composable<KeepUninstalledPackages> { composable<KeepUninstalledPackages> {
PackageFunctionScreenWithoutResult(R.string.keep_uninstalled_packages, vm.kuPackages, PackageFunctionScreen(
vm::getKuPackages, vm::setPackageKu, ::navigateUp, vm.chosenPackage, R.string.keep_uninstalled_packages, vm.kuPackages, vm::getKuPackages,
::choosePackage, ::navigateToAppGroups, vm.appGroups, vm::setPackageKu, ::navigateUp, vm.chosenPackage, ::choosePackage,
R.string.info_keep_uninstalled_apps) ::navigateToAppGroups, vm.appGroups, R.string.info_keep_uninstalled_apps
)
} }
composable<InstallExistingApp> { composable<InstallExistingApp> {
InstallExistingAppScreen(vm.chosenPackage, ::choosePackage, InstallExistingAppScreen(
vm::installExistingApp, ::navigateUp) vm.chosenPackage, ::chooseSinglePackage, vm::installExistingApp, ::navigateUp
)
} }
composable<CrossProfilePackages> { composable<CrossProfilePackages> {
PackageFunctionScreenWithoutResult(R.string.cross_profile_apps, vm.cpPackages, PackageFunctionScreen(
R.string.cross_profile_apps, vm.cpPackages,
vm::getCpPackages, vm::setPackageCp, ::navigateUp, vm.chosenPackage, vm::getCpPackages, vm::setPackageCp, ::navigateUp, vm.chosenPackage,
::choosePackage, ::navigateToAppGroups, vm.appGroups) ::choosePackage, ::navigateToAppGroups, vm.appGroups
)
} }
composable<CrossProfileWidgetProviders> { composable<CrossProfileWidgetProviders> {
PackageFunctionScreen(R.string.cross_profile_widget, vm.cpwProviders, PackageFunctionScreen(R.string.cross_profile_widget, vm.cpwProviders,
@@ -579,28 +604,45 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
::choosePackage, ::navigateToAppGroups, vm.appGroups) ::choosePackage, ::navigateToAppGroups, vm.appGroups)
} }
composable<CredentialManagerPolicy> { composable<CredentialManagerPolicy> {
CredentialManagerPolicyScreen(vm.chosenPackage, ::choosePackage, CredentialManagerPolicyScreen(
vm.cmPackages, vm::getCmPolicy, vm::setCmPackage, vm::setCmPolicy, ::navigateUp) vm.chosenPackage, ::choosePackage, vm.cmPackages, vm::getCmPolicy,
vm::setCmPackage, vm::setCmPolicy, ::navigateUp
)
} }
composable<PermittedAccessibilityServices> { composable<PermittedAccessibilityServices> {
PermittedAsAndImPackages(R.string.permitted_accessibility_services, PermittedAsAndImPackages(
R.string.permitted_accessibility_services,
R.string.system_accessibility_always_allowed, vm.chosenPackage, ::choosePackage, R.string.system_accessibility_always_allowed, vm.chosenPackage, ::choosePackage,
vm.pasPackages, vm::getPasPackages, vm::setPasPackage, vm::setPasPolicy, ::navigateUp) vm.pasPackages, vm::getPasPackages, vm::setPasPackage, vm::setPasPolicy,
::navigateUp
)
} }
composable<PermittedInputMethods> { composable<PermittedInputMethods> {
PermittedAsAndImPackages(R.string.permitted_ime, R.string.system_ime_always_allowed, PermittedAsAndImPackages(
R.string.permitted_ime, R.string.system_ime_always_allowed,
vm.chosenPackage, ::choosePackage, vm.pimPackages, vm::getPimPackages, vm.chosenPackage, ::choosePackage, vm.pimPackages, vm::getPimPackages,
vm::setPimPackage, vm::setPimPolicy, ::navigateUp) vm::setPimPackage, vm::setPimPolicy, ::navigateUp
)
} }
composable<EnableSystemApp> { composable<EnableSystemApp> {
EnableSystemAppScreen(vm.chosenPackage, ::choosePackage, vm::enableSystemApp, ::navigateUp) EnableSystemAppScreen(
vm.chosenPackage, ::chooseSinglePackage, vm::enableSystemApp, ::navigateUp
)
} }
composable<SetDefaultDialer> { composable<SetDefaultDialer> {
SetDefaultDialerScreen(vm.chosenPackage, ::choosePackage, vm::setDefaultDialer, ::navigateUp) SetDefaultDialerScreen(
vm.chosenPackage, ::chooseSinglePackage, vm::setDefaultDialer, ::navigateUp
)
}
composable<ManagedConfiguration> {
ManagedConfigurationScreen(
it.toRoute(), vm.appRestrictions, vm::setAppRestrictions,
vm::clearAppRestrictions, ::navigateUp
)
} }
composable<ManageAppGroups> { composable<ManageAppGroups> {
ManageAppGroupsScreen( ManageAppGroupsScreen(
vm.appGroups, vm.appGroups, vm::exportAppGroups, vm::importAppGroups,
{ id, name, apps -> navController.navigate(EditAppGroup(id, name, apps)) }, { id, name, apps -> navController.navigate(EditAppGroup(id, name, apps)) },
::navigateUp ::navigateUp
) )
@@ -751,7 +793,10 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) {
} }
if(privilege.device || privilege.profile) { if(privilege.device || privilege.profile) {
HomePageItem(R.string.applications, R.drawable.apps_fill0) { HomePageItem(R.string.applications, R.drawable.apps_fill0) {
onNavigate(if(SP.applicationsListView) ApplicationsList(true) else ApplicationsFeatures) onNavigate(
if (SP.applicationsListView) ApplicationsList(true, true)
else ApplicationsFeatures
)
} }
if(VERSION.SDK_INT >= 24) { if(VERSION.SDK_INT >= 24) {
HomePageItem(R.string.user_restriction, R.drawable.person_off) { onNavigate(UserRestriction) } HomePageItem(R.string.user_restriction, R.drawable.person_off) { onNavigate(UserRestriction) }

View File

@@ -25,6 +25,8 @@ import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.RestrictionEntry
import android.content.RestrictionsManager
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller
import android.content.pm.PackageManager import android.content.pm.PackageManager
@@ -39,6 +41,7 @@ import android.net.wifi.WifiManager
import android.net.wifi.WifiSsid import android.net.wifi.WifiSsid
import android.os.Binder import android.os.Binder
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Bundle
import android.os.HardwarePropertiesManager import android.os.HardwarePropertiesManager
import android.os.UserHandle import android.os.UserHandle
import android.os.UserManager import android.os.UserManager
@@ -60,7 +63,9 @@ import com.bintianqi.owndroid.dpm.ApnConfig
import com.bintianqi.owndroid.dpm.ApnMvnoType import com.bintianqi.owndroid.dpm.ApnMvnoType
import com.bintianqi.owndroid.dpm.ApnProtocol import com.bintianqi.owndroid.dpm.ApnProtocol
import com.bintianqi.owndroid.dpm.AppGroup import com.bintianqi.owndroid.dpm.AppGroup
import com.bintianqi.owndroid.dpm.AppRestriction
import com.bintianqi.owndroid.dpm.AppStatus import com.bintianqi.owndroid.dpm.AppStatus
import com.bintianqi.owndroid.dpm.BasicAppGroup
import com.bintianqi.owndroid.dpm.CaCertInfo import com.bintianqi.owndroid.dpm.CaCertInfo
import com.bintianqi.owndroid.dpm.CreateUserResult import com.bintianqi.owndroid.dpm.CreateUserResult
import com.bintianqi.owndroid.dpm.CreateWorkProfileOptions import com.bintianqi.owndroid.dpm.CreateWorkProfileOptions
@@ -115,6 +120,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.put
import java.net.InetAddress import java.net.InetAddress
import java.security.MessageDigest import java.security.MessageDigest
import java.security.cert.CertificateException import java.security.cert.CertificateException
@@ -154,10 +163,10 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
return AppLockConfig(passwordHash?.ifEmpty { null }, SP.biometricsUnlock, SP.lockWhenLeaving) return AppLockConfig(passwordHash?.ifEmpty { null }, SP.biometricsUnlock, SP.lockWhenLeaving)
} }
fun setAppLockConfig(config: AppLockConfig) { fun setAppLockConfig(config: AppLockConfig) {
SP.lockPasswordHash = if (config.password == null) { if (config.password == null) {
"" SP.lockPasswordHash = ""
} else { } else if (!config.password.isEmpty()) {
config.password.hash() SP.lockPasswordHash = config.password.hash()
} }
SP.biometricsUnlock = config.biometrics SP.biometricsUnlock = config.biometrics
SP.lockWhenLeaving = config.whenLeaving SP.lockWhenLeaving = config.whenLeaving
@@ -196,6 +205,11 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
} }
fun onPackageRemoved(name: String) {
installedPackages.update { list ->
list.filter { it.name != name }
}
}
fun getAppInfo(info: ApplicationInfo) = fun getAppInfo(info: ApplicationInfo) =
AppInfo(info.packageName, info.loadLabel(PM).toString(), info.loadIcon(PM), info.flags) AppInfo(info.packageName, info.loadLabel(PM).toString(), info.loadIcon(PM), info.flags)
fun getAppInfo(name: String): AppInfo { fun getAppInfo(name: String): AppInfo {
@@ -215,10 +229,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
suspendedPackages.value = packages.map { getAppInfo(it) } suspendedPackages.value = packages.map { getAppInfo(it) }
} }
@RequiresApi(24) @RequiresApi(24)
fun setPackageSuspended(name: String, status: Boolean): Boolean { fun setPackageSuspended(packages: List<String>, status: Boolean) {
val result = DPM.setPackagesSuspended(DAR, arrayOf(name), status) DPM.setPackagesSuspended(DAR, packages.toTypedArray(), status)
getSuspendedPackaged() getSuspendedPackaged()
return result.isEmpty()
} }
val hiddenPackages = MutableStateFlow(emptyList<AppInfo>()) val hiddenPackages = MutableStateFlow(emptyList<AppInfo>())
@@ -227,10 +240,11 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
DPM.isApplicationHidden(DAR, it.packageName) DPM.isApplicationHidden(DAR, it.packageName)
}.map { getAppInfo(it) } }.map { getAppInfo(it) }
} }
fun setPackageHidden(name: String, status: Boolean): Boolean { fun setPackageHidden(packages: List<String>, status: Boolean) {
val result = DPM.setApplicationHidden(DAR, name, status) for (name in packages) {
DPM.setApplicationHidden(DAR, name, status)
}
getHiddenPackages() getHiddenPackages()
return result
} }
// Uninstall blocked packages // Uninstall blocked packages
@@ -240,8 +254,10 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
DPM.isUninstallBlocked(DAR, it.packageName) DPM.isUninstallBlocked(DAR, it.packageName)
}.map { getAppInfo(it) } }.map { getAppInfo(it) }
} }
fun setPackageUb(name: String, status: Boolean) { fun setPackageUb(packages: List<String>, status: Boolean) {
for (name in packages) {
DPM.setUninstallBlocked(DAR, name, status) DPM.setUninstallBlocked(DAR, name, status)
}
getUbPackages() getUbPackages()
} }
@@ -254,16 +270,17 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
@RequiresApi(30) @RequiresApi(30)
fun setPackageUcd(name: String, status: Boolean) { fun setPackageUcd(packages: List<String>, status: Boolean) {
DPM.setUserControlDisabledPackages( DPM.setUserControlDisabledPackages(
DAR, DAR,
ucdPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) } ucdPackages.value.map { it.name }.run {
if (status) plus(packages) else minus(packages)
}
) )
getUcdPackages() getUcdPackages()
} }
val packagePermissions = MutableStateFlow(emptyMap<String, Int>()) val packagePermissions = MutableStateFlow(emptyMap<String, Int>())
@RequiresApi(23)
fun getPackagePermissions(name: String) { fun getPackagePermissions(name: String) {
if (name.isValidPackageName) { if (name.isValidPackageName) {
packagePermissions.value = runtimePermissions.associate { packagePermissions.value = runtimePermissions.associate {
@@ -273,7 +290,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
packagePermissions.value = emptyMap() packagePermissions.value = emptyMap()
} }
} }
@RequiresApi(23)
fun setPackagePermission(name: String, permission: String, status: Int): Boolean { fun setPackagePermission(name: String, permission: String, status: Int): Boolean {
val result = DPM.setPermissionGrantState(DAR, name, permission, status) val result = DPM.setPermissionGrantState(DAR, name, permission, status)
getPackagePermissions(name) getPackagePermissions(name)
@@ -287,12 +303,13 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
mddPackages.value = DPM.getMeteredDataDisabledPackages(DAR).distinct().map { getAppInfo(it) } mddPackages.value = DPM.getMeteredDataDisabledPackages(DAR).distinct().map { getAppInfo(it) }
} }
@RequiresApi(28) @RequiresApi(28)
fun setPackageMdd(name: String, status: Boolean): Boolean { fun setPackageMdd(packages: List<String>, status: Boolean) {
val result = DPM.setMeteredDataDisabledPackages( DPM.setMeteredDataDisabledPackages(
DAR, mddPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) } DAR, mddPackages.value.map { it.name }.run {
if (status) plus(packages) else minus(packages)
}
) )
getMddPackages() getMddPackages()
return result.isEmpty()
} }
// Keep uninstalled packages // Keep uninstalled packages
@@ -302,9 +319,11 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
kuPackages.value = DPM.getKeepUninstalledPackages(DAR)?.distinct()?.map { getAppInfo(it) } ?: emptyList() kuPackages.value = DPM.getKeepUninstalledPackages(DAR)?.distinct()?.map { getAppInfo(it) } ?: emptyList()
} }
@RequiresApi(28) @RequiresApi(28)
fun setPackageKu(name: String, status: Boolean) { fun setPackageKu(packages: List<String>, status: Boolean) {
DPM.setKeepUninstalledPackages( DPM.setKeepUninstalledPackages(
DAR, kuPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) } DAR, kuPackages.value.map { it.name }.run {
if (status) plus(packages) else minus(packages)
}
) )
getKuPackages() getKuPackages()
} }
@@ -316,10 +335,12 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
cpPackages.value = DPM.getCrossProfilePackages(DAR).map { getAppInfo(it) } cpPackages.value = DPM.getCrossProfilePackages(DAR).map { getAppInfo(it) }
} }
@RequiresApi(30) @RequiresApi(30)
fun setPackageCp(name: String, status: Boolean) { fun setPackageCp(packages: List<String>, status: Boolean) {
DPM.setCrossProfilePackages( DPM.setCrossProfilePackages(
DAR, DAR,
cpPackages.value.map { it.name }.toSet().run { if (status) plus(name) else minus(name) } cpPackages.value.map { it.name }.toSet().run {
if (status) plus(packages) else minus(packages)
}
) )
getCpPackages() getCpPackages()
} }
@@ -329,14 +350,15 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
fun getCpwProviders() { fun getCpwProviders() {
cpwProviders.value = DPM.getCrossProfileWidgetProviders(DAR).distinct().map { getAppInfo(it) } cpwProviders.value = DPM.getCrossProfileWidgetProviders(DAR).distinct().map { getAppInfo(it) }
} }
fun setCpwProvider(name: String, status: Boolean): Boolean { fun setCpwProvider(packages: List<String>, status: Boolean) {
val result = if (status) { for (name in packages) {
if (status) {
DPM.addCrossProfileWidgetProvider(DAR, name) DPM.addCrossProfileWidgetProvider(DAR, name)
} else { } else {
DPM.removeCrossProfileWidgetProvider(DAR, name) DPM.removeCrossProfileWidgetProvider(DAR, name)
} }
}
getCpwProviders() getCpwProviders()
return result
} }
@RequiresApi(28) @RequiresApi(28)
@@ -347,6 +369,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
fun uninstallPackage(packageName: String, onComplete: (String?) -> Unit) { fun uninstallPackage(packageName: String, onComplete: (String?) -> Unit) {
val action = "com.bintianqi.owndroid.action.PACKAGE_UNINSTALLED"
val receiver = object : BroadcastReceiver() { val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999) val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999)
@@ -364,16 +387,17 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
ContextCompat.registerReceiver( ContextCompat.registerReceiver(
application, receiver, IntentFilter(AppInstallerViewModel.ACTION), null, application, receiver, IntentFilter(action), null,
null, ContextCompat.RECEIVER_EXPORTED null, ContextCompat.RECEIVER_NOT_EXPORTED
) )
val intent = Intent(action).setPackage(application.packageName)
val pi = if(VERSION.SDK_INT >= 34) { val pi = if(VERSION.SDK_INT >= 34) {
PendingIntent.getBroadcast( PendingIntent.getBroadcast(
application, 0, Intent(AppInstallerViewModel.ACTION), application, 0, intent,
PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE
).intentSender ).intentSender
} else { } else {
PendingIntent.getBroadcast(application, 0, Intent(AppInstallerViewModel.ACTION), PendingIntent.FLAG_MUTABLE).intentSender PendingIntent.getBroadcast(application, 0, intent, PendingIntent.FLAG_MUTABLE).intentSender
} }
application.getPackageInstaller().uninstall(packageName, pi) application.getPackageInstaller().uninstall(packageName, pi)
} }
@@ -392,9 +416,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
policy.policyType policy.policyType
} ?: -1 } ?: -1
} }
fun setCmPackage(name: String, status: Boolean) { fun setCmPackage(packages: List<String>, status: Boolean) {
cmPackages.update { list -> cmPackages.update {
if (status) list + getAppInfo(name) else list.filter { it.name != name } updateAppInfoList(it, packages, status)
} }
} }
@RequiresApi(34) @RequiresApi(34)
@@ -405,6 +429,16 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
getCmPolicy() getCmPolicy()
} }
fun updateAppInfoList(
origin: List<AppInfo>, input: List<String>, status: Boolean
): List<AppInfo> {
return if (status) {
origin + input.map { getAppInfo(it) }
} else {
origin.filter { it.name !in input }
}
}
// Permitted input method // Permitted input method
val pimPackages = MutableStateFlow(emptyList<AppInfo>()) val pimPackages = MutableStateFlow(emptyList<AppInfo>())
fun getPimPackages(): Boolean { fun getPimPackages(): Boolean {
@@ -413,9 +447,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
packages == null packages == null
} }
} }
fun setPimPackage(name: String, status: Boolean) { fun setPimPackage(packages: List<String>, status: Boolean) {
pimPackages.update { packages -> pimPackages.update {
if (status) packages + getAppInfo(name) else packages.filter { it.name != name } updateAppInfoList(it, packages, status)
} }
} }
fun setPimPolicy(allowAll: Boolean): Boolean { fun setPimPolicy(allowAll: Boolean): Boolean {
@@ -433,9 +467,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
packages == null packages == null
} }
} }
fun setPasPackage(name: String, status: Boolean) { fun setPasPackage(packages: List<String>, status: Boolean) {
pasPackages.update { packages -> pasPackages.update {
if (status) packages + getAppInfo(name) else packages.filter { it.name != name } updateAppInfoList(it, packages, status)
} }
} }
fun setPasPolicy(allowAll: Boolean): Boolean { fun setPasPolicy(allowAll: Boolean): Boolean {
@@ -457,14 +491,18 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
DPM.isUninstallBlocked(DAR, name), DPM.isUninstallBlocked(DAR, name),
if (VERSION.SDK_INT >= 30) name in DPM.getUserControlDisabledPackages(DAR) else false, if (VERSION.SDK_INT >= 30) name in DPM.getUserControlDisabledPackages(DAR) else false,
if (VERSION.SDK_INT >= 28) name in DPM.getMeteredDataDisabledPackages(DAR) else false, if (VERSION.SDK_INT >= 28) name in DPM.getMeteredDataDisabledPackages(DAR) else false,
if (VERSION.SDK_INT >= 28) DPM.getKeepUninstalledPackages(DAR)?.contains(name) == true else false if (VERSION.SDK_INT >= 28 && Privilege.status.value.device)
DPM.getKeepUninstalledPackages(DAR)?.contains(name) == true
else false
) )
} }
// Application details // Application details
@RequiresApi(24) @RequiresApi(24)
fun adSetPackageSuspended(name: String, status: Boolean) { fun adSetPackageSuspended(name: String, status: Boolean) {
try {
DPM.setPackagesSuspended(DAR, arrayOf(name), status) DPM.setPackagesSuspended(DAR, arrayOf(name), status)
appStatus.update { it.copy(suspend = DPM.isPackageSuspended(DAR, name)) } appStatus.update { it.copy(suspend = DPM.isPackageSuspended(DAR, name)) }
} catch (_: Exception) {}
} }
fun adSetPackageHidden(name: String, status: Boolean) { fun adSetPackageHidden(name: String, status: Boolean) {
DPM.setApplicationHidden(DAR, name, status) DPM.setApplicationHidden(DAR, name, status)
@@ -510,6 +548,79 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
val appRestrictions = MutableStateFlow(emptyList<AppRestriction>())
fun getAppRestrictions(name: String) {
val rm = application.getSystemService(RestrictionsManager::class.java)
try {
val bundle = DPM.getApplicationRestrictions(DAR, name)
appRestrictions.value = rm.getManifestRestrictions(name)?.mapNotNull {
transformRestrictionEntry(it)
}?.map {
if (bundle.containsKey(it.key)) {
when (it) {
is AppRestriction.BooleanItem -> it.value = bundle.getBoolean(it.key)
is AppRestriction.StringItem -> it.value = bundle.getString(it.key)
is AppRestriction.IntItem -> it.value = bundle.getInt(it.key)
is AppRestriction.ChoiceItem -> it.value = bundle.getString(it.key)
is AppRestriction.MultiSelectItem -> it.value = bundle.getStringArray(it.key)
}
}
it
} ?: emptyList()
} catch (e: Exception) {
e.printStackTrace()
appRestrictions.value = emptyList()
}
}
fun setAppRestrictions(name: String, item: AppRestriction) {
viewModelScope.launch(Dispatchers.IO) {
val bundle = transformAppRestriction(
appRestrictions.value.filter { it.key != item.key }.plus(item)
)
DPM.setApplicationRestrictions(DAR, name, bundle)
getAppRestrictions(name)
}
}
fun clearAppRestrictions(name: String) {
viewModelScope.launch(Dispatchers.IO) {
DPM.setApplicationRestrictions(DAR, name, Bundle())
getAppRestrictions(name)
}
}
fun transformRestrictionEntry(e: RestrictionEntry): AppRestriction? {
return when (e.type) {
RestrictionEntry.TYPE_INTEGER ->
AppRestriction.IntItem(e.key, e.title, e.description, null)
RestrictionEntry.TYPE_STRING ->
AppRestriction.StringItem(e.key, e.title, e.description, null)
RestrictionEntry.TYPE_BOOLEAN ->
AppRestriction.BooleanItem(e.key, e.title, e.description, null)
RestrictionEntry.TYPE_CHOICE -> AppRestriction.ChoiceItem(e.key, e.title,
e.description, e.choiceEntries, e.choiceValues, null)
RestrictionEntry.TYPE_MULTI_SELECT -> AppRestriction.MultiSelectItem(e.key, e.title,
e.description, e.choiceEntries, e.choiceValues, null)
else -> null
}
}
fun transformAppRestriction(list: List<AppRestriction>): Bundle {
val b = Bundle()
for (r in list) {
when (r) {
is AppRestriction.IntItem -> r.value?.let { b.putInt(r.key, it) }
is AppRestriction.StringItem -> r.value?.let { b.putString(r.key, it) }
is AppRestriction.BooleanItem -> r.value?.let { b.putBoolean(r.key, it) }
is AppRestriction.ChoiceItem -> r.value?.let { b.putString(r.key, it) }
is AppRestriction.MultiSelectItem -> r.value?.let { b.putStringArray(r.key, r.value) }
}
}
return b
}
val appGroups = MutableStateFlow(emptyList<AppGroup>()) val appGroups = MutableStateFlow(emptyList<AppGroup>())
init { init {
getAppGroups() getAppGroups()
@@ -527,6 +638,20 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
group.filter { it.id != id } group.filter { it.id != id }
} }
} }
fun exportAppGroups(uri: Uri) {
application.contentResolver.openOutputStream(uri)!!.use {
val list: List<BasicAppGroup> = appGroups.value
it.write(Json.encodeToString(list).encodeToByteArray())
}
}
fun importAppGroups(uri: Uri) {
application.contentResolver.openInputStream(uri)!!.use {
Json.decodeFromString<List<BasicAppGroup>>(it.readBytes().decodeToString())
}.forEach {
myRepo.setAppGroup(null, it.name, it.apps)
}
getAppGroups()
}
@RequiresApi(24) @RequiresApi(24)
fun reboot() { fun reboot() {
@@ -584,16 +709,17 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
statusBarDisabled = if (VERSION.SDK_INT >= 34 && statusBarDisabled = if (VERSION.SDK_INT >= 34 &&
privilege.run { device || (profile && affiliated) }) privilege.run { device || (profile && affiliated) })
DPM.isStatusBarDisabled else false, DPM.isStatusBarDisabled else false,
autoTimeEnabled = if (VERSION.SDK_INT >= 30 && privilege.run { device || org }) autoTimeEnabled = if (VERSION.SDK_INT >= 30 && (privilege.device || privilege.org))
DPM.getAutoTimeEnabled(DAR) else false, DPM.getAutoTimeEnabled(DAR) else false,
autoTimeZoneEnabled = if (VERSION.SDK_INT >= 30 && privilege.run { device || org }) autoTimeZoneEnabled = if (VERSION.SDK_INT >= 30 && (privilege.device || privilege.org))
DPM.getAutoTimeZoneEnabled(DAR) else false, DPM.getAutoTimeZoneEnabled(DAR) else false,
autoTimeRequired = if (VERSION.SDK_INT < 30) DPM.autoTimeRequired else false, autoTimeRequired = if (VERSION.SDK_INT < 30) DPM.autoTimeRequired else false,
masterVolumeMuted = DPM.isMasterVolumeMuted(DAR), masterVolumeMuted = DPM.isMasterVolumeMuted(DAR),
backupServiceEnabled = if (VERSION.SDK_INT >= 26) DPM.isBackupServiceEnabled(DAR) else false, backupServiceEnabled = if (VERSION.SDK_INT >= 26) DPM.isBackupServiceEnabled(DAR) else false,
btContactSharingDisabled = if (VERSION.SDK_INT >= 23 && privilege.work) btContactSharingDisabled = if (privilege.work)
DPM.getBluetoothContactSharingDisabled(DAR) else false, DPM.getBluetoothContactSharingDisabled(DAR) else false,
commonCriteriaMode = if (VERSION.SDK_INT >= 30) DPM.isCommonCriteriaModeEnabled(DAR) else false, commonCriteriaMode = if (VERSION.SDK_INT >= 30 && (privilege.device || privilege.org))
DPM.isCommonCriteriaModeEnabled(DAR) else false,
usbSignalEnabled = if (VERSION.SDK_INT >= 31) DPM.isUsbDataSignalingEnabled else false, usbSignalEnabled = if (VERSION.SDK_INT >= 31) DPM.isUsbDataSignalingEnabled else false,
canDisableUsbSignal = if (VERSION.SDK_INT >= 31) DPM.canUsbDataSignalingBeDisabled() else false canDisableUsbSignal = if (VERSION.SDK_INT >= 31) DPM.canUsbDataSignalingBeDisabled() else false
) )
@@ -609,7 +735,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
it.copy(screenCaptureDisabled = DPM.getScreenCaptureDisabled(null)) it.copy(screenCaptureDisabled = DPM.getScreenCaptureDisabled(null))
} }
} }
@RequiresApi(23)
fun setStatusBarDisabled(disabled: Boolean) { fun setStatusBarDisabled(disabled: Boolean) {
val result = DPM.setStatusBarDisabled(DAR, disabled) val result = DPM.setStatusBarDisabled(DAR, disabled)
if (result) systemOptionsStatus.update { it.copy(statusBarDisabled = disabled) } if (result) systemOptionsStatus.update { it.copy(statusBarDisabled = disabled) }
@@ -643,7 +768,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
it.copy(backupServiceEnabled = DPM.isBackupServiceEnabled(DAR)) it.copy(backupServiceEnabled = DPM.isBackupServiceEnabled(DAR))
} }
} }
@RequiresApi(23)
fun setBtContactSharingDisabled(disabled: Boolean) { fun setBtContactSharingDisabled(disabled: Boolean) {
DPM.setBluetoothContactSharingDisabled(DAR, disabled) DPM.setBluetoothContactSharingDisabled(DAR, disabled)
systemOptionsStatus.update { systemOptionsStatus.update {
@@ -662,7 +786,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
DPM.isUsbDataSignalingEnabled = enabled DPM.isUsbDataSignalingEnabled = enabled
systemOptionsStatus.update { it.copy(usbSignalEnabled = DPM.isUsbDataSignalingEnabled) } systemOptionsStatus.update { it.copy(usbSignalEnabled = DPM.isUsbDataSignalingEnabled) }
} }
@RequiresApi(23)
fun setKeyguardDisabled(disabled: Boolean): Boolean { fun setKeyguardDisabled(disabled: Boolean): Boolean {
return DPM.setKeyguardDisabled(DAR, disabled) return DPM.setKeyguardDisabled(DAR, disabled)
} }
@@ -732,11 +855,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
fun setContentProtectionPolicy(policy: Int) { fun setContentProtectionPolicy(policy: Int) {
DPM.setContentProtectionPolicy(DAR, policy) DPM.setContentProtectionPolicy(DAR, policy)
} }
@RequiresApi(23)
fun getPermissionPolicy(): Int { fun getPermissionPolicy(): Int {
return DPM.getPermissionPolicy(DAR) return DPM.getPermissionPolicy(DAR)
} }
@RequiresApi(23)
fun setPermissionPolicy(policy: Int) { fun setPermissionPolicy(policy: Int) {
DPM.setPermissionPolicy(DAR, policy) DPM.setPermissionPolicy(DAR, policy)
} }
@@ -784,19 +905,35 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
getLockTaskPackages() getLockTaskPackages()
} }
@RequiresApi(28) @RequiresApi(28)
fun startLockTaskMode(packageName: String, activity: String): Boolean { fun startLockTaskMode(
packageName: String, activity: String, clearTask: Boolean, showNotification: Boolean
): Boolean {
if (!DPM.isLockTaskPermitted(packageName)) { if (!DPM.isLockTaskPermitted(packageName)) {
val list = lockTaskPackages.value.map { it.name } + packageName val list = lockTaskPackages.value.map { it.name } + packageName
DPM.setLockTaskPackages(DAR, list.toTypedArray()) DPM.setLockTaskPackages(DAR, list.toTypedArray())
getLockTaskPackages() getLockTaskPackages()
} }
if (showNotification) {
DPM.setLockTaskFeatures(
DAR,
DPM.getLockTaskFeatures(DAR) or
DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS or
DevicePolicyManager.LOCK_TASK_FEATURE_HOME
)
}
val options = ActivityOptions.makeBasic().setLockTaskEnabled(true) val options = ActivityOptions.makeBasic().setLockTaskEnabled(true)
val intent = if(activity.isNotEmpty()) { val intent = if(activity.isNotEmpty()) {
Intent().setComponent(ComponentName(packageName, activity)) Intent().setComponent(ComponentName(packageName, activity))
} else PM.getLaunchIntentForPackage(packageName) } else PM.getLaunchIntentForPackage(packageName)
if (intent != null) { if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
or (if (clearTask) Intent.FLAG_ACTIVITY_CLEAR_TASK else 0)
)
application.startActivity(intent, options.toBundle()) application.startActivity(intent, options.toBundle())
if (showNotification) {
application.startForegroundService(Intent(application, LockTaskService::class.java))
}
return true return true
} else { } else {
return false return false
@@ -906,14 +1043,12 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
} }
@RequiresApi(23)
fun getSystemUpdatePolicy(): SystemUpdatePolicyInfo { fun getSystemUpdatePolicy(): SystemUpdatePolicyInfo {
val policy = DPM.systemUpdatePolicy val policy = DPM.systemUpdatePolicy
return SystemUpdatePolicyInfo( return SystemUpdatePolicyInfo(
policy?.policyType ?: -1, policy?.installWindowStart ?: 0, policy?.installWindowEnd ?: 0 policy?.policyType ?: -1, policy?.installWindowStart ?: 0, policy?.installWindowEnd ?: 0
) )
} }
@RequiresApi(23)
fun setSystemUpdatePolicy(info: SystemUpdatePolicyInfo) { fun setSystemUpdatePolicy(info: SystemUpdatePolicyInfo) {
val policy = when (info.type) { val policy = when (info.type) {
SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC -> SystemUpdatePolicy.createAutomaticInstallPolicy() SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC -> SystemUpdatePolicy.createAutomaticInstallPolicy()
@@ -1227,18 +1362,11 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
fun createWorkProfile(options: CreateWorkProfileOptions): Intent { fun createWorkProfile(options: CreateWorkProfileOptions): Intent {
val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
if (VERSION.SDK_INT >= 23) {
intent.putExtra( intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
MyAdminComponent MyAdminComponent
) )
} else { if (options.migrateAccount) {
intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
application.packageName
)
}
if (options.migrateAccount && VERSION.SDK_INT >= 22) {
intent.putExtra( intent.putExtra(
DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE,
Account(options.accountName, options.accountType) Account(options.accountName, options.accountType)
@@ -1315,10 +1443,10 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
return UserInformation( return UserInformation(
if (VERSION.SDK_INT >= 24) UserManager.supportsMultipleUsers() else false, if (VERSION.SDK_INT >= 24) UserManager.supportsMultipleUsers() else false,
if (VERSION.SDK_INT >= 31) UserManager.isHeadlessSystemUserMode() else false, if (VERSION.SDK_INT >= 31) UserManager.isHeadlessSystemUserMode() else false,
if (VERSION.SDK_INT >= 23) UM.isSystemUser else false, UM.isSystemUser,
if (VERSION.SDK_INT >= 34) UM.isAdminUser else false, if (VERSION.SDK_INT >= 34) UM.isAdminUser else false,
if (VERSION.SDK_INT >= 25) UM.isDemoUser else false, if (VERSION.SDK_INT >= 25) UM.isDemoUser else false,
if (VERSION.SDK_INT >= 23) UM.getUserCreationTime(uh) else 0, UM.getUserCreationTime(uh),
if (VERSION.SDK_INT >= 28) DPM.isLogoutEnabled else false, 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.isEphemeralUser(DAR) else false,
if (VERSION.SDK_INT >= 28) DPM.isAffiliatedUser else false, if (VERSION.SDK_INT >= 28) DPM.isAffiliatedUser else false,
@@ -1354,6 +1482,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
UserManager.USER_OPERATION_ERROR_UNKNOWN -> R.string.unknown_error UserManager.USER_OPERATION_ERROR_UNKNOWN -> R.string.unknown_error
UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE-> R.string.fail_managed_profile 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_MAX_RUNNING_USERS -> R.string.limit_reached
UserManager.USER_OPERATION_ERROR_MAX_USERS -> R.string.limit_reached
UserManager.USER_OPERATION_ERROR_CURRENT_USER -> R.string.fail_current_user UserManager.USER_OPERATION_ERROR_CURRENT_USER -> R.string.fail_current_user
else -> R.string.unknown else -> R.string.unknown
} }
@@ -1392,7 +1521,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
fun setProfileName(name: String) { fun setProfileName(name: String) {
DPM.setProfileName(DAR, name) DPM.setProfileName(DAR, name)
} }
@RequiresApi(23)
fun setUserIcon(bitmap: Bitmap) { fun setUserIcon(bitmap: Bitmap) {
DPM.setUserIcon(DAR, bitmap) DPM.setUserIcon(DAR, bitmap)
} }
@@ -1530,7 +1658,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
return PM.getPackageUid(name, 0) return PM.getPackageUid(name, 0)
} }
var networkStatsData = emptyList<NetworkStatsData>() var networkStatsData = emptyList<NetworkStatsData>()
@RequiresApi(23)
fun readNetworkStats(stats: NetworkStats): List<NetworkStatsData> { fun readNetworkStats(stats: NetworkStats): List<NetworkStatsData> {
val list = mutableListOf<NetworkStatsData>() val list = mutableListOf<NetworkStatsData>()
while (stats.hasNextBucket()) { while (stats.hasNextBucket()) {
@@ -1541,7 +1668,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
stats.close() stats.close()
return list return list
} }
@RequiresApi(23)
fun readNetworkStatsBucket(bucket: NetworkStats.Bucket): NetworkStatsData { fun readNetworkStatsBucket(bucket: NetworkStats.Bucket): NetworkStatsData {
return NetworkStatsData( return NetworkStatsData(
bucket.rxBytes, bucket.rxPackets, bucket.txBytes, bucket.txPackets, bucket.rxBytes, bucket.rxPackets, bucket.txBytes, bucket.txPackets,
@@ -1823,7 +1949,12 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
@RequiresApi(26) @RequiresApi(26)
fun setRpToken(token: String): Boolean { fun setRpToken(token: String): Boolean {
return DPM.setResetPasswordToken(DAR, token.encodeToByteArray()) return try {
DPM.setResetPasswordToken(DAR, token.encodeToByteArray())
} catch (e: Exception) {
e.printStackTrace()
false
}
} }
@RequiresApi(26) @RequiresApi(26)
fun clearRpToken(): Boolean { fun clearRpToken(): Boolean {

View File

@@ -6,7 +6,9 @@ import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -22,7 +24,14 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
@@ -36,6 +45,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@@ -45,8 +55,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
@@ -66,17 +78,20 @@ data class AppInfo(
private fun searchInString(query: String, content: String) private fun searchInString(query: String, content: String)
= query.split(' ').all { content.contains(it, true) } = query.split(' ').all { content.contains(it, true) }
@Serializable data class ApplicationsList(val canSwitchView: Boolean) @Serializable data class ApplicationsList(val canSwitchView: Boolean, val multiSelect: Boolean)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun AppChooserScreen( fun AppChooserScreen(
canSwitchView: Boolean, packageList: MutableStateFlow<List<AppInfo>>, params: ApplicationsList, packageList: MutableStateFlow<List<AppInfo>>,
refreshProgress: MutableStateFlow<Float>, onChoosePackage: (String?) -> Unit, refreshProgress: MutableStateFlow<Float>, onChoosePackage: (String?) -> Unit,
onSwitchView: () -> Unit, onRefresh: () -> Unit onSwitchView: () -> Unit, onRefresh: () -> Unit,
setPackagesSuspend: (List<String>, Boolean) -> Unit,
setPackagesHidden: (List<String>, Boolean) -> Unit,
) { ) {
val packages by packageList.collectAsStateWithLifecycle() val packages by packageList.collectAsStateWithLifecycle()
val context = LocalContext.current val context = LocalContext.current
val hf = LocalHapticFeedback.current
val progress by refreshProgress.collectAsStateWithLifecycle() val progress by refreshProgress.collectAsStateWithLifecycle()
var system by rememberSaveable { mutableStateOf(false) } var system by rememberSaveable { mutableStateOf(false) }
var query by rememberSaveable { mutableStateOf("") } var query by rememberSaveable { mutableStateOf("") }
@@ -85,6 +100,7 @@ fun AppChooserScreen(
system == (it.flags and ApplicationInfo.FLAG_SYSTEM != 0) && system == (it.flags and ApplicationInfo.FLAG_SYSTEM != 0) &&
(query.isEmpty() || (searchInString(query, it.label) || searchInString(query, it.name))) (query.isEmpty() || (searchInString(query, it.label) || searchInString(query, it.name)))
} }
val selectedPackages = remember { mutableStateListOf<AppInfo>() }
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if(packages.size <= 1) onRefresh() if(packages.size <= 1) onRefresh()
@@ -101,15 +117,83 @@ fun AppChooserScreen(
system = !system system = !system
context.popToast(if(system) R.string.show_system_app else R.string.show_user_app) context.popToast(if(system) R.string.show_system_app else R.string.show_user_app)
}) { }) {
Icon(painter = painterResource(R.drawable.filter_alt_fill0), contentDescription = null) Icon(painterResource(R.drawable.filter_alt_fill0), null)
} }
if (selectedPackages.isEmpty()) {
IconButton(onRefresh, enabled = progress == 1F) { IconButton(onRefresh, enabled = progress == 1F) {
Icon(painter = painterResource(R.drawable.refresh_fill0), contentDescription = null) Icon(Icons.Default.Refresh, null)
} }
if (canSwitchView) IconButton(onSwitchView) { if (params.canSwitchView) IconButton(onSwitchView) {
Icon(Icons.AutoMirrored.Default.List, null) Icon(Icons.AutoMirrored.Default.List, null)
} }
} }
}
if (selectedPackages.isNotEmpty()) {
if (params.canSwitchView) {
var dropdown by remember { mutableStateOf(false) }
Box {
IconButton({
dropdown = !dropdown
}) {
Icon(Icons.Default.MoreVert, null)
}
DropdownMenu(dropdown, { dropdown = false }) {
if (Build.VERSION.SDK_INT >= 24) {
DropdownMenuItem(
{ Text(stringResource(R.string.suspend)) },
{
setPackagesSuspend(selectedPackages.map { it.name }, true)
dropdown = false
selectedPackages.clear()
},
leadingIcon = {
Icon(painterResource(R.drawable.block_fill0), null)
}
)
DropdownMenuItem(
{ Text(stringResource(R.string.unsuspend)) },
{
setPackagesSuspend(selectedPackages.map { it.name }, false)
dropdown = false
selectedPackages.clear()
},
leadingIcon = {
Icon(painterResource(R.drawable.enable_fill0), null)
}
)
}
DropdownMenuItem(
{ Text(stringResource(R.string.hide)) },
{
setPackagesHidden(selectedPackages.map { it.name }, true)
dropdown = false
selectedPackages.clear()
},
leadingIcon = {
Icon(painterResource(R.drawable.visibility_off_fill0), null)
}
)
DropdownMenuItem(
{ Text(stringResource(R.string.unhide)) },
{
setPackagesHidden(selectedPackages.map { it.name }, false)
dropdown = false
selectedPackages.clear()
},
leadingIcon = {
Icon(painterResource(R.drawable.visibility_fill0), null)
}
)
}
}
} else {
FilledIconButton({
onChoosePackage(selectedPackages.joinToString("\n") { it.name })
}) {
Icon(Icons.Default.Check, null)
}
}
}
}, },
title = { title = {
if (searchMode) { if (searchMode) {
@@ -122,19 +206,20 @@ fun AppChooserScreen(
keyboardActions = KeyboardActions { focusMgr.clearFocus() }, keyboardActions = KeyboardActions { focusMgr.clearFocus() },
placeholder = { Text(stringResource(R.string.search)) }, placeholder = { Text(stringResource(R.string.search)) },
trailingIcon = { trailingIcon = {
Icon( IconButton({
painter = painterResource(R.drawable.close_fill0),
contentDescription = null,
modifier = Modifier.clickable {
focusMgr.clearFocus()
query = "" query = ""
searchMode = false searchMode = false
}) {
Icon(Icons.Outlined.Clear, null)
} }
)
}, },
textStyle = typography.bodyLarge, textStyle = typography.bodyLarge,
modifier = Modifier.fillMaxWidth().focusRequester(fr) modifier = Modifier.fillMaxWidth().focusRequester(fr)
) )
} else {
if (selectedPackages.isNotEmpty()) {
Text(selectedPackages.size.toString())
}
} }
}, },
navigationIcon = { navigationIcon = {
@@ -156,10 +241,24 @@ fun AppChooserScreen(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .combinedClickable(onLongClick = {
if (params.multiSelect && it !in selectedPackages) {
selectedPackages += it
hf.performHapticFeedback(HapticFeedbackType.LongPress)
}
}, onClick = {
if (selectedPackages.isEmpty()) {
focusMgr.clearFocus() focusMgr.clearFocus()
onChoosePackage(it.name) onChoosePackage(it.name)
} else {
if (it in selectedPackages) selectedPackages -= it
else selectedPackages += it
} }
})
.background(
if (it in selectedPackages) MaterialTheme.colorScheme.primaryContainer
else MaterialTheme.colorScheme.background
)
.padding(horizontal = 8.dp, vertical = 10.dp) .padding(horizontal = 8.dp, vertical = 10.dp)
.animateItem() .animateItem()
) { ) {

View File

@@ -1,31 +1,17 @@
package com.bintianqi.owndroid package com.bintianqi.owndroid
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.admin.DeviceAdminReceiver import android.app.admin.DeviceAdminReceiver
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Binder import android.os.Binder
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.UserHandle import android.os.UserHandle
import android.os.UserManager import android.os.UserManager
import androidx.core.app.NotificationCompat
import com.bintianqi.owndroid.dpm.handlePrivilegeChange import com.bintianqi.owndroid.dpm.handlePrivilegeChange
import com.bintianqi.owndroid.dpm.retrieveNetworkLogs import com.bintianqi.owndroid.dpm.retrieveNetworkLogs
import com.bintianqi.owndroid.dpm.retrieveSecurityLogs import com.bintianqi.owndroid.dpm.retrieveSecurityLogs
class Receiver : DeviceAdminReceiver() { class Receiver : DeviceAdminReceiver() {
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
if(VERSION.SDK_INT >= 26 && intent.action == "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE") {
val receiver = ComponentName(context, this::class.java)
val packages = Privilege.DPM.getLockTaskPackages(receiver)
Privilege.DPM.setLockTaskPackages(receiver, arrayOf())
Privilege.DPM.setLockTaskPackages(receiver, packages)
}
}
override fun onEnabled(context: Context, intent: Intent) { override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent) super.onEnabled(context, intent)
Privilege.updateStatus() Privilege.updateStatus()
@@ -56,25 +42,6 @@ class Receiver : DeviceAdminReceiver() {
} }
} }
override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) {
super.onLockTaskModeEntering(context, intent, pkg)
val stopIntent = Intent(context, this::class.java)
.setAction("com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE")
val pendingIntent = PendingIntent.getBroadcast(context, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(context, MyNotificationChannel.LockTaskMode.id)
.setContentTitle(context.getText(R.string.lock_task_mode))
.setSmallIcon(R.drawable.lock_fill0)
.addAction(NotificationCompat.Action.Builder(null, context.getString(R.string.stop), pendingIntent).build())
.build()
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(NotificationType.LockTaskMode.id, notification)
}
override fun onLockTaskModeExiting(context: Context, intent: Intent) {
super.onLockTaskModeExiting(context, intent)
NotificationUtils.cancel(context, NotificationType.LockTaskMode)
}
override fun onPasswordChanged(context: Context, intent: Intent, userHandle: UserHandle) { override fun onPasswordChanged(context: Context, intent: Intent, userHandle: UserHandle) {
super.onPasswordChanged(context, intent, userHandle) super.onPasswordChanged(context, intent, userHandle)
sendUserRelatedNotification(context, userHandle, NotificationType.PasswordChanged) sendUserRelatedNotification(context, userHandle, NotificationType.PasswordChanged)

View File

@@ -232,7 +232,7 @@ fun AppLockSettingsScreen(
config: AppLockConfig, setConfig: (AppLockConfig) -> Unit, config: AppLockConfig, setConfig: (AppLockConfig) -> Unit,
onNavigateUp: () -> Unit onNavigateUp: () -> Unit
) = MyScaffold(R.string.app_lock, onNavigateUp) { ) = MyScaffold(R.string.app_lock, onNavigateUp) {
var password by rememberSaveable { mutableStateOf(config.password ?: "") } var password by rememberSaveable { mutableStateOf("") }
var confirmPassword by rememberSaveable { mutableStateOf("") } var confirmPassword by rememberSaveable { mutableStateOf("") }
var allowBiometrics by rememberSaveable { mutableStateOf(config.biometrics) } var allowBiometrics by rememberSaveable { mutableStateOf(config.biometrics) }
var lockWhenLeaving by rememberSaveable { mutableStateOf(config.whenLeaving) } var lockWhenLeaving by rememberSaveable { mutableStateOf(config.whenLeaving) }

View File

@@ -1,9 +1,12 @@
package com.bintianqi.owndroid package com.bintianqi.owndroid
import android.content.BroadcastReceiver
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
@@ -160,3 +163,19 @@ fun adaptiveInsets(): WindowInsets {
val navbar = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal) val navbar = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
return WindowInsets.ime.union(navbar).union(WindowInsets.displayCutout) return WindowInsets.ime.union(navbar).union(WindowInsets.displayCutout)
} }
fun registerPackageRemovedReceiver(
ctx: Context, callback: (String) -> Unit
) {
val br = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
callback(intent.data!!.schemeSpecificPart)
}
}
val filter = IntentFilter()
filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED)
filter.addDataScheme("package")
ctx.registerReceiver(br, filter)
}
fun parsePackageNames(input: String) = input.split('\n').filter { it.isNotEmpty() }

File diff suppressed because it is too large Load Diff

View File

@@ -152,7 +152,7 @@ fun NetworkScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
if(VERSION.SDK_INT >= 30) { if(VERSION.SDK_INT >= 30) {
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(NetworkOptions) } FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(NetworkOptions) }
} }
if (VERSION.SDK_INT >= 23 && !privilege.dhizuku) if (!privilege.dhizuku)
FunctionItem(R.string.network_stats, icon = R.drawable.query_stats_fill0) { onNavigate(QueryNetworkStats) } FunctionItem(R.string.network_stats, icon = R.drawable.query_stats_fill0) { onNavigate(QueryNetworkStats) }
if(VERSION.SDK_INT >= 29 && privilege.device) { if(VERSION.SDK_INT >= 29 && privilege.device) {
FunctionItem(R.string.private_dns, icon = R.drawable.dns_fill0) { onNavigate(PrivateDns) } FunctionItem(R.string.private_dns, icon = R.drawable.dns_fill0) { onNavigate(PrivateDns) }
@@ -186,9 +186,9 @@ fun NetworkOptionsScreen(
) { ) {
val privilege by Privilege.status.collectAsStateWithLifecycle() val privilege by Privilege.status.collectAsStateWithLifecycle()
var dialog by rememberSaveable { mutableIntStateOf(0) } var dialog by rememberSaveable { mutableIntStateOf(0) }
var lanEnabled by rememberSaveable { mutableStateOf(getLanEnabled()) }
MyScaffold(R.string.options, onNavigateUp, 0.dp) { MyScaffold(R.string.options, onNavigateUp, 0.dp) {
if(VERSION.SDK_INT >= 30 && (privilege.device || privilege.org)) { if(VERSION.SDK_INT >= 30 && (privilege.device || privilege.org)) {
var lanEnabled by rememberSaveable { mutableStateOf(getLanEnabled()) }
SwitchItem(R.string.lockdown_admin_configured_network, icon = R.drawable.wifi_password_fill0, SwitchItem(R.string.lockdown_admin_configured_network, icon = R.drawable.wifi_password_fill0,
state = lanEnabled, state = lanEnabled,
onCheckedChange = { onCheckedChange = {
@@ -937,7 +937,6 @@ enum class NetworkStatsState(val id: Int, val text: Int) {
Default(NetworkStats.Bucket.STATE_DEFAULT, R.string.default_str), Default(NetworkStats.Bucket.STATE_DEFAULT, R.string.default_str),
Foreground(NetworkStats.Bucket.STATE_FOREGROUND, R.string.foreground) Foreground(NetworkStats.Bucket.STATE_FOREGROUND, R.string.foreground)
} }
@RequiresApi(23)
enum class NetworkStatsUID(val uid: Int, val text: Int) { enum class NetworkStatsUID(val uid: Int, val text: Int) {
All(NetworkStats.Bucket.UID_ALL, R.string.all), All(NetworkStats.Bucket.UID_ALL, R.string.all),
Removed(NetworkStats.Bucket.UID_REMOVED, R.string.uninstalled), Removed(NetworkStats.Bucket.UID_REMOVED, R.string.uninstalled),
@@ -952,7 +951,6 @@ data class QueryNetworkStatsParams(
@Serializable object QueryNetworkStats @Serializable object QueryNetworkStats
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@RequiresApi(23)
@Composable @Composable
fun NetworkStatsScreen( fun NetworkStatsScreen(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, getUid: (String) -> Int, chosenPackage: Channel<String>, onChoosePackage: () -> Unit, getUid: (String) -> Int,
@@ -1270,7 +1268,6 @@ data class NetworkStatsData(
@Serializable object NetworkStatsViewer @Serializable object NetworkStatsViewer
@RequiresApi(23)
@Composable @Composable
fun NetworkStatsViewerScreen( fun NetworkStatsViewerScreen(
data: List<NetworkStatsData>, clearData: () -> Unit, onNavigateUp: () -> Unit data: List<NetworkStatsData>, clearData: () -> Unit, onNavigateUp: () -> Unit

View File

@@ -97,8 +97,10 @@ fun PasswordScreen(vm: MyViewModel,onNavigateUp: () -> Unit, onNavigate: (Any) -
if(privilege.device) { if(privilege.device) {
FunctionItem(R.string.max_time_to_lock, icon = R.drawable.schedule_fill0) { dialog = 1 } FunctionItem(R.string.max_time_to_lock, icon = R.drawable.schedule_fill0) { dialog = 1 }
FunctionItem(R.string.pwd_expiration_timeout, icon = R.drawable.lock_clock_fill0) { dialog = 3 } FunctionItem(R.string.pwd_expiration_timeout, icon = R.drawable.lock_clock_fill0) { dialog = 3 }
if (SP.displayDangerousFeatures) {
FunctionItem(R.string.max_pwd_fail, icon = R.drawable.no_encryption_fill0) { dialog = 4 } FunctionItem(R.string.max_pwd_fail, icon = R.drawable.no_encryption_fill0) { dialog = 4 }
} }
}
if(VERSION.SDK_INT >= 26) { if(VERSION.SDK_INT >= 26) {
FunctionItem(R.string.required_strong_auth_timeout, icon = R.drawable.fingerprint_off_fill0) { dialog = 2 } FunctionItem(R.string.required_strong_auth_timeout, icon = R.drawable.fingerprint_off_fill0) { dialog = 2 }
} }
@@ -333,12 +335,10 @@ fun ResetPasswordScreen(resetPassword: (String, String, Int) -> Boolean, onNavig
visualTransformation = PasswordVisualTransformation() visualTransformation = PasswordVisualTransformation()
) )
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT >= 23) {
CheckBoxItem( CheckBoxItem(
R.string.do_not_ask_credentials_on_boot, R.string.do_not_ask_credentials_on_boot,
flags and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0 flags and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0
) { flags = flags xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT } ) { flags = flags xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT }
}
CheckBoxItem( CheckBoxItem(
R.string.reset_password_require_entry, R.string.reset_password_require_entry,
flags and RESET_PASSWORD_REQUIRE_ENTRY != 0 flags and RESET_PASSWORD_REQUIRE_ENTRY != 0

View File

@@ -72,7 +72,6 @@ import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TimePicker import androidx.compose.material3.TimePicker
@@ -109,9 +108,9 @@ import com.bintianqi.owndroid.MyViewModel
import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.Privilege
import com.bintianqi.owndroid.R import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.SP import com.bintianqi.owndroid.SP
import com.bintianqi.owndroid.adaptiveInsets
import com.bintianqi.owndroid.clickableTextField import com.bintianqi.owndroid.clickableTextField
import com.bintianqi.owndroid.formatDate import com.bintianqi.owndroid.formatDate
import com.bintianqi.owndroid.adaptiveInsets
import com.bintianqi.owndroid.popToast import com.bintianqi.owndroid.popToast
import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.CheckBoxItem
@@ -172,16 +171,14 @@ fun SystemManagerScreen(
FunctionItem(R.string.key_pairs, icon = R.drawable.key_vertical_fill0) { navCtrl.navigate("KeyPairs") }*/ FunctionItem(R.string.key_pairs, icon = R.drawable.key_vertical_fill0) { navCtrl.navigate("KeyPairs") }*/
if(VERSION.SDK_INT >= 35 && (privilege.device || (privilege.profile && privilege.affiliated))) if(VERSION.SDK_INT >= 35 && (privilege.device || (privilege.profile && privilege.affiliated)))
FunctionItem(R.string.content_protection_policy, icon = R.drawable.search_fill0) { onNavigate(ContentProtectionPolicy) } FunctionItem(R.string.content_protection_policy, icon = R.drawable.search_fill0) { onNavigate(ContentProtectionPolicy) }
if(VERSION.SDK_INT >= 23) {
FunctionItem(R.string.permission_policy, icon = R.drawable.key_fill0) { onNavigate(PermissionPolicy) } FunctionItem(R.string.permission_policy, icon = R.drawable.key_fill0) { onNavigate(PermissionPolicy) }
}
if(VERSION.SDK_INT >= 34 && privilege.device) { if(VERSION.SDK_INT >= 34 && privilege.device) {
FunctionItem(R.string.mte_policy, icon = R.drawable.memory_fill0) { onNavigate(MtePolicy) } FunctionItem(R.string.mte_policy, icon = R.drawable.memory_fill0) { onNavigate(MtePolicy) }
} }
if(VERSION.SDK_INT >= 31) { if(VERSION.SDK_INT >= 31) {
FunctionItem(R.string.nearby_streaming_policy, icon = R.drawable.share_fill0) { onNavigate(NearbyStreamingPolicy) } FunctionItem(R.string.nearby_streaming_policy, icon = R.drawable.share_fill0) { onNavigate(NearbyStreamingPolicy) }
} }
if (VERSION.SDK_INT >= 28 && privilege.device && !privilege.dhizuku) { if (VERSION.SDK_INT >= 28 && privilege.device) {
FunctionItem(R.string.lock_task_mode, icon = R.drawable.lock_fill0) { onNavigate(LockTaskMode) } FunctionItem(R.string.lock_task_mode, icon = R.drawable.lock_fill0) { onNavigate(LockTaskMode) }
} }
FunctionItem(R.string.ca_cert, icon = R.drawable.license_fill0) { onNavigate(CaCert) } FunctionItem(R.string.ca_cert, icon = R.drawable.license_fill0) { onNavigate(CaCert) }
@@ -205,7 +202,7 @@ fun SystemManagerScreen(
FunctionItem(R.string.support_messages, icon = R.drawable.chat_fill0) { onNavigate(SupportMessage) } FunctionItem(R.string.support_messages, icon = R.drawable.chat_fill0) { onNavigate(SupportMessage) }
} }
FunctionItem(R.string.disable_account_management, icon = R.drawable.account_circle_fill0) { onNavigate(DisableAccountManagement) } FunctionItem(R.string.disable_account_management, icon = R.drawable.account_circle_fill0) { onNavigate(DisableAccountManagement) }
if(VERSION.SDK_INT >= 23 && (privilege.device || privilege.org)) { if (privilege.device || privilege.org) {
FunctionItem(R.string.system_update_policy, icon = R.drawable.system_update_fill0) { onNavigate(SetSystemUpdatePolicy) } FunctionItem(R.string.system_update_policy, icon = R.drawable.system_update_fill0) { onNavigate(SetSystemUpdatePolicy) }
} }
if(VERSION.SDK_INT >= 29 && (privilege.device || privilege.org)) { if(VERSION.SDK_INT >= 29 && (privilege.device || privilege.org)) {
@@ -369,7 +366,7 @@ fun SystemOptionsScreen(vm: MyViewModel, onNavigateUp: () -> Unit) {
SwitchItem(R.string.enable_usb_signal, status.usbSignalEnabled, SwitchItem(R.string.enable_usb_signal, status.usbSignalEnabled,
vm::setUsbSignalEnabled, R.drawable.usb_fill0) vm::setUsbSignalEnabled, R.drawable.usb_fill0)
} }
if (VERSION.SDK_INT >= 23 && VERSION.SDK_INT < 34) { if (VERSION.SDK_INT < 34) {
Row( Row(
Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding), Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -414,8 +411,8 @@ fun KeyguardScreen(
val context = LocalContext.current val context = LocalContext.current
val privilege by Privilege.status.collectAsStateWithLifecycle() val privilege by Privilege.status.collectAsStateWithLifecycle()
MyScaffold(R.string.keyguard, onNavigateUp) { MyScaffold(R.string.keyguard, onNavigateUp) {
if (VERSION.SDK_INT >= 23 && (privilege.device || if (privilege.device ||
(VERSION.SDK_INT >= 28 && privilege.profile && privilege.affiliated))) { (VERSION.SDK_INT >= 28 && privilege.profile && privilege.affiliated)) {
Row( Row(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
@@ -436,7 +433,7 @@ fun KeyguardScreen(
Notes(R.string.info_disable_keyguard) Notes(R.string.info_disable_keyguard)
Spacer(Modifier.padding(vertical = 12.dp)) Spacer(Modifier.padding(vertical = 12.dp))
} }
if(VERSION.SDK_INT >= 23) Text(text = stringResource(R.string.lock_now), style = typography.headlineLarge) Text(text = stringResource(R.string.lock_now), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 2.dp)) Spacer(Modifier.padding(vertical = 2.dp))
var evictKey by rememberSaveable { mutableStateOf(false) } var evictKey by rememberSaveable { mutableStateOf(false) }
Button( Button(
@@ -1008,7 +1005,6 @@ fun ContentProtectionPolicyScreen(
@Serializable object PermissionPolicy @Serializable object PermissionPolicy
@RequiresApi(23)
@Composable @Composable
fun PermissionPolicyScreen( fun PermissionPolicyScreen(
getPolicy: () -> Int, setPolicy: (Int) -> Unit, onNavigateUp: () -> Unit getPolicy: () -> Int, setPolicy: (Int) -> Unit, onNavigateUp: () -> Unit
@@ -1148,9 +1144,10 @@ fun NearbyStreamingPolicyScreen(
@RequiresApi(28) @RequiresApi(28)
@Composable @Composable
fun LockTaskModeScreen( fun LockTaskModeScreen(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, chosenPackage: Channel<String>, chooseSinglePackage: () -> Unit, choosePackage: () -> Unit,
lockTaskPackages: StateFlow<List<AppInfo>>, getLockTaskPackages: () -> Unit, lockTaskPackages: StateFlow<List<AppInfo>>, getLockTaskPackages: () -> Unit,
setLockTaskPackage: (String, Boolean) -> Unit, startLockTaskMode: (String, String) -> Boolean, setLockTaskPackage: (String, Boolean) -> Unit,
startLockTaskMode: (String, String, Boolean, Boolean) -> Boolean,
getLockTaskFeatures: () -> Int, setLockTaskFeature: (Int) -> String?, onNavigateUp: () -> Unit getLockTaskFeatures: () -> Int, setLockTaskFeature: (Int) -> String?, onNavigateUp: () -> Unit
) { ) {
val coroutine = rememberCoroutineScope() val coroutine = rememberCoroutineScope()
@@ -1175,7 +1172,7 @@ fun LockTaskModeScreen(
.fillMaxSize() .fillMaxSize()
.padding(paddingValues) .padding(paddingValues)
) { ) {
TabRow(tabIndex) { PrimaryTabRow(tabIndex) {
Tab( Tab(
tabIndex == 0, onClick = { coroutine.launch { pagerState.animateScrollToPage(0) } }, tabIndex == 0, onClick = { coroutine.launch { pagerState.animateScrollToPage(0) } },
text = { Text(stringResource(R.string.start)) } text = { Text(stringResource(R.string.start)) }
@@ -1191,9 +1188,9 @@ fun LockTaskModeScreen(
} }
HorizontalPager(pagerState, verticalAlignment = Alignment.Top) { page -> HorizontalPager(pagerState, verticalAlignment = Alignment.Top) { page ->
if(page == 0) { if(page == 0) {
StartLockTaskMode(startLockTaskMode, chosenPackage, onChoosePackage) StartLockTaskMode(startLockTaskMode, chosenPackage, chooseSinglePackage)
} else if (page == 1) { } else if (page == 1) {
LockTaskPackages(chosenPackage, onChoosePackage, lockTaskPackages, setLockTaskPackage) LockTaskPackages(chosenPackage, choosePackage, lockTaskPackages, setLockTaskPackage)
} else { } else {
LockTaskFeatures(getLockTaskFeatures, setLockTaskFeature) LockTaskFeatures(getLockTaskFeatures, setLockTaskFeature)
} }
@@ -1205,29 +1202,39 @@ fun LockTaskModeScreen(
@RequiresApi(28) @RequiresApi(28)
@Composable @Composable
private fun StartLockTaskMode( private fun StartLockTaskMode(
startLockTaskMode: (String, String) -> Boolean, startLockTaskMode: (String, String, Boolean, Boolean) -> Boolean,
chosenPackage: Channel<String>, onChoosePackage: () -> Unit chosenPackage: Channel<String>, onChoosePackage: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
val privilege by Privilege.status.collectAsStateWithLifecycle()
var packageName by rememberSaveable { mutableStateOf("") } var packageName by rememberSaveable { mutableStateOf("") }
var activity by rememberSaveable { mutableStateOf("") } var activity by rememberSaveable { mutableStateOf("") }
var specifyActivity by rememberSaveable { mutableStateOf(false) } var specifyActivity by rememberSaveable { mutableStateOf(false) }
var clearTask by rememberSaveable { mutableStateOf(true) }
var showNotification by rememberSaveable { mutableStateOf(true) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
packageName = chosenPackage.receive() packageName = chosenPackage.receive()
} }
Column( Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = HorizontalPadding)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
Spacer(Modifier.height(5.dp)) PackageNameTextField(
PackageNameTextField(packageName, onChoosePackage) { packageName = it } packageName, onChoosePackage, Modifier.padding(HorizontalPadding, 8.dp)
) { packageName = it }
FullWidthCheckBoxItem(
R.string.lock_task_mode_start_clear_task, clearTask
) { clearTask = it }
FullWidthCheckBoxItem(
R.string.lock_task_mode_show_notification, showNotification
) { showNotification = it }
Row( Row(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically .padding(start = 4.dp, top = 4.dp, end = HorizontalPadding, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
) { ) {
Checkbox(specifyActivity, { Checkbox(specifyActivity, {
specifyActivity = it specifyActivity = it
@@ -1246,16 +1253,17 @@ private fun StartLockTaskMode(
Button( Button(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 5.dp), .padding(horizontal = HorizontalPadding),
onClick = { onClick = {
val result = startLockTaskMode(packageName, activity) val result = startLockTaskMode(packageName, activity, clearTask, showNotification)
if (!result) context.showOperationResultToast(false) if (!result) context.showOperationResultToast(false)
}, },
enabled = packageName.isNotBlank() && (!specifyActivity || activity.isNotBlank()) enabled = packageName.isNotBlank() && (!specifyActivity || activity.isNotBlank())
) { ) {
Text(stringResource(R.string.start)) Text(stringResource(R.string.start))
} }
Notes(R.string.info_start_lock_task_mode) Spacer(Modifier.height(5.dp))
if (!privilege.dhizuku) Notes(R.string.info_start_lock_task_mode)
} }
} }
@@ -1774,7 +1782,7 @@ fun WipeDataScreen(
FullWidthCheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) { FullWidthCheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) {
flag = flag xor WIPE_EXTERNAL_STORAGE flag = flag xor WIPE_EXTERNAL_STORAGE
} }
if(VERSION.SDK_INT >= 22 && privilege.device) FullWidthCheckBoxItem( if (privilege.device) FullWidthCheckBoxItem(
R.string.wipe_reset_protection_data, flag and WIPE_RESET_PROTECTION_DATA != 0) { R.string.wipe_reset_protection_data, flag and WIPE_RESET_PROTECTION_DATA != 0) {
flag = flag xor WIPE_RESET_PROTECTION_DATA flag = flag xor WIPE_RESET_PROTECTION_DATA
} }
@@ -1828,7 +1836,7 @@ fun WipeDataScreen(
text = { text = {
Text( Text(
text = stringResource( text = stringResource(
if(VERSION.SDK_INT >= 23 && userManager.isSystemUser) R.string.wipe_data_warning if (userManager.isSystemUser) R.string.wipe_data_warning
else R.string.info_wipe_data_in_managed_user else R.string.info_wipe_data_in_managed_user
), ),
color = colorScheme.error color = colorScheme.error
@@ -1869,7 +1877,6 @@ data class PendingSystemUpdateInfo(val exists: Boolean, val time: Long, val secu
@Serializable object SetSystemUpdatePolicy @Serializable object SetSystemUpdatePolicy
@RequiresApi(23)
@Composable @Composable
fun SystemUpdatePolicyScreen( fun SystemUpdatePolicyScreen(
getPolicy: () -> SystemUpdatePolicyInfo, setPolicy: (SystemUpdatePolicyInfo) -> Unit, getPolicy: () -> SystemUpdatePolicyInfo, setPolicy: (SystemUpdatePolicyInfo) -> Unit,

View File

@@ -30,12 +30,12 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SegmentedButtonDefaults
@@ -105,7 +105,6 @@ fun UsersScreen(vm: MyViewModel, onNavigateUp: () -> Unit, onNavigate: (Any) ->
FunctionItem(R.string.create_user, icon = R.drawable.person_add_fill0) { onNavigate(CreateUser) } FunctionItem(R.string.create_user, icon = R.drawable.person_add_fill0) { onNavigate(CreateUser) }
} }
FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) } FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) }
if(VERSION.SDK_INT >= 23) {
var changeUserIconDialog by remember { mutableStateOf(false) } var changeUserIconDialog by remember { mutableStateOf(false) }
var bitmap: Bitmap? by remember { mutableStateOf(null) } var bitmap: Bitmap? by remember { mutableStateOf(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
@@ -123,7 +122,6 @@ fun UsersScreen(vm: MyViewModel, onNavigateUp: () -> Unit, onNavigate: (Any) ->
vm.setUserIcon(bitmap!!) vm.setUserIcon(bitmap!!)
changeUserIconDialog = false changeUserIconDialog = false
}) { changeUserIconDialog = false } }) { changeUserIconDialog = false }
}
if(VERSION.SDK_INT >= 28 && privilege.device) { if(VERSION.SDK_INT >= 28 && privilege.device) {
FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) } FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) }
} }
@@ -191,7 +189,7 @@ fun UserInfoScreen(getInfo: () -> UserInformation, onNavigateUp: () -> Unit) {
if (VERSION.SDK_INT >= 24) InfoItem(R.string.support_multiuser, info.multiUser.yesOrNo) 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 } if (VERSION.SDK_INT >= 31) InfoItem(R.string.headless_system_user_mode, info.headless.yesOrNo, true) { infoDialog = 1 }
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
if (VERSION.SDK_INT >= 23) InfoItem(R.string.system_user, info.system.yesOrNo) 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 >= 34) InfoItem(R.string.admin_user, info.admin.yesOrNo)
if (VERSION.SDK_INT >= 25) InfoItem(R.string.demo_user, info.demo.yesOrNo) if (VERSION.SDK_INT >= 25) InfoItem(R.string.demo_user, info.demo.yesOrNo)
if (info.time != 0L) InfoItem(R.string.creation_time, formatDate(info.time)) if (info.time != 0L) InfoItem(R.string.creation_time, formatDate(info.time))
@@ -264,7 +262,7 @@ fun UserOperationScreen(
input, { input = it }, input, { input = it },
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.menuAnchor(MenuAnchorType.PrimaryEditable) .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable)
.padding(top = 4.dp, bottom = 8.dp), .padding(top = 4.dp, bottom = 8.dp),
label = { label = {
Text(stringResource(if(useUserId) R.string.user_id else R.string.serial_number)) Text(stringResource(if(useUserId) R.string.user_id else R.string.serial_number))
@@ -586,7 +584,6 @@ fun UserSessionMessageScreen(
} }
} }
@RequiresApi(23)
@Composable @Composable
private fun ChangeUserIconDialog(bitmap: Bitmap, onSet: () -> Unit, onClose: () -> Unit) { private fun ChangeUserIconDialog(bitmap: Bitmap, onSet: () -> Unit, onClose: () -> Unit) {
AlertDialog( AlertDialog(

View File

@@ -101,7 +101,6 @@ fun CreateWorkProfileScreen(
var migrateAccountName by remember { mutableStateOf("") } var migrateAccountName by remember { mutableStateOf("") }
var migrateAccountType by remember { mutableStateOf("") } var migrateAccountType by remember { mutableStateOf("") }
var keepAccount by remember { mutableStateOf(true) } var keepAccount by remember { mutableStateOf(true) }
if (VERSION.SDK_INT >= 22) {
FullWidthCheckBoxItem(R.string.migrate_account, migrateAccount) { migrateAccount = it } FullWidthCheckBoxItem(R.string.migrate_account, migrateAccount) { migrateAccount = it }
AnimatedVisibility(migrateAccount) { AnimatedVisibility(migrateAccount) {
val fr = FocusRequester() val fr = FocusRequester()
@@ -128,7 +127,6 @@ fun CreateWorkProfileScreen(
} }
} }
} }
}
if (VERSION.SDK_INT >= 24) FullWidthCheckBoxItem( if (VERSION.SDK_INT >= 24) FullWidthCheckBoxItem(
R.string.skip_encryption, skipEncrypt R.string.skip_encryption, skipEncrypt
) { skipEncrypt = it } ) { skipEncrypt = it }

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M680,600q-17,0 -28.5,-11.5T640,560v-160q0,-17 11.5,-28.5T680,360h120q17,0 28.5,11.5T840,400v40h-60v-20h-80v120h80v-20h60v40q0,17 -11.5,28.5T800,600L680,600ZM380,600v-240h160q17,0 28.5,11.5T580,400v40q0,17 -11.5,28.5T540,480q17,0 28.5,11.5T580,520v40q0,17 -11.5,28.5T540,600L380,600ZM440,450h80v-30h-80v30ZM440,540h80v-30h-80v30ZM120,600v-200q0,-17 11.5,-28.5T160,360h120q17,0 28.5,11.5T320,400v200h-60v-60h-80v60h-60ZM180,480h80v-60h-80v60Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m424,648 l282,-282 -56,-56 -226,226 -114,-114 -56,56 170,170ZM200,840q-33,0 -56.5,-23.5T120,760v-560q0,-33 23.5,-56.5T200,120h560q33,0 56.5,23.5T840,200v560q0,33 -23.5,56.5T760,840L200,840ZM200,760h560v-560L200,200v560ZM200,200v560,-560Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M360,800q-33,0 -56.5,-23.5T280,720q0,-33 23.5,-56.5T360,640q33,0 56.5,23.5T440,720q0,33 -23.5,56.5T360,800ZM600,800q-33,0 -56.5,-23.5T520,720q0,-33 23.5,-56.5T600,640q33,0 56.5,23.5T680,720q0,33 -23.5,56.5T600,800ZM360,560q-33,0 -56.5,-23.5T280,480q0,-33 23.5,-56.5T360,400q33,0 56.5,23.5T440,480q0,33 -23.5,56.5T360,560ZM600,560q-33,0 -56.5,-23.5T520,480q0,-33 23.5,-56.5T600,400q33,0 56.5,23.5T680,480q0,33 -23.5,56.5T600,560ZM360,320q-33,0 -56.5,-23.5T280,240q0,-33 23.5,-56.5T360,160q33,0 56.5,23.5T440,240q0,33 -23.5,56.5T360,320ZM600,320q-33,0 -56.5,-23.5T520,240q0,-33 23.5,-56.5T600,160q33,0 56.5,23.5T680,240q0,33 -23.5,56.5T600,320Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,480ZM202,895l-56,-57 118,-118h-90v-80h226v226h-80v-89L202,895ZM480,880v-80h240v-440L520,360v-200L240,160v400h-80v-400q0,-33 23.5,-56.5T240,80h320l240,240v480q0,33 -23.5,56.5T720,880L480,880Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M240,880q-33,0 -56.5,-23.5T160,800v-640q0,-33 23.5,-56.5T240,80h320l240,240v240h-80v-200L520,360v-200L240,160v640h360v80L240,880ZM878,895L760,777v89h-80v-226h226v80h-90l118,118 -56,57ZM240,800v-640,640Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M220,600v-180h-60v-60h120v240h-60ZM360,600v-100q0,-17 11.5,-28.5T400,460h80v-40L360,420v-60h140q17,0 28.5,11.5T540,400v60q0,17 -11.5,28.5T500,500h-80v40h120v60L360,600ZM600,600v-60h120v-40h-80v-40h80v-40L600,420v-60h140q17,0 28.5,11.5T780,400v160q0,17 -11.5,28.5T740,600L600,600Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,680q83,0 141.5,-58.5T680,480q0,-83 -58.5,-141.5T480,280q-83,0 -141.5,58.5T280,480q0,83 58.5,141.5T480,680ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,800q134,0 227,-93t93,-227q0,-134 -93,-227t-227,-93q-134,0 -227,93t-93,227q0,134 93,227t227,93Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M280,720q-100,0 -170,-70T40,480q0,-100 70,-170t170,-70h400q100,0 170,70t70,170q0,100 -70,170t-170,70L280,720ZM280,640h400q66,0 113,-47t47,-113q0,-66 -47,-113t-113,-47L280,320q-66,0 -113,47t-47,113q0,66 47,113t113,47ZM280,600q50,0 85,-35t35,-85q0,-50 -35,-85t-85,-35q-50,0 -85,35t-35,85q0,50 35,85t85,35ZM480,480Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:tint="#000000"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="24dp">
<path
android:fillColor="@android:color/white"
android:pathData="M480,640Q555,640 607.5,587.5Q660,535 660,460Q660,385 607.5,332.5Q555,280 480,280Q405,280 352.5,332.5Q300,385 300,460Q300,535 352.5,587.5Q405,640 480,640ZM480,568Q435,568 403.5,536.5Q372,505 372,460Q372,415 403.5,383.5Q435,352 480,352Q525,352 556.5,383.5Q588,415 588,460Q588,505 556.5,536.5Q525,568 480,568ZM480,760Q334,760 214,678.5Q94,597 40,460Q94,323 214,241.5Q334,160 480,160Q626,160 746,241.5Q866,323 920,460Q866,597 746,678.5Q626,760 480,760ZM480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460ZM480,680Q593,680 687.5,620.5Q782,561 832,460Q782,359 687.5,299.5Q593,240 480,240Q367,240 272.5,299.5Q178,359 128,460Q178,561 272.5,620.5Q367,680 480,680Z"/>
</vector>

View File

@@ -278,7 +278,6 @@
<string name="network_logging">Сетевой журнал</string> <string name="network_logging">Сетевой журнал</string>
<string name="delete_logs">Удалить журналы</string> <string name="delete_logs">Удалить журналы</string>
<string name="export_logs">Экспортировать журналы</string> <string name="export_logs">Экспортировать журналы</string>
<string name="wifi_auth_keypair">Пара ключей Wi-Fi</string>
<string name="preferential_network_service">Предпочтительная сетевая служба</string> <string name="preferential_network_service">Предпочтительная сетевая служба</string>
<string name="add_config">Add config</string> <!--TODO--> <string name="add_config">Add config</string> <!--TODO-->
<string name="network_id">Идентификатор сети</string> <string name="network_id">Идентификатор сети</string>

View File

@@ -307,7 +307,6 @@
<string name="network_logging">Ağ Kayıtları</string> <string name="network_logging">Ağ Kayıtları</string>
<string name="delete_logs">Kayıtları Sil</string> <string name="delete_logs">Kayıtları Sil</string>
<string name="export_logs">Kayıtları Dışa Aktar</string> <string name="export_logs">Kayıtları Dışa Aktar</string>
<string name="wifi_auth_keypair">Wi-Fi Kimlik Doğrulama Anahtar Çifti</string>
<string name="preferential_network_service">Tercihli Ağ Servisi</string> <string name="preferential_network_service">Tercihli Ağ Servisi</string>
<string name="add_config">Yapılandırma Ekle</string> <string name="add_config">Yapılandırma Ekle</string>
<string name="network_id">Ağ Kimliği</string> <string name="network_id">Ağ Kimliği</string>

View File

@@ -68,6 +68,7 @@
<string name="timeout">超时</string> <string name="timeout">超时</string>
<string name="continue_str">继续</string> <string name="continue_str">继续</string>
<string name="exit">退出</string> <string name="exit">退出</string>
<string name="undo">撤销</string>
<!--Permissions--> <!--Permissions-->
<string name="profile_owner">Profile owner</string> <string name="profile_owner">Profile owner</string>
@@ -173,6 +174,8 @@
<string name="nearby_notification_streaming">附近通知传输</string> <string name="nearby_notification_streaming">附近通知传输</string>
<string name="enable_if_secure_enough">在足够安全时启用</string> <string name="enable_if_secure_enough">在足够安全时启用</string>
<string name="lock_task_mode">锁定任务模式</string> <string name="lock_task_mode">锁定任务模式</string>
<string name="lock_task_mode_start_clear_task">清除任务(新实例)</string>
<string name="lock_task_mode_show_notification">显示通知以退出</string>
<string name="app_not_allowed">应用未被允许</string> <string name="app_not_allowed">应用未被允许</string>
<string name="disable_all">禁用全部</string> <string name="disable_all">禁用全部</string>
<string name="ltf_sys_info">允许状态栏信息</string> <string name="ltf_sys_info">允许状态栏信息</string>
@@ -293,7 +296,6 @@
<string name="network_logs_collected">网络日志已收集</string> <string name="network_logs_collected">网络日志已收集</string>
<string name="delete_logs">删除日志</string> <string name="delete_logs">删除日志</string>
<string name="export_logs">导出日志</string> <string name="export_logs">导出日志</string>
<string name="wifi_auth_keypair">Wi-Fi密钥对</string>
<string name="preferential_network_service">首选网络服务</string> <string name="preferential_network_service">首选网络服务</string>
<string name="add_config">添加配置</string> <string name="add_config">添加配置</string>
<string name="network_id">网络ID</string> <string name="network_id">网络ID</string>
@@ -343,7 +345,9 @@
<string name="show_user_app">显示用户应用</string> <string name="show_user_app">显示用户应用</string>
<string name="show_system_app">显示系统应用</string> <string name="show_system_app">显示系统应用</string>
<string name="suspend">挂起</string> <string name="suspend">挂起</string>
<string name="unsuspend">取消挂起</string>
<string name="hide">隐藏</string> <string name="hide">隐藏</string>
<string name="unhide">取消隐藏</string>
<string name="always_on_vpn">VPN保持打开</string> <string name="always_on_vpn">VPN保持打开</string>
<string name="enable_lockdown">启用锁定</string> <string name="enable_lockdown">启用锁定</string>
<string name="clear_current_config">清除当前配置</string> <string name="clear_current_config">清除当前配置</string>
@@ -362,6 +366,7 @@
<string name="permitted_ime">许可的输入法</string> <string name="permitted_ime">许可的输入法</string>
<string name="keep_uninstalled_packages">卸载后保留的应用</string> <string name="keep_uninstalled_packages">卸载后保留的应用</string>
<string name="clear_app_storage">清除应用存储</string> <string name="clear_app_storage">清除应用存储</string>
<string name="clear_app_storage_confirmation">你确定要删除应用数据吗?</string>
<string name="set_default_dialer">设置默认拨号器</string> <string name="set_default_dialer">设置默认拨号器</string>
<string name="uninstall_app">卸载应用</string> <string name="uninstall_app">卸载应用</string>
<string name="install_app">安装应用</string> <string name="install_app">安装应用</string>
@@ -371,9 +376,14 @@
<string name="search">搜索</string> <string name="search">搜索</string>
<string name="app_group">应用组</string> <string name="app_group">应用组</string>
<string name="manage_app_groups">管理组</string> <string name="manage_app_groups">管理组</string>
<string name="import_str">导入</string>
<string name="edit_app_group">编辑组</string> <string name="edit_app_group">编辑组</string>
<string name="add_to_list">添加到列表</string> <string name="add_to_list">添加到列表</string>
<string name="remove_from_list">从列表中移除</string> <string name="remove_from_list">从列表中移除</string>
<string name="managed_configuration">托管配置</string>
<string name="clear_configurations">清除配置</string>
<string name="specify_value">指定值</string>
<string name="package_removed">移除了 %1$s</string>
<!--UserRestriction--> <!--UserRestriction-->
<string name="user_restriction">用户限制</string> <string name="user_restriction">用户限制</string>

View File

@@ -73,6 +73,7 @@
<string name="timeout">Timeout</string> <string name="timeout">Timeout</string>
<string name="continue_str">Continue</string> <string name="continue_str">Continue</string>
<string name="exit">Exit</string> <string name="exit">Exit</string>
<string name="undo">Undo</string>
<!--Permissions--> <!--Permissions-->
<string name="profile_owner">Profile owner</string> <string name="profile_owner">Profile owner</string>
@@ -102,7 +103,7 @@
<string name="org_name">Organization name</string> <string name="org_name">Organization name</string>
<string name="disable_account_management">Disable account management</string> <string name="disable_account_management">Disable account management</string>
<string name="account_type">Account type</string> <string name="account_type">Account type</string>
<string name="transfer_ownership">Transfer Ownership</string> <string name="transfer_ownership">Transfer ownership</string>
<string name="lock_screen_info">Lock screen info</string> <string name="lock_screen_info">Lock screen info</string>
<string name="support_messages">Support Messages</string> <string name="support_messages">Support Messages</string>
<string name="short_support_msg">Short message</string> <string name="short_support_msg">Short message</string>
@@ -201,6 +202,8 @@
<string name="nearby_notification_streaming">Nearby notification streaming policy</string> <string name="nearby_notification_streaming">Nearby notification streaming policy</string>
<string name="enable_if_secure_enough">Same managed account only</string> <string name="enable_if_secure_enough">Same managed account only</string>
<string name="lock_task_mode">Lock task mode</string> <string name="lock_task_mode">Lock task mode</string>
<string name="lock_task_mode_start_clear_task">Clear task (start fresh)</string>
<string name="lock_task_mode_show_notification">Show a notification to exit</string>
<string name="app_not_allowed">App is not allowed</string> <string name="app_not_allowed">App is not allowed</string>
<string name="disable_all">Disable all</string> <string name="disable_all">Disable all</string>
<!--ltf: lock task feature--> <!--ltf: lock task feature-->
@@ -327,7 +330,6 @@
<string name="network_logs_collected">Network logs collected</string> <string name="network_logs_collected">Network logs collected</string>
<string name="delete_logs">Delete logs</string> <string name="delete_logs">Delete logs</string>
<string name="export_logs">Export logs</string> <string name="export_logs">Export logs</string>
<string name="wifi_auth_keypair">Wi-Fi keypair</string>
<string name="preferential_network_service">Preferential network service</string> <string name="preferential_network_service">Preferential network service</string>
<string name="add_config">Add config</string> <string name="add_config">Add config</string>
<string name="network_id">Network ID</string> <string name="network_id">Network ID</string>
@@ -377,7 +379,9 @@
<string name="show_user_app">Show user apps</string> <string name="show_user_app">Show user apps</string>
<string name="show_system_app">Show system apps</string> <string name="show_system_app">Show system apps</string>
<string name="suspend">Suspend</string> <string name="suspend">Suspend</string>
<string name="unsuspend">Unsuspend</string>
<string name="hide">Hide</string> <string name="hide">Hide</string>
<string name="unhide">Unhide</string>
<string name="always_on_vpn">Always-on VPN</string> <string name="always_on_vpn">Always-on VPN</string>
<string name="enable_lockdown">Enable lockdown</string> <string name="enable_lockdown">Enable lockdown</string>
<string name="clear_current_config">Clear current config</string> <string name="clear_current_config">Clear current config</string>
@@ -397,6 +401,7 @@
<string name="permitted_ime">Permitted IME</string> <string name="permitted_ime">Permitted IME</string>
<string name="keep_uninstalled_packages">Keep uninstalled packages</string> <string name="keep_uninstalled_packages">Keep uninstalled packages</string>
<string name="clear_app_storage">Clear app storage</string> <string name="clear_app_storage">Clear app storage</string>
<string name="clear_app_storage_confirmation">Are you sure to delete app data?</string>
<string name="set_default_dialer">Set default dialer</string> <string name="set_default_dialer">Set default dialer</string>
<string name="uninstall_app">Uninstall app</string> <string name="uninstall_app">Uninstall app</string>
<string name="install_app">Install app</string> <string name="install_app">Install app</string>
@@ -405,9 +410,14 @@
<string name="search">Search</string> <string name="search">Search</string>
<string name="app_group">App group</string> <string name="app_group">App group</string>
<string name="manage_app_groups">Manage groups</string> <string name="manage_app_groups">Manage groups</string>
<string name="import_str">Import</string>
<string name="edit_app_group">Edit group</string> <string name="edit_app_group">Edit group</string>
<string name="add_to_list">Add to list</string> <string name="add_to_list">Add to list</string>
<string name="remove_from_list">Remove from list</string> <string name="remove_from_list">Remove from list</string>
<string name="managed_configuration">Managed configuration</string>
<string name="clear_configurations">Clear configurations</string>
<string name="specify_value">Specify value</string>
<string name="package_removed">Removed %1$s</string>
<!--UserRestriction--> <!--UserRestriction-->
<string name="user_restriction">User restriction</string> <string name="user_restriction">User restriction</string>

View File

@@ -1,9 +1,9 @@
[versions] [versions]
agp = "8.13.1" agp = "8.13.2"
kotlin = "2.2.21" kotlin = "2.3.0"
navigation-compose = "2.9.6" navigation-compose = "2.9.6"
composeBom = "2025.11.00" composeBom = "2025.12.01"
accompanist-drawablepainter = "0.37.3" accompanist-drawablepainter = "0.37.3"
accompanist-permissions = "0.37.3" accompanist-permissions = "0.37.3"
shizuku = "13.1.5" shizuku = "13.1.5"
@@ -12,6 +12,7 @@ dhizuku = "2.5.4"
dhizuku-server = "0.0.10" dhizuku-server = "0.0.10"
hiddenApiBypass = "6.1" hiddenApiBypass = "6.1"
libsu = "6.0.0" libsu = "6.0.0"
reoderable = "3.0.0"
serialization = "1.9.0" serialization = "1.9.0"
[libraries] [libraries]
@@ -33,6 +34,7 @@ dhizuku-api = { module = "io.github.iamr0s:Dhizuku-API", version.ref = "dhizuku"
dhizuku-server-api = { group = "io.github.iamr0s", name = "Dhizuku-SERVER_API", version.ref = "dhizuku-server" } dhizuku-server-api = { group = "io.github.iamr0s", name = "Dhizuku-SERVER_API", version.ref = "dhizuku-server" }
hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" } hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" }
libsu = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } libsu = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
reoderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reoderable" }
serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
@@ -40,4 +42,4 @@ serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version = "2.2.21" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version = "2.3.0" }