mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 02:56:01 +00:00
Merge branch 'dev'
This commit is contained in:
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -72,12 +72,6 @@ jobs:
|
||||
with:
|
||||
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
|
||||
env:
|
||||
COMMIT_MESSAGE: |+
|
||||
@@ -91,8 +85,7 @@ jobs:
|
||||
mv ./$RELEASE_TEST_PWD/app-release.apk ./$RELEASE_TEST_PWD.apk && rm -rf ./$RELEASE_TEST_PWD
|
||||
export RELEASE_SIGNED_PWD=$(find . -name "*release-signed*")
|
||||
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 }}
|
||||
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 releaseSigned="@$RELEASE_SIGNED_PWD.apk"
|
||||
|
||||
@@ -57,7 +57,7 @@ java.lang.IllegalStateException: Not allowed to set the device owner because the
|
||||
> [!NOTE]
|
||||
> 一些系统有应用克隆、儿童空间等功能,它们通常是用户。
|
||||
|
||||
#### Device owner 已存在
|
||||
### Device owner 已存在
|
||||
|
||||
```text
|
||||
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)
|
||||
|
||||
> 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.
|
||||
>
|
||||
|
||||
@@ -57,7 +57,7 @@ Solutions:
|
||||
> [!NOTE]
|
||||
> 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
|
||||
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)
|
||||
|
||||
> 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.
|
||||
>
|
||||
|
||||
@@ -24,10 +24,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.bintianqi.owndroid"
|
||||
minSdk = 21
|
||||
minSdk = 23
|
||||
targetSdk = 36
|
||||
versionCode = 41
|
||||
versionName = "7.2"
|
||||
versionCode = 42
|
||||
versionName = "7.3"
|
||||
multiDexEnabled = false
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ dependencies {
|
||||
implementation(libs.androidx.fragment)
|
||||
implementation(libs.hiddenApiBypass)
|
||||
implementation(libs.libsu)
|
||||
implementation(libs.reoderable)
|
||||
implementation(libs.serialization)
|
||||
implementation(kotlin("reflect"))
|
||||
}
|
||||
@@ -12,6 +12,8 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_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.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-feature android:name="android.software.device_admin"/>
|
||||
<application
|
||||
@@ -81,7 +83,7 @@
|
||||
android:name=".Receiver"
|
||||
android:description="@string/app_name"
|
||||
android:permission="android.permission.BIND_DEVICE_ADMIN"
|
||||
android:exported="true">
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.app.device_admin"
|
||||
android:resource="@xml/device_admin"/>
|
||||
@@ -99,6 +101,12 @@
|
||||
android:name=".ApiReceiver"
|
||||
android:exported="true">
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".LockTaskService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse" />
|
||||
|
||||
<provider
|
||||
android:name="rikka.shizuku.ShizukuProvider"
|
||||
android:authorities="${applicationId}.shizuku"
|
||||
|
||||
@@ -16,6 +16,7 @@ class AppInstallerActivity:FragmentActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
val vm by viewModels<AppInstallerViewModel>()
|
||||
vm.initialize(intent)
|
||||
vm.registerInstallerReceiver(this)
|
||||
val theme = ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme)
|
||||
setContent {
|
||||
OwnDroidTheme(theme) {
|
||||
|
||||
@@ -36,13 +36,19 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
|
||||
intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let { list += it }
|
||||
intent.getParcelableArrayExtra(Intent.EXTRA_STREAM)?.forEach { list += it as Uri }
|
||||
intent.clipData?.let { clipData ->
|
||||
for(i in 0..clipData.itemCount - 1) {
|
||||
for(i in 0..<clipData.itemCount) {
|
||||
list += clipData.getItemAt(i).uri
|
||||
}
|
||||
}
|
||||
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>) {
|
||||
uiState.update {
|
||||
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) }
|
||||
return
|
||||
}
|
||||
ContextCompat.registerReceiver(
|
||||
application, Receiver(), IntentFilter(ACTION), null,
|
||||
null, ContextCompat.RECEIVER_EXPORTED
|
||||
)
|
||||
val intent = Intent(ACTION).setPackage(application.packageName)
|
||||
val pi = if(Build.VERSION.SDK_INT >= 34) {
|
||||
PendingIntent.getBroadcast(
|
||||
application, sessionId, Intent(ACTION),
|
||||
application, sessionId, intent,
|
||||
PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE
|
||||
).intentSender
|
||||
} else {
|
||||
PendingIntent.getBroadcast(application, sessionId, Intent(ACTION), PendingIntent.FLAG_MUTABLE).intentSender
|
||||
PendingIntent.getBroadcast(application, sessionId, intent, PendingIntent.FLAG_MUTABLE).intentSender
|
||||
}
|
||||
session.commit(pi)
|
||||
}
|
||||
@@ -119,7 +122,6 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
|
||||
)
|
||||
} else {
|
||||
uiState.update { it.copy(result = intent) }
|
||||
context.unregisterReceiver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.bintianqi.owndroid
|
||||
import android.content.Context
|
||||
import android.hardware.biometrics.BiometricPrompt
|
||||
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.Build
|
||||
import android.os.CancellationSignal
|
||||
import androidx.activity.compose.BackHandler
|
||||
@@ -19,6 +20,7 @@ import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
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.text.input.ImeAction
|
||||
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.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
@@ -50,6 +54,7 @@ fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismi
|
||||
val fr = remember { FocusRequester() }
|
||||
var input by rememberSaveable { mutableStateOf("") }
|
||||
var isError by rememberSaveable { mutableStateOf(false) }
|
||||
var showPassword by remember { mutableStateOf(false) }
|
||||
fun unlock() {
|
||||
if(input.hash() == SP.lockPasswordHash) {
|
||||
fm.clearFocus()
|
||||
@@ -75,7 +80,18 @@ fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismi
|
||||
keyboardOptions = KeyboardOptions(
|
||||
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) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -92,6 +108,7 @@ fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismi
|
||||
|
||||
@RequiresApi(28)
|
||||
fun startBiometricsUnlock(context: Context, onSucceed: () -> Unit) {
|
||||
context.getSystemService(FingerprintManager::class.java) ?: return
|
||||
val callback = object : AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
|
||||
84
app/src/main/java/com/bintianqi/owndroid/LockTaskService.kt
Normal file
84
app/src/main/java/com/bintianqi/owndroid/LockTaskService.kt
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -137,6 +137,8 @@ import com.bintianqi.owndroid.dpm.LockTaskMode
|
||||
import com.bintianqi.owndroid.dpm.LockTaskModeScreen
|
||||
import com.bintianqi.owndroid.dpm.ManageAppGroups
|
||||
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.MtePolicyScreen
|
||||
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.OverrideApnScreen
|
||||
import com.bintianqi.owndroid.dpm.PackageFunctionScreen
|
||||
import com.bintianqi.owndroid.dpm.PackageFunctionScreenWithoutResult
|
||||
import com.bintianqi.owndroid.dpm.Password
|
||||
import com.bintianqi.owndroid.dpm.PasswordInfo
|
||||
import com.bintianqi.owndroid.dpm.PasswordInfoScreen
|
||||
@@ -253,6 +254,9 @@ class MainActivity : FragmentActivity() {
|
||||
val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
|
||||
launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
registerPackageRemovedReceiver(this) {
|
||||
vm.onPackageRemoved(it)
|
||||
}
|
||||
setContent {
|
||||
var appLockDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val theme by vm.theme.collectAsStateWithLifecycle()
|
||||
@@ -265,10 +269,6 @@ class MainActivity : FragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ExperimentalMaterial3Api
|
||||
@@ -285,7 +285,10 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
}
|
||||
}
|
||||
fun choosePackage() {
|
||||
navController.navigate(ApplicationsList(false))
|
||||
navController.navigate(ApplicationsList(false, true))
|
||||
}
|
||||
fun chooseSinglePackage() {
|
||||
navController.navigate(ApplicationsList(false, false))
|
||||
}
|
||||
fun navigateToAppGroups() {
|
||||
navController.navigate(ManageAppGroups)
|
||||
@@ -331,7 +334,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
DelegatedAdminsScreen(vm.delegatedAdmins, vm::getDelegatedAdmins, ::navigateUp, ::navigate)
|
||||
}
|
||||
composable<AddDelegatedAdmin>{
|
||||
AddDelegatedAdminScreen(vm.chosenPackage, ::choosePackage, it.toRoute(),
|
||||
AddDelegatedAdminScreen(vm.chosenPackage, ::chooseSinglePackage, it.toRoute(),
|
||||
vm::setDelegatedAdmin, ::navigateUp)
|
||||
}
|
||||
composable<DeviceInfo> { DeviceInfoScreen(vm, ::navigateUp) }
|
||||
@@ -385,9 +388,11 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
vm::getNsNotificationPolicy, vm::setNsNotificationPolicy, ::navigateUp)
|
||||
}
|
||||
composable<LockTaskMode> {
|
||||
LockTaskModeScreen(vm.chosenPackage, ::choosePackage, vm.lockTaskPackages,
|
||||
LockTaskModeScreen(
|
||||
vm.chosenPackage, ::chooseSinglePackage, ::choosePackage, vm.lockTaskPackages,
|
||||
vm::getLockTaskPackages, vm::setLockTaskPackage, vm::startLockTaskMode,
|
||||
vm:: getLockTaskFeatures, vm::setLockTaskFeatures, ::navigateUp)
|
||||
vm:: getLockTaskFeatures, vm::setLockTaskFeatures, ::navigateUp
|
||||
)
|
||||
}
|
||||
composable<CaCert> {
|
||||
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)
|
||||
}
|
||||
composable<QueryNetworkStats> {
|
||||
NetworkStatsScreen(vm.chosenPackage, ::choosePackage, vm::getPackageUid,
|
||||
NetworkStatsScreen(vm.chosenPackage, ::chooseSinglePackage, vm::getPackageUid,
|
||||
vm::queryNetworkStats, ::navigateUp) { navController.navigate(NetworkStatsViewer) }
|
||||
}
|
||||
composable<NetworkStatsViewer> {
|
||||
@@ -446,7 +451,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
}
|
||||
composable<AlwaysOnVpnPackage> {
|
||||
AlwaysOnVpnPackageScreen(vm::getAlwaysOnVpnPackage, vm::getAlwaysOnVpnLockdown,
|
||||
vm::setAlwaysOnVpn, vm.chosenPackage, ::choosePackage, ::navigateUp)
|
||||
vm::setAlwaysOnVpn, vm.chosenPackage, ::chooseSinglePackage, ::navigateUp)
|
||||
}
|
||||
composable<RecommendedGlobalProxy> {
|
||||
RecommendedGlobalProxyScreen(vm::setRecommendedGlobalProxy, ::navigateUp)
|
||||
@@ -494,10 +499,10 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
composable<DeleteWorkProfile> { DeleteWorkProfileScreen(vm::wipeData, ::navigateUp) }
|
||||
|
||||
composable<ApplicationsList> {
|
||||
val canSwitchView = (it.toRoute() as ApplicationsList).canSwitchView
|
||||
val params = it.toRoute<ApplicationsList>()
|
||||
AppChooserScreen(
|
||||
canSwitchView, vm.installedPackages, vm.refreshPackagesProgress, { name ->
|
||||
if (canSwitchView) {
|
||||
params, vm.installedPackages, vm.refreshPackagesProgress, { name ->
|
||||
if (params.canSwitchView) {
|
||||
if (name == null) {
|
||||
navigateUp()
|
||||
} else {
|
||||
@@ -512,12 +517,12 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
navController.navigate(ApplicationsFeatures) {
|
||||
popUpTo(Home)
|
||||
}
|
||||
}, vm::refreshPackageList)
|
||||
}, vm::refreshPackageList, vm::setPackageSuspended, vm::setPackageHidden)
|
||||
}
|
||||
composable<ApplicationsFeatures> {
|
||||
ApplicationsFeaturesScreen(::navigateUp, ::navigate) {
|
||||
SP.applicationsListView = true
|
||||
navController.navigate(ApplicationsList(true)) {
|
||||
navController.navigate(ApplicationsList(true, true)) {
|
||||
popUpTo(Home)
|
||||
}
|
||||
}
|
||||
@@ -526,52 +531,72 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
ApplicationDetailsScreen(it.toRoute(), vm, ::navigateUp, ::navigate)
|
||||
}
|
||||
composable<Suspend> {
|
||||
PackageFunctionScreen(R.string.suspend, vm.suspendedPackages, vm::getSuspendedPackaged,
|
||||
PackageFunctionScreen(
|
||||
R.string.suspend, vm.suspendedPackages, vm::getSuspendedPackaged,
|
||||
vm::setPackageSuspended, ::navigateUp, vm.chosenPackage, ::choosePackage,
|
||||
::navigateToAppGroups, vm.appGroups, R.string.info_suspend_app)
|
||||
::navigateToAppGroups, vm.appGroups, R.string.info_suspend_app
|
||||
)
|
||||
}
|
||||
composable<Hide> {
|
||||
PackageFunctionScreen(R.string.hide, vm.hiddenPackages, vm::getHiddenPackages,
|
||||
vm::setPackageHidden, ::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups)
|
||||
PackageFunctionScreen(
|
||||
R.string.hide, vm.hiddenPackages, vm::getHiddenPackages, vm::setPackageHidden,
|
||||
::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups
|
||||
)
|
||||
}
|
||||
composable<BlockUninstall> {
|
||||
PackageFunctionScreenWithoutResult(R.string.block_uninstall, vm.ubPackages,
|
||||
vm::getUbPackages, vm::setPackageUb, ::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups)
|
||||
PackageFunctionScreen(
|
||||
R.string.block_uninstall, vm.ubPackages, vm::getUbPackages, vm::setPackageUb,
|
||||
::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups
|
||||
)
|
||||
}
|
||||
composable<DisableUserControl> {
|
||||
PackageFunctionScreenWithoutResult(R.string.disable_user_control, vm.ucdPackages,
|
||||
vm::getUcdPackages, vm::setPackageUcd, ::navigateUp, vm.chosenPackage,
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups, R.string.info_disable_user_control)
|
||||
PackageFunctionScreen(
|
||||
R.string.disable_user_control, vm.ucdPackages, vm::getUcdPackages,
|
||||
vm::setPackageUcd, ::navigateUp, vm.chosenPackage, ::choosePackage,
|
||||
::navigateToAppGroups, vm.appGroups, R.string.info_disable_user_control
|
||||
)
|
||||
}
|
||||
composable<PermissionsManager> {
|
||||
PermissionsManagerScreen(vm.packagePermissions, vm::getPackagePermissions,
|
||||
vm::setPackagePermission, ::navigateUp, it.toRoute(), vm.chosenPackage, ::choosePackage)
|
||||
PermissionsManagerScreen(
|
||||
vm.packagePermissions, vm::getPackagePermissions, vm::setPackagePermission,
|
||||
::navigateUp, it.toRoute(), vm.chosenPackage, ::chooseSinglePackage
|
||||
)
|
||||
}
|
||||
composable<DisableMeteredData> {
|
||||
PackageFunctionScreen(R.string.disable_metered_data, vm.mddPackages,
|
||||
vm::getMddPackages, vm::setPackageMdd, ::navigateUp, vm.chosenPackage,
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups)
|
||||
PackageFunctionScreen(
|
||||
R.string.disable_metered_data, vm.mddPackages, vm::getMddPackages,
|
||||
vm::setPackageMdd, ::navigateUp, vm.chosenPackage, ::choosePackage,
|
||||
::navigateToAppGroups, vm.appGroups
|
||||
)
|
||||
}
|
||||
composable<ClearAppStorage> {
|
||||
ClearAppStorageScreen(vm.chosenPackage, ::choosePackage, vm::clearAppData, ::navigateUp)
|
||||
ClearAppStorageScreen(
|
||||
vm.chosenPackage, ::chooseSinglePackage, vm::clearAppData, ::navigateUp
|
||||
)
|
||||
}
|
||||
composable<UninstallApp> {
|
||||
UninstallAppScreen(vm.chosenPackage, ::choosePackage, vm::uninstallPackage, ::navigateUp)
|
||||
UninstallAppScreen(
|
||||
vm.chosenPackage, ::chooseSinglePackage, vm::uninstallPackage, ::navigateUp
|
||||
)
|
||||
}
|
||||
composable<KeepUninstalledPackages> {
|
||||
PackageFunctionScreenWithoutResult(R.string.keep_uninstalled_packages, vm.kuPackages,
|
||||
vm::getKuPackages, vm::setPackageKu, ::navigateUp, vm.chosenPackage,
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups,
|
||||
R.string.info_keep_uninstalled_apps)
|
||||
PackageFunctionScreen(
|
||||
R.string.keep_uninstalled_packages, vm.kuPackages, vm::getKuPackages,
|
||||
vm::setPackageKu, ::navigateUp, vm.chosenPackage, ::choosePackage,
|
||||
::navigateToAppGroups, vm.appGroups, R.string.info_keep_uninstalled_apps
|
||||
)
|
||||
}
|
||||
composable<InstallExistingApp> {
|
||||
InstallExistingAppScreen(vm.chosenPackage, ::choosePackage,
|
||||
vm::installExistingApp, ::navigateUp)
|
||||
InstallExistingAppScreen(
|
||||
vm.chosenPackage, ::chooseSinglePackage, vm::installExistingApp, ::navigateUp
|
||||
)
|
||||
}
|
||||
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,
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups)
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups
|
||||
)
|
||||
}
|
||||
composable<CrossProfileWidgetProviders> {
|
||||
PackageFunctionScreen(R.string.cross_profile_widget, vm.cpwProviders,
|
||||
@@ -579,28 +604,45 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups)
|
||||
}
|
||||
composable<CredentialManagerPolicy> {
|
||||
CredentialManagerPolicyScreen(vm.chosenPackage, ::choosePackage,
|
||||
vm.cmPackages, vm::getCmPolicy, vm::setCmPackage, vm::setCmPolicy, ::navigateUp)
|
||||
CredentialManagerPolicyScreen(
|
||||
vm.chosenPackage, ::choosePackage, vm.cmPackages, vm::getCmPolicy,
|
||||
vm::setCmPackage, vm::setCmPolicy, ::navigateUp
|
||||
)
|
||||
}
|
||||
composable<PermittedAccessibilityServices> {
|
||||
PermittedAsAndImPackages(R.string.permitted_accessibility_services,
|
||||
PermittedAsAndImPackages(
|
||||
R.string.permitted_accessibility_services,
|
||||
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> {
|
||||
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::setPimPackage, vm::setPimPolicy, ::navigateUp)
|
||||
vm::setPimPackage, vm::setPimPolicy, ::navigateUp
|
||||
)
|
||||
}
|
||||
composable<EnableSystemApp> {
|
||||
EnableSystemAppScreen(vm.chosenPackage, ::choosePackage, vm::enableSystemApp, ::navigateUp)
|
||||
EnableSystemAppScreen(
|
||||
vm.chosenPackage, ::chooseSinglePackage, vm::enableSystemApp, ::navigateUp
|
||||
)
|
||||
}
|
||||
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> {
|
||||
ManageAppGroupsScreen(
|
||||
vm.appGroups,
|
||||
vm.appGroups, vm::exportAppGroups, vm::importAppGroups,
|
||||
{ id, name, apps -> navController.navigate(EditAppGroup(id, name, apps)) },
|
||||
::navigateUp
|
||||
)
|
||||
@@ -751,7 +793,10 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) {
|
||||
}
|
||||
if(privilege.device || privilege.profile) {
|
||||
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) {
|
||||
HomePageItem(R.string.user_restriction, R.drawable.person_off) { onNavigate(UserRestriction) }
|
||||
|
||||
@@ -25,6 +25,8 @@ import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.RestrictionEntry
|
||||
import android.content.RestrictionsManager
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageManager
|
||||
@@ -39,6 +41,7 @@ import android.net.wifi.WifiManager
|
||||
import android.net.wifi.WifiSsid
|
||||
import android.os.Binder
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Bundle
|
||||
import android.os.HardwarePropertiesManager
|
||||
import android.os.UserHandle
|
||||
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.ApnProtocol
|
||||
import com.bintianqi.owndroid.dpm.AppGroup
|
||||
import com.bintianqi.owndroid.dpm.AppRestriction
|
||||
import com.bintianqi.owndroid.dpm.AppStatus
|
||||
import com.bintianqi.owndroid.dpm.BasicAppGroup
|
||||
import com.bintianqi.owndroid.dpm.CaCertInfo
|
||||
import com.bintianqi.owndroid.dpm.CreateUserResult
|
||||
import com.bintianqi.owndroid.dpm.CreateWorkProfileOptions
|
||||
@@ -115,6 +120,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
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.security.MessageDigest
|
||||
import java.security.cert.CertificateException
|
||||
@@ -154,10 +163,10 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
return AppLockConfig(passwordHash?.ifEmpty { null }, SP.biometricsUnlock, SP.lockWhenLeaving)
|
||||
}
|
||||
fun setAppLockConfig(config: AppLockConfig) {
|
||||
SP.lockPasswordHash = if (config.password == null) {
|
||||
""
|
||||
} else {
|
||||
config.password.hash()
|
||||
if (config.password == null) {
|
||||
SP.lockPasswordHash = ""
|
||||
} else if (!config.password.isEmpty()) {
|
||||
SP.lockPasswordHash = config.password.hash()
|
||||
}
|
||||
SP.biometricsUnlock = config.biometrics
|
||||
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) =
|
||||
AppInfo(info.packageName, info.loadLabel(PM).toString(), info.loadIcon(PM), info.flags)
|
||||
fun getAppInfo(name: String): AppInfo {
|
||||
@@ -215,10 +229,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
suspendedPackages.value = packages.map { getAppInfo(it) }
|
||||
}
|
||||
@RequiresApi(24)
|
||||
fun setPackageSuspended(name: String, status: Boolean): Boolean {
|
||||
val result = DPM.setPackagesSuspended(DAR, arrayOf(name), status)
|
||||
fun setPackageSuspended(packages: List<String>, status: Boolean) {
|
||||
DPM.setPackagesSuspended(DAR, packages.toTypedArray(), status)
|
||||
getSuspendedPackaged()
|
||||
return result.isEmpty()
|
||||
}
|
||||
|
||||
val hiddenPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||
@@ -227,10 +240,11 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
DPM.isApplicationHidden(DAR, it.packageName)
|
||||
}.map { getAppInfo(it) }
|
||||
}
|
||||
fun setPackageHidden(name: String, status: Boolean): Boolean {
|
||||
val result = DPM.setApplicationHidden(DAR, name, status)
|
||||
fun setPackageHidden(packages: List<String>, status: Boolean) {
|
||||
for (name in packages) {
|
||||
DPM.setApplicationHidden(DAR, name, status)
|
||||
}
|
||||
getHiddenPackages()
|
||||
return result
|
||||
}
|
||||
|
||||
// Uninstall blocked packages
|
||||
@@ -240,8 +254,10 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
DPM.isUninstallBlocked(DAR, it.packageName)
|
||||
}.map { getAppInfo(it) }
|
||||
}
|
||||
fun setPackageUb(name: String, status: Boolean) {
|
||||
DPM.setUninstallBlocked(DAR, name, status)
|
||||
fun setPackageUb(packages: List<String>, status: Boolean) {
|
||||
for (name in packages) {
|
||||
DPM.setUninstallBlocked(DAR, name, status)
|
||||
}
|
||||
getUbPackages()
|
||||
}
|
||||
|
||||
@@ -254,16 +270,17 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
@RequiresApi(30)
|
||||
fun setPackageUcd(name: String, status: Boolean) {
|
||||
fun setPackageUcd(packages: List<String>, status: Boolean) {
|
||||
DPM.setUserControlDisabledPackages(
|
||||
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()
|
||||
}
|
||||
|
||||
val packagePermissions = MutableStateFlow(emptyMap<String, Int>())
|
||||
@RequiresApi(23)
|
||||
fun getPackagePermissions(name: String) {
|
||||
if (name.isValidPackageName) {
|
||||
packagePermissions.value = runtimePermissions.associate {
|
||||
@@ -273,7 +290,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
packagePermissions.value = emptyMap()
|
||||
}
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun setPackagePermission(name: String, permission: String, status: Int): Boolean {
|
||||
val result = DPM.setPermissionGrantState(DAR, name, permission, status)
|
||||
getPackagePermissions(name)
|
||||
@@ -287,12 +303,13 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
mddPackages.value = DPM.getMeteredDataDisabledPackages(DAR).distinct().map { getAppInfo(it) }
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun setPackageMdd(name: String, status: Boolean): Boolean {
|
||||
val result = DPM.setMeteredDataDisabledPackages(
|
||||
DAR, mddPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) }
|
||||
fun setPackageMdd(packages: List<String>, status: Boolean) {
|
||||
DPM.setMeteredDataDisabledPackages(
|
||||
DAR, mddPackages.value.map { it.name }.run {
|
||||
if (status) plus(packages) else minus(packages)
|
||||
}
|
||||
)
|
||||
getMddPackages()
|
||||
return result.isEmpty()
|
||||
}
|
||||
|
||||
// Keep uninstalled packages
|
||||
@@ -302,9 +319,11 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
kuPackages.value = DPM.getKeepUninstalledPackages(DAR)?.distinct()?.map { getAppInfo(it) } ?: emptyList()
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun setPackageKu(name: String, status: Boolean) {
|
||||
fun setPackageKu(packages: List<String>, status: Boolean) {
|
||||
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()
|
||||
}
|
||||
@@ -316,10 +335,12 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
cpPackages.value = DPM.getCrossProfilePackages(DAR).map { getAppInfo(it) }
|
||||
}
|
||||
@RequiresApi(30)
|
||||
fun setPackageCp(name: String, status: Boolean) {
|
||||
fun setPackageCp(packages: List<String>, status: Boolean) {
|
||||
DPM.setCrossProfilePackages(
|
||||
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()
|
||||
}
|
||||
@@ -329,14 +350,15 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
fun getCpwProviders() {
|
||||
cpwProviders.value = DPM.getCrossProfileWidgetProviders(DAR).distinct().map { getAppInfo(it) }
|
||||
}
|
||||
fun setCpwProvider(name: String, status: Boolean): Boolean {
|
||||
val result = if (status) {
|
||||
DPM.addCrossProfileWidgetProvider(DAR, name)
|
||||
} else {
|
||||
DPM.removeCrossProfileWidgetProvider(DAR, name)
|
||||
fun setCpwProvider(packages: List<String>, status: Boolean) {
|
||||
for (name in packages) {
|
||||
if (status) {
|
||||
DPM.addCrossProfileWidgetProvider(DAR, name)
|
||||
} else {
|
||||
DPM.removeCrossProfileWidgetProvider(DAR, name)
|
||||
}
|
||||
}
|
||||
getCpwProviders()
|
||||
return result
|
||||
}
|
||||
|
||||
@RequiresApi(28)
|
||||
@@ -347,6 +369,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
fun uninstallPackage(packageName: String, onComplete: (String?) -> Unit) {
|
||||
val action = "com.bintianqi.owndroid.action.PACKAGE_UNINSTALLED"
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999)
|
||||
@@ -355,7 +378,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT) as Intent?)
|
||||
} else {
|
||||
context.unregisterReceiver(this)
|
||||
if(statusExtra == PackageInstaller.STATUS_SUCCESS) {
|
||||
if (statusExtra == PackageInstaller.STATUS_SUCCESS) {
|
||||
onComplete(null)
|
||||
} else {
|
||||
onComplete(parsePackageInstallerMessage(context, intent))
|
||||
@@ -364,16 +387,17 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
ContextCompat.registerReceiver(
|
||||
application, receiver, IntentFilter(AppInstallerViewModel.ACTION), null,
|
||||
null, ContextCompat.RECEIVER_EXPORTED
|
||||
application, receiver, IntentFilter(action), null,
|
||||
null, ContextCompat.RECEIVER_NOT_EXPORTED
|
||||
)
|
||||
val intent = Intent(action).setPackage(application.packageName)
|
||||
val pi = if(VERSION.SDK_INT >= 34) {
|
||||
PendingIntent.getBroadcast(
|
||||
application, 0, Intent(AppInstallerViewModel.ACTION),
|
||||
application, 0, intent,
|
||||
PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE
|
||||
).intentSender
|
||||
} 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)
|
||||
}
|
||||
@@ -392,9 +416,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
policy.policyType
|
||||
} ?: -1
|
||||
}
|
||||
fun setCmPackage(name: String, status: Boolean) {
|
||||
cmPackages.update { list ->
|
||||
if (status) list + getAppInfo(name) else list.filter { it.name != name }
|
||||
fun setCmPackage(packages: List<String>, status: Boolean) {
|
||||
cmPackages.update {
|
||||
updateAppInfoList(it, packages, status)
|
||||
}
|
||||
}
|
||||
@RequiresApi(34)
|
||||
@@ -405,6 +429,16 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
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
|
||||
val pimPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||
fun getPimPackages(): Boolean {
|
||||
@@ -413,9 +447,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
packages == null
|
||||
}
|
||||
}
|
||||
fun setPimPackage(name: String, status: Boolean) {
|
||||
pimPackages.update { packages ->
|
||||
if (status) packages + getAppInfo(name) else packages.filter { it.name != name }
|
||||
fun setPimPackage(packages: List<String>, status: Boolean) {
|
||||
pimPackages.update {
|
||||
updateAppInfoList(it, packages, status)
|
||||
}
|
||||
}
|
||||
fun setPimPolicy(allowAll: Boolean): Boolean {
|
||||
@@ -433,9 +467,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
packages == null
|
||||
}
|
||||
}
|
||||
fun setPasPackage(name: String, status: Boolean) {
|
||||
pasPackages.update { packages ->
|
||||
if (status) packages + getAppInfo(name) else packages.filter { it.name != name }
|
||||
fun setPasPackage(packages: List<String>, status: Boolean) {
|
||||
pasPackages.update {
|
||||
updateAppInfoList(it, packages, status)
|
||||
}
|
||||
}
|
||||
fun setPasPolicy(allowAll: Boolean): Boolean {
|
||||
@@ -457,14 +491,18 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
DPM.isUninstallBlocked(DAR, name),
|
||||
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) 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
|
||||
@RequiresApi(24)
|
||||
fun adSetPackageSuspended(name: String, status: Boolean) {
|
||||
DPM.setPackagesSuspended(DAR, arrayOf(name), status)
|
||||
appStatus.update { it.copy(suspend = DPM.isPackageSuspended(DAR, name)) }
|
||||
try {
|
||||
DPM.setPackagesSuspended(DAR, arrayOf(name), status)
|
||||
appStatus.update { it.copy(suspend = DPM.isPackageSuspended(DAR, name)) }
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
fun adSetPackageHidden(name: String, status: Boolean) {
|
||||
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>())
|
||||
init {
|
||||
getAppGroups()
|
||||
@@ -527,6 +638,20 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
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)
|
||||
fun reboot() {
|
||||
@@ -584,16 +709,17 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
statusBarDisabled = if (VERSION.SDK_INT >= 34 &&
|
||||
privilege.run { device || (profile && affiliated) })
|
||||
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,
|
||||
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,
|
||||
autoTimeRequired = if (VERSION.SDK_INT < 30) DPM.autoTimeRequired else false,
|
||||
masterVolumeMuted = DPM.isMasterVolumeMuted(DAR),
|
||||
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,
|
||||
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,
|
||||
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))
|
||||
}
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun setStatusBarDisabled(disabled: Boolean) {
|
||||
val result = DPM.setStatusBarDisabled(DAR, 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))
|
||||
}
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun setBtContactSharingDisabled(disabled: Boolean) {
|
||||
DPM.setBluetoothContactSharingDisabled(DAR, disabled)
|
||||
systemOptionsStatus.update {
|
||||
@@ -662,7 +786,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
DPM.isUsbDataSignalingEnabled = enabled
|
||||
systemOptionsStatus.update { it.copy(usbSignalEnabled = DPM.isUsbDataSignalingEnabled) }
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun setKeyguardDisabled(disabled: Boolean): Boolean {
|
||||
return DPM.setKeyguardDisabled(DAR, disabled)
|
||||
}
|
||||
@@ -732,11 +855,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
fun setContentProtectionPolicy(policy: Int) {
|
||||
DPM.setContentProtectionPolicy(DAR, policy)
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun getPermissionPolicy(): Int {
|
||||
return DPM.getPermissionPolicy(DAR)
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun setPermissionPolicy(policy: Int) {
|
||||
DPM.setPermissionPolicy(DAR, policy)
|
||||
}
|
||||
@@ -784,19 +905,35 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
getLockTaskPackages()
|
||||
}
|
||||
@RequiresApi(28)
|
||||
fun startLockTaskMode(packageName: String, activity: String): Boolean {
|
||||
fun startLockTaskMode(
|
||||
packageName: String, activity: String, clearTask: Boolean, showNotification: Boolean
|
||||
): Boolean {
|
||||
if (!DPM.isLockTaskPermitted(packageName)) {
|
||||
val list = lockTaskPackages.value.map { it.name } + packageName
|
||||
DPM.setLockTaskPackages(DAR, list.toTypedArray())
|
||||
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 intent = if(activity.isNotEmpty()) {
|
||||
Intent().setComponent(ComponentName(packageName, activity))
|
||||
} else PM.getLaunchIntentForPackage(packageName)
|
||||
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())
|
||||
if (showNotification) {
|
||||
application.startForegroundService(Intent(application, LockTaskService::class.java))
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -906,14 +1043,12 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun getSystemUpdatePolicy(): SystemUpdatePolicyInfo {
|
||||
val policy = DPM.systemUpdatePolicy
|
||||
return SystemUpdatePolicyInfo(
|
||||
policy?.policyType ?: -1, policy?.installWindowStart ?: 0, policy?.installWindowEnd ?: 0
|
||||
)
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun setSystemUpdatePolicy(info: SystemUpdatePolicyInfo) {
|
||||
val policy = when (info.type) {
|
||||
SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC -> SystemUpdatePolicy.createAutomaticInstallPolicy()
|
||||
@@ -1227,18 +1362,11 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
fun createWorkProfile(options: CreateWorkProfileOptions): Intent {
|
||||
val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
|
||||
if (VERSION.SDK_INT >= 23) {
|
||||
intent.putExtra(
|
||||
DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
|
||||
MyAdminComponent
|
||||
)
|
||||
} else {
|
||||
intent.putExtra(
|
||||
DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
|
||||
application.packageName
|
||||
)
|
||||
}
|
||||
if (options.migrateAccount && VERSION.SDK_INT >= 22) {
|
||||
intent.putExtra(
|
||||
DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
|
||||
MyAdminComponent
|
||||
)
|
||||
if (options.migrateAccount) {
|
||||
intent.putExtra(
|
||||
DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE,
|
||||
Account(options.accountName, options.accountType)
|
||||
@@ -1315,10 +1443,10 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
return UserInformation(
|
||||
if (VERSION.SDK_INT >= 24) UserManager.supportsMultipleUsers() else false,
|
||||
if (VERSION.SDK_INT >= 31) UserManager.isHeadlessSystemUserMode() else false,
|
||||
if (VERSION.SDK_INT >= 23) UM.isSystemUser else false,
|
||||
UM.isSystemUser,
|
||||
if (VERSION.SDK_INT >= 34) UM.isAdminUser else false,
|
||||
if (VERSION.SDK_INT >= 25) UM.isDemoUser else false,
|
||||
if (VERSION.SDK_INT >= 23) UM.getUserCreationTime(uh) else 0,
|
||||
UM.getUserCreationTime(uh),
|
||||
if (VERSION.SDK_INT >= 28) DPM.isLogoutEnabled else false,
|
||||
if (VERSION.SDK_INT >= 28) DPM.isEphemeralUser(DAR) else false,
|
||||
if (VERSION.SDK_INT >= 28) DPM.isAffiliatedUser else false,
|
||||
@@ -1354,6 +1482,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
UserManager.USER_OPERATION_ERROR_UNKNOWN -> R.string.unknown_error
|
||||
UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE-> R.string.fail_managed_profile
|
||||
UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS -> R.string.limit_reached
|
||||
UserManager.USER_OPERATION_ERROR_MAX_USERS -> R.string.limit_reached
|
||||
UserManager.USER_OPERATION_ERROR_CURRENT_USER -> R.string.fail_current_user
|
||||
else -> R.string.unknown
|
||||
}
|
||||
@@ -1392,7 +1521,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
fun setProfileName(name: String) {
|
||||
DPM.setProfileName(DAR, name)
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun setUserIcon(bitmap: Bitmap) {
|
||||
DPM.setUserIcon(DAR, bitmap)
|
||||
}
|
||||
@@ -1530,7 +1658,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
return PM.getPackageUid(name, 0)
|
||||
}
|
||||
var networkStatsData = emptyList<NetworkStatsData>()
|
||||
@RequiresApi(23)
|
||||
fun readNetworkStats(stats: NetworkStats): List<NetworkStatsData> {
|
||||
val list = mutableListOf<NetworkStatsData>()
|
||||
while (stats.hasNextBucket()) {
|
||||
@@ -1541,7 +1668,6 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
stats.close()
|
||||
return list
|
||||
}
|
||||
@RequiresApi(23)
|
||||
fun readNetworkStatsBucket(bucket: NetworkStats.Bucket): NetworkStatsData {
|
||||
return NetworkStatsData(
|
||||
bucket.rxBytes, bucket.rxPackets, bucket.txBytes, bucket.txPackets,
|
||||
@@ -1823,7 +1949,12 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
@RequiresApi(26)
|
||||
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)
|
||||
fun clearRpToken(): Boolean {
|
||||
|
||||
@@ -6,7 +6,9 @@ import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
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.Row
|
||||
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.automirrored.filled.ArrowBack
|
||||
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.FilledIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
@@ -36,6 +45,7 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.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.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
@@ -66,17 +78,20 @@ data class AppInfo(
|
||||
private fun searchInString(query: String, content: String)
|
||||
= 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)
|
||||
@Composable
|
||||
fun AppChooserScreen(
|
||||
canSwitchView: Boolean, packageList: MutableStateFlow<List<AppInfo>>,
|
||||
params: ApplicationsList, packageList: MutableStateFlow<List<AppInfo>>,
|
||||
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 context = LocalContext.current
|
||||
val hf = LocalHapticFeedback.current
|
||||
val progress by refreshProgress.collectAsStateWithLifecycle()
|
||||
var system by rememberSaveable { mutableStateOf(false) }
|
||||
var query by rememberSaveable { mutableStateOf("") }
|
||||
@@ -85,6 +100,7 @@ fun AppChooserScreen(
|
||||
system == (it.flags and ApplicationInfo.FLAG_SYSTEM != 0) &&
|
||||
(query.isEmpty() || (searchInString(query, it.label) || searchInString(query, it.name)))
|
||||
}
|
||||
val selectedPackages = remember { mutableStateListOf<AppInfo>() }
|
||||
val focusMgr = LocalFocusManager.current
|
||||
LaunchedEffect(Unit) {
|
||||
if(packages.size <= 1) onRefresh()
|
||||
@@ -101,18 +117,86 @@ fun AppChooserScreen(
|
||||
system = !system
|
||||
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)
|
||||
}
|
||||
IconButton(onRefresh, enabled = progress == 1F) {
|
||||
Icon(painter = painterResource(R.drawable.refresh_fill0), contentDescription = null)
|
||||
if (selectedPackages.isEmpty()) {
|
||||
IconButton(onRefresh, enabled = progress == 1F) {
|
||||
Icon(Icons.Default.Refresh, null)
|
||||
}
|
||||
if (params.canSwitchView) IconButton(onSwitchView) {
|
||||
Icon(Icons.AutoMirrored.Default.List, null)
|
||||
}
|
||||
}
|
||||
if (canSwitchView) IconButton(onSwitchView) {
|
||||
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 = {
|
||||
if(searchMode) {
|
||||
if (searchMode) {
|
||||
val fr = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { fr.requestFocus() }
|
||||
OutlinedTextField(
|
||||
@@ -122,19 +206,20 @@ fun AppChooserScreen(
|
||||
keyboardActions = KeyboardActions { focusMgr.clearFocus() },
|
||||
placeholder = { Text(stringResource(R.string.search)) },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.close_fill0),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.clickable {
|
||||
focusMgr.clearFocus()
|
||||
query = ""
|
||||
searchMode = false
|
||||
}
|
||||
)
|
||||
IconButton({
|
||||
query = ""
|
||||
searchMode = false
|
||||
}) {
|
||||
Icon(Icons.Outlined.Clear, null)
|
||||
}
|
||||
},
|
||||
textStyle = typography.bodyLarge,
|
||||
modifier = Modifier.fillMaxWidth().focusRequester(fr)
|
||||
)
|
||||
} else {
|
||||
if (selectedPackages.isNotEmpty()) {
|
||||
Text(selectedPackages.size.toString())
|
||||
}
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
@@ -156,10 +241,24 @@ fun AppChooserScreen(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
focusMgr.clearFocus()
|
||||
onChoosePackage(it.name)
|
||||
}
|
||||
.combinedClickable(onLongClick = {
|
||||
if (params.multiSelect && it !in selectedPackages) {
|
||||
selectedPackages += it
|
||||
hf.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
}, onClick = {
|
||||
if (selectedPackages.isEmpty()) {
|
||||
focusMgr.clearFocus()
|
||||
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)
|
||||
.animateItem()
|
||||
) {
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
package com.bintianqi.owndroid
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.admin.DeviceAdminReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Build.VERSION
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.bintianqi.owndroid.dpm.handlePrivilegeChange
|
||||
import com.bintianqi.owndroid.dpm.retrieveNetworkLogs
|
||||
import com.bintianqi.owndroid.dpm.retrieveSecurityLogs
|
||||
|
||||
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) {
|
||||
super.onEnabled(context, intent)
|
||||
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) {
|
||||
super.onPasswordChanged(context, intent, userHandle)
|
||||
sendUserRelatedNotification(context, userHandle, NotificationType.PasswordChanged)
|
||||
|
||||
@@ -232,7 +232,7 @@ fun AppLockSettingsScreen(
|
||||
config: AppLockConfig, setConfig: (AppLockConfig) -> Unit,
|
||||
onNavigateUp: () -> Unit
|
||||
) = MyScaffold(R.string.app_lock, onNavigateUp) {
|
||||
var password by rememberSaveable { mutableStateOf(config.password ?: "") }
|
||||
var password by rememberSaveable { mutableStateOf("") }
|
||||
var confirmPassword by rememberSaveable { mutableStateOf("") }
|
||||
var allowBiometrics by rememberSaveable { mutableStateOf(config.biometrics) }
|
||||
var lockWhenLeaving by rememberSaveable { mutableStateOf(config.whenLeaving) }
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.bintianqi.owndroid
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
@@ -159,4 +162,20 @@ fun Modifier.clickableTextField(onClick: () -> Unit) =
|
||||
fun adaptiveInsets(): WindowInsets {
|
||||
val navbar = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
|
||||
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
@@ -152,7 +152,7 @@ fun NetworkScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
|
||||
if(VERSION.SDK_INT >= 30) {
|
||||
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) }
|
||||
if(VERSION.SDK_INT >= 29 && privilege.device) {
|
||||
FunctionItem(R.string.private_dns, icon = R.drawable.dns_fill0) { onNavigate(PrivateDns) }
|
||||
@@ -186,9 +186,9 @@ fun NetworkOptionsScreen(
|
||||
) {
|
||||
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||
var dialog by rememberSaveable { mutableIntStateOf(0) }
|
||||
var lanEnabled by rememberSaveable { mutableStateOf(getLanEnabled()) }
|
||||
MyScaffold(R.string.options, onNavigateUp, 0.dp) {
|
||||
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,
|
||||
state = lanEnabled,
|
||||
onCheckedChange = {
|
||||
@@ -937,7 +937,6 @@ enum class NetworkStatsState(val id: Int, val text: Int) {
|
||||
Default(NetworkStats.Bucket.STATE_DEFAULT, R.string.default_str),
|
||||
Foreground(NetworkStats.Bucket.STATE_FOREGROUND, R.string.foreground)
|
||||
}
|
||||
@RequiresApi(23)
|
||||
enum class NetworkStatsUID(val uid: Int, val text: Int) {
|
||||
All(NetworkStats.Bucket.UID_ALL, R.string.all),
|
||||
Removed(NetworkStats.Bucket.UID_REMOVED, R.string.uninstalled),
|
||||
@@ -952,7 +951,6 @@ data class QueryNetworkStatsParams(
|
||||
@Serializable object QueryNetworkStats
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
fun NetworkStatsScreen(
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, getUid: (String) -> Int,
|
||||
@@ -1270,7 +1268,6 @@ data class NetworkStatsData(
|
||||
|
||||
@Serializable object NetworkStatsViewer
|
||||
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
fun NetworkStatsViewerScreen(
|
||||
data: List<NetworkStatsData>, clearData: () -> Unit, onNavigateUp: () -> Unit
|
||||
|
||||
@@ -97,7 +97,9 @@ fun PasswordScreen(vm: MyViewModel,onNavigateUp: () -> Unit, onNavigate: (Any) -
|
||||
if(privilege.device) {
|
||||
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.max_pwd_fail, icon = R.drawable.no_encryption_fill0) { dialog = 4 }
|
||||
if (SP.displayDangerousFeatures) {
|
||||
FunctionItem(R.string.max_pwd_fail, icon = R.drawable.no_encryption_fill0) { dialog = 4 }
|
||||
}
|
||||
}
|
||||
if(VERSION.SDK_INT >= 26) {
|
||||
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()
|
||||
)
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
if(VERSION.SDK_INT >= 23) {
|
||||
CheckBoxItem(
|
||||
R.string.do_not_ask_credentials_on_boot,
|
||||
flags and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0
|
||||
) { flags = flags xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT }
|
||||
}
|
||||
CheckBoxItem(
|
||||
R.string.do_not_ask_credentials_on_boot,
|
||||
flags and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0
|
||||
) { flags = flags xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT }
|
||||
CheckBoxItem(
|
||||
R.string.reset_password_require_entry,
|
||||
flags and RESET_PASSWORD_REQUIRE_ENTRY != 0
|
||||
|
||||
@@ -72,7 +72,6 @@ import androidx.compose.material3.PrimaryTabRow
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TimePicker
|
||||
@@ -109,9 +108,9 @@ import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.Privilege
|
||||
import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.SP
|
||||
import com.bintianqi.owndroid.adaptiveInsets
|
||||
import com.bintianqi.owndroid.clickableTextField
|
||||
import com.bintianqi.owndroid.formatDate
|
||||
import com.bintianqi.owndroid.adaptiveInsets
|
||||
import com.bintianqi.owndroid.popToast
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
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") }*/
|
||||
if(VERSION.SDK_INT >= 35 && (privilege.device || (privilege.profile && privilege.affiliated)))
|
||||
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) {
|
||||
FunctionItem(R.string.mte_policy, icon = R.drawable.memory_fill0) { onNavigate(MtePolicy) }
|
||||
}
|
||||
if(VERSION.SDK_INT >= 31) {
|
||||
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.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.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) }
|
||||
}
|
||||
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,
|
||||
vm::setUsbSignalEnabled, R.drawable.usb_fill0)
|
||||
}
|
||||
if (VERSION.SDK_INT >= 23 && VERSION.SDK_INT < 34) {
|
||||
if (VERSION.SDK_INT < 34) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
@@ -414,8 +411,8 @@ fun KeyguardScreen(
|
||||
val context = LocalContext.current
|
||||
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||
MyScaffold(R.string.keyguard, onNavigateUp) {
|
||||
if (VERSION.SDK_INT >= 23 && (privilege.device ||
|
||||
(VERSION.SDK_INT >= 28 && privilege.profile && privilege.affiliated))) {
|
||||
if (privilege.device ||
|
||||
(VERSION.SDK_INT >= 28 && privilege.profile && privilege.affiliated)) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
@@ -436,7 +433,7 @@ fun KeyguardScreen(
|
||||
Notes(R.string.info_disable_keyguard)
|
||||
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))
|
||||
var evictKey by rememberSaveable { mutableStateOf(false) }
|
||||
Button(
|
||||
@@ -1008,7 +1005,6 @@ fun ContentProtectionPolicyScreen(
|
||||
|
||||
@Serializable object PermissionPolicy
|
||||
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
fun PermissionPolicyScreen(
|
||||
getPolicy: () -> Int, setPolicy: (Int) -> Unit, onNavigateUp: () -> Unit
|
||||
@@ -1148,9 +1144,10 @@ fun NearbyStreamingPolicyScreen(
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
fun LockTaskModeScreen(
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
|
||||
chosenPackage: Channel<String>, chooseSinglePackage: () -> Unit, choosePackage: () -> 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
|
||||
) {
|
||||
val coroutine = rememberCoroutineScope()
|
||||
@@ -1175,7 +1172,7 @@ fun LockTaskModeScreen(
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
TabRow(tabIndex) {
|
||||
PrimaryTabRow(tabIndex) {
|
||||
Tab(
|
||||
tabIndex == 0, onClick = { coroutine.launch { pagerState.animateScrollToPage(0) } },
|
||||
text = { Text(stringResource(R.string.start)) }
|
||||
@@ -1191,9 +1188,9 @@ fun LockTaskModeScreen(
|
||||
}
|
||||
HorizontalPager(pagerState, verticalAlignment = Alignment.Top) { page ->
|
||||
if(page == 0) {
|
||||
StartLockTaskMode(startLockTaskMode, chosenPackage, onChoosePackage)
|
||||
StartLockTaskMode(startLockTaskMode, chosenPackage, chooseSinglePackage)
|
||||
} else if (page == 1) {
|
||||
LockTaskPackages(chosenPackage, onChoosePackage, lockTaskPackages, setLockTaskPackage)
|
||||
LockTaskPackages(chosenPackage, choosePackage, lockTaskPackages, setLockTaskPackage)
|
||||
} else {
|
||||
LockTaskFeatures(getLockTaskFeatures, setLockTaskFeature)
|
||||
}
|
||||
@@ -1205,29 +1202,39 @@ fun LockTaskModeScreen(
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
private fun StartLockTaskMode(
|
||||
startLockTaskMode: (String, String) -> Boolean,
|
||||
startLockTaskMode: (String, String, Boolean, Boolean) -> Boolean,
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val focusMgr = LocalFocusManager.current
|
||||
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||
var packageName by rememberSaveable { mutableStateOf("") }
|
||||
var activity by rememberSaveable { mutableStateOf("") }
|
||||
var specifyActivity by rememberSaveable { mutableStateOf(false) }
|
||||
var clearTask by rememberSaveable { mutableStateOf(true) }
|
||||
var showNotification by rememberSaveable { mutableStateOf(true) }
|
||||
LaunchedEffect(Unit) {
|
||||
packageName = chosenPackage.receive()
|
||||
}
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = HorizontalPadding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Spacer(Modifier.height(5.dp))
|
||||
PackageNameTextField(packageName, onChoosePackage) { packageName = it }
|
||||
PackageNameTextField(
|
||||
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(
|
||||
Modifier
|
||||
.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, {
|
||||
specifyActivity = it
|
||||
@@ -1246,16 +1253,17 @@ private fun StartLockTaskMode(
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 5.dp),
|
||||
.padding(horizontal = HorizontalPadding),
|
||||
onClick = {
|
||||
val result = startLockTaskMode(packageName, activity)
|
||||
val result = startLockTaskMode(packageName, activity, clearTask, showNotification)
|
||||
if (!result) context.showOperationResultToast(false)
|
||||
},
|
||||
enabled = packageName.isNotBlank() && (!specifyActivity || activity.isNotBlank())
|
||||
) {
|
||||
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) {
|
||||
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) {
|
||||
flag = flag xor WIPE_RESET_PROTECTION_DATA
|
||||
}
|
||||
@@ -1828,7 +1836,7 @@ fun WipeDataScreen(
|
||||
text = {
|
||||
Text(
|
||||
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
|
||||
),
|
||||
color = colorScheme.error
|
||||
@@ -1869,7 +1877,6 @@ data class PendingSystemUpdateInfo(val exists: Boolean, val time: Long, val secu
|
||||
|
||||
@Serializable object SetSystemUpdatePolicy
|
||||
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
fun SystemUpdatePolicyScreen(
|
||||
getPolicy: () -> SystemUpdatePolicyInfo, setPolicy: (SystemUpdatePolicyInfo) -> Unit,
|
||||
|
||||
@@ -30,12 +30,12 @@ import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MenuAnchorType
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
@@ -105,25 +105,23 @@ fun UsersScreen(vm: MyViewModel, onNavigateUp: () -> Unit, onNavigate: (Any) ->
|
||||
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) }
|
||||
if(VERSION.SDK_INT >= 23) {
|
||||
var changeUserIconDialog by remember { mutableStateOf(false) }
|
||||
var bitmap: Bitmap? by remember { mutableStateOf(null) }
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
if(it != null) uriToStream(context, it) { stream ->
|
||||
bitmap = BitmapFactory.decodeStream(stream)
|
||||
if(bitmap != null) changeUserIconDialog = true
|
||||
}
|
||||
var changeUserIconDialog by remember { mutableStateOf(false) }
|
||||
var bitmap: Bitmap? by remember { mutableStateOf(null) }
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
if(it != null) uriToStream(context, it) { stream ->
|
||||
bitmap = BitmapFactory.decodeStream(stream)
|
||||
if(bitmap != null) changeUserIconDialog = true
|
||||
}
|
||||
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) {
|
||||
context.popToast(R.string.select_an_image)
|
||||
launcher.launch("image/*")
|
||||
}
|
||||
if (changeUserIconDialog) ChangeUserIconDialog(
|
||||
bitmap!!, {
|
||||
vm.setUserIcon(bitmap!!)
|
||||
changeUserIconDialog = false
|
||||
}) { changeUserIconDialog = false }
|
||||
}
|
||||
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) {
|
||||
context.popToast(R.string.select_an_image)
|
||||
launcher.launch("image/*")
|
||||
}
|
||||
if (changeUserIconDialog) ChangeUserIconDialog(
|
||||
bitmap!!, {
|
||||
vm.setUserIcon(bitmap!!)
|
||||
changeUserIconDialog = false
|
||||
}) { changeUserIconDialog = false }
|
||||
if(VERSION.SDK_INT >= 28 && privilege.device) {
|
||||
FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) }
|
||||
}
|
||||
@@ -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 >= 31) InfoItem(R.string.headless_system_user_mode, info.headless.yesOrNo, true) { infoDialog = 1 }
|
||||
Spacer(Modifier.height(8.dp))
|
||||
if (VERSION.SDK_INT >= 23) InfoItem(R.string.system_user, info.system.yesOrNo)
|
||||
InfoItem(R.string.system_user, info.system.yesOrNo)
|
||||
if (VERSION.SDK_INT >= 34) InfoItem(R.string.admin_user, info.admin.yesOrNo)
|
||||
if (VERSION.SDK_INT >= 25) InfoItem(R.string.demo_user, info.demo.yesOrNo)
|
||||
if (info.time != 0L) InfoItem(R.string.creation_time, formatDate(info.time))
|
||||
@@ -264,7 +262,7 @@ fun UserOperationScreen(
|
||||
input, { input = it },
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor(MenuAnchorType.PrimaryEditable)
|
||||
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable)
|
||||
.padding(top = 4.dp, bottom = 8.dp),
|
||||
label = {
|
||||
Text(stringResource(if(useUserId) R.string.user_id else R.string.serial_number))
|
||||
@@ -586,7 +584,6 @@ fun UserSessionMessageScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(23)
|
||||
@Composable
|
||||
private fun ChangeUserIconDialog(bitmap: Bitmap, onSet: () -> Unit, onClose: () -> Unit) {
|
||||
AlertDialog(
|
||||
|
||||
@@ -101,31 +101,29 @@ fun CreateWorkProfileScreen(
|
||||
var migrateAccountName by remember { mutableStateOf("") }
|
||||
var migrateAccountType by remember { mutableStateOf("") }
|
||||
var keepAccount by remember { mutableStateOf(true) }
|
||||
if (VERSION.SDK_INT >= 22) {
|
||||
FullWidthCheckBoxItem(R.string.migrate_account, migrateAccount) { migrateAccount = it }
|
||||
AnimatedVisibility(migrateAccount) {
|
||||
val fr = FocusRequester()
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
OutlinedTextField(
|
||||
value = migrateAccountName, onValueChange = { migrateAccountName = it },
|
||||
label = { Text(stringResource(R.string.account_name)) },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
keyboardActions = KeyboardActions { fr.requestFocus() },
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = migrateAccountType, onValueChange = { migrateAccountType = it },
|
||||
label = { Text(stringResource(R.string.account_type)) },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions { focusMgr.clearFocus() },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = HorizontalPadding)
|
||||
.focusRequester(fr)
|
||||
)
|
||||
if(VERSION.SDK_INT >= 26) {
|
||||
FullWidthCheckBoxItem(R.string.keep_account, keepAccount) { keepAccount = it }
|
||||
}
|
||||
FullWidthCheckBoxItem(R.string.migrate_account, migrateAccount) { migrateAccount = it }
|
||||
AnimatedVisibility(migrateAccount) {
|
||||
val fr = FocusRequester()
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
OutlinedTextField(
|
||||
value = migrateAccountName, onValueChange = { migrateAccountName = it },
|
||||
label = { Text(stringResource(R.string.account_name)) },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
keyboardActions = KeyboardActions { fr.requestFocus() },
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = migrateAccountType, onValueChange = { migrateAccountType = it },
|
||||
label = { Text(stringResource(R.string.account_type)) },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions { focusMgr.clearFocus() },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = HorizontalPadding)
|
||||
.focusRequester(fr)
|
||||
)
|
||||
if(VERSION.SDK_INT >= 26) {
|
||||
FullWidthCheckBoxItem(R.string.keep_account, keepAccount) { keepAccount = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
app/src/main/res/drawable/abc_fill0.xml
Normal file
9
app/src/main/res/drawable/abc_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/check_box_fill0.xml
Normal file
9
app/src/main/res/drawable/check_box_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/drag_indicator_fill0.xml
Normal file
9
app/src/main/res/drawable/drag_indicator_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/file_export_fill0.xml
Normal file
9
app/src/main/res/drawable/file_export_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/file_open_fill0.xml
Normal file
9
app/src/main/res/drawable/file_open_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/number_123_fill0.xml
Normal file
9
app/src/main/res/drawable/number_123_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/radio_button_checked_fill0.xml
Normal file
9
app/src/main/res/drawable/radio_button_checked_fill0.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/toggle_off_fill0.xml
Normal file
9
app/src/main/res/drawable/toggle_off_fill0.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/visibility_fill0.xml
Normal file
11
app/src/main/res/drawable/visibility_fill0.xml
Normal 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>
|
||||
@@ -278,7 +278,6 @@
|
||||
<string name="network_logging">Сетевой журнал</string>
|
||||
<string name="delete_logs">Удалить журналы</string>
|
||||
<string name="export_logs">Экспортировать журналы</string>
|
||||
<string name="wifi_auth_keypair">Пара ключей Wi-Fi</string>
|
||||
<string name="preferential_network_service">Предпочтительная сетевая служба</string>
|
||||
<string name="add_config">Add config</string> <!--TODO-->
|
||||
<string name="network_id">Идентификатор сети</string>
|
||||
|
||||
@@ -307,7 +307,6 @@
|
||||
<string name="network_logging">Ağ Kayıtları</string>
|
||||
<string name="delete_logs">Kayıtları Sil</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="add_config">Yapılandırma Ekle</string>
|
||||
<string name="network_id">Ağ Kimliği</string>
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
<string name="timeout">超时</string>
|
||||
<string name="continue_str">继续</string>
|
||||
<string name="exit">退出</string>
|
||||
<string name="undo">撤销</string>
|
||||
|
||||
<!--Permissions-->
|
||||
<string name="profile_owner">Profile owner</string>
|
||||
@@ -173,6 +174,8 @@
|
||||
<string name="nearby_notification_streaming">附近通知传输</string>
|
||||
<string name="enable_if_secure_enough">在足够安全时启用</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="disable_all">禁用全部</string>
|
||||
<string name="ltf_sys_info">允许状态栏信息</string>
|
||||
@@ -293,7 +296,6 @@
|
||||
<string name="network_logs_collected">网络日志已收集</string>
|
||||
<string name="delete_logs">删除日志</string>
|
||||
<string name="export_logs">导出日志</string>
|
||||
<string name="wifi_auth_keypair">Wi-Fi密钥对</string>
|
||||
<string name="preferential_network_service">首选网络服务</string>
|
||||
<string name="add_config">添加配置</string>
|
||||
<string name="network_id">网络ID</string>
|
||||
@@ -343,7 +345,9 @@
|
||||
<string name="show_user_app">显示用户应用</string>
|
||||
<string name="show_system_app">显示系统应用</string>
|
||||
<string name="suspend">挂起</string>
|
||||
<string name="unsuspend">取消挂起</string>
|
||||
<string name="hide">隐藏</string>
|
||||
<string name="unhide">取消隐藏</string>
|
||||
<string name="always_on_vpn">VPN保持打开</string>
|
||||
<string name="enable_lockdown">启用锁定</string>
|
||||
<string name="clear_current_config">清除当前配置</string>
|
||||
@@ -362,6 +366,7 @@
|
||||
<string name="permitted_ime">许可的输入法</string>
|
||||
<string name="keep_uninstalled_packages">卸载后保留的应用</string>
|
||||
<string name="clear_app_storage">清除应用存储</string>
|
||||
<string name="clear_app_storage_confirmation">你确定要删除应用数据吗?</string>
|
||||
<string name="set_default_dialer">设置默认拨号器</string>
|
||||
<string name="uninstall_app">卸载应用</string>
|
||||
<string name="install_app">安装应用</string>
|
||||
@@ -371,9 +376,14 @@
|
||||
<string name="search">搜索</string>
|
||||
<string name="app_group">应用组</string>
|
||||
<string name="manage_app_groups">管理组</string>
|
||||
<string name="import_str">导入</string>
|
||||
<string name="edit_app_group">编辑组</string>
|
||||
<string name="add_to_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-->
|
||||
<string name="user_restriction">用户限制</string>
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
<string name="timeout">Timeout</string>
|
||||
<string name="continue_str">Continue</string>
|
||||
<string name="exit">Exit</string>
|
||||
<string name="undo">Undo</string>
|
||||
|
||||
<!--Permissions-->
|
||||
<string name="profile_owner">Profile owner</string>
|
||||
@@ -102,7 +103,7 @@
|
||||
<string name="org_name">Organization name</string>
|
||||
<string name="disable_account_management">Disable account management</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="support_messages">Support Messages</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="enable_if_secure_enough">Same managed account only</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="disable_all">Disable all</string>
|
||||
<!--ltf: lock task feature-->
|
||||
@@ -327,7 +330,6 @@
|
||||
<string name="network_logs_collected">Network logs collected</string>
|
||||
<string name="delete_logs">Delete 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="add_config">Add config</string>
|
||||
<string name="network_id">Network ID</string>
|
||||
@@ -377,7 +379,9 @@
|
||||
<string name="show_user_app">Show user apps</string>
|
||||
<string name="show_system_app">Show system apps</string>
|
||||
<string name="suspend">Suspend</string>
|
||||
<string name="unsuspend">Unsuspend</string>
|
||||
<string name="hide">Hide</string>
|
||||
<string name="unhide">Unhide</string>
|
||||
<string name="always_on_vpn">Always-on VPN</string>
|
||||
<string name="enable_lockdown">Enable lockdown</string>
|
||||
<string name="clear_current_config">Clear current config</string>
|
||||
@@ -397,6 +401,7 @@
|
||||
<string name="permitted_ime">Permitted IME</string>
|
||||
<string name="keep_uninstalled_packages">Keep uninstalled packages</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="uninstall_app">Uninstall app</string>
|
||||
<string name="install_app">Install app</string>
|
||||
@@ -405,9 +410,14 @@
|
||||
<string name="search">Search</string>
|
||||
<string name="app_group">App group</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="add_to_list">Add to 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-->
|
||||
<string name="user_restriction">User restriction</string>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[versions]
|
||||
agp = "8.13.1"
|
||||
kotlin = "2.2.21"
|
||||
agp = "8.13.2"
|
||||
kotlin = "2.3.0"
|
||||
|
||||
navigation-compose = "2.9.6"
|
||||
composeBom = "2025.11.00"
|
||||
composeBom = "2025.12.01"
|
||||
accompanist-drawablepainter = "0.37.3"
|
||||
accompanist-permissions = "0.37.3"
|
||||
shizuku = "13.1.5"
|
||||
@@ -12,6 +12,7 @@ dhizuku = "2.5.4"
|
||||
dhizuku-server = "0.0.10"
|
||||
hiddenApiBypass = "6.1"
|
||||
libsu = "6.0.0"
|
||||
reoderable = "3.0.0"
|
||||
serialization = "1.9.0"
|
||||
|
||||
[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" }
|
||||
hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" }
|
||||
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" }
|
||||
|
||||
@@ -40,4 +42,4 @@ serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", 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" }
|
||||
|
||||
Reference in New Issue
Block a user