Optimize Applications and PackageChooser

This commit is contained in:
BinTianqi
2025-09-17 18:29:52 +08:00
parent e7c7a3b3c6
commit 80c1ddb36c
13 changed files with 787 additions and 802 deletions

View File

@@ -24,3 +24,5 @@
-dontwarn android.app.ActivityThread -dontwarn android.app.ActivityThread
-dontwarn android.app.ContextImpl -dontwarn android.app.ContextImpl
-dontwarn android.app.LoadedApk -dontwarn android.app.LoadedApk
-keep class com.bintianqi.owndroid.MyViewModel { *; }

View File

@@ -60,11 +60,6 @@
<data android:mimeType="application/vnd.android.package-archive"/> <data android:mimeType="application/vnd.android.package-archive"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".PackageChooserActivity"
android:label="@string/package_chooser"
android:exported="false"
android:launchMode="singleInstance"/>
<activity <activity
android:name=".ShortcutsReceiverActivity" android:name=".ShortcutsReceiverActivity"
android:permission="com.bintianqi.owndroid.MyPermission" android:permission="com.bintianqi.owndroid.MyPermission"

View File

@@ -34,7 +34,6 @@ import com.rosan.dhizuku.server_api.DhizukuService
import com.rosan.dhizuku.shared.DhizukuVariables import com.rosan.dhizuku.shared.DhizukuVariables
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
private const val TAG = "DhizukuServer" private const val TAG = "DhizukuServer"

View File

@@ -79,7 +79,6 @@ import com.bintianqi.owndroid.dpm.AutoTimePolicyScreen
import com.bintianqi.owndroid.dpm.AutoTimeZonePolicy import com.bintianqi.owndroid.dpm.AutoTimeZonePolicy
import com.bintianqi.owndroid.dpm.AutoTimeZonePolicyScreen import com.bintianqi.owndroid.dpm.AutoTimeZonePolicyScreen
import com.bintianqi.owndroid.dpm.BlockUninstall import com.bintianqi.owndroid.dpm.BlockUninstall
import com.bintianqi.owndroid.dpm.BlockUninstallScreen
import com.bintianqi.owndroid.dpm.CaCert import com.bintianqi.owndroid.dpm.CaCert
import com.bintianqi.owndroid.dpm.CaCertScreen import com.bintianqi.owndroid.dpm.CaCertScreen
import com.bintianqi.owndroid.dpm.ChangeTime import com.bintianqi.owndroid.dpm.ChangeTime
@@ -101,9 +100,7 @@ import com.bintianqi.owndroid.dpm.CredentialManagerPolicyScreen
import com.bintianqi.owndroid.dpm.CrossProfileIntentFilter import com.bintianqi.owndroid.dpm.CrossProfileIntentFilter
import com.bintianqi.owndroid.dpm.CrossProfileIntentFilterScreen import com.bintianqi.owndroid.dpm.CrossProfileIntentFilterScreen
import com.bintianqi.owndroid.dpm.CrossProfilePackages import com.bintianqi.owndroid.dpm.CrossProfilePackages
import com.bintianqi.owndroid.dpm.CrossProfilePackagesScreen
import com.bintianqi.owndroid.dpm.CrossProfileWidgetProviders import com.bintianqi.owndroid.dpm.CrossProfileWidgetProviders
import com.bintianqi.owndroid.dpm.CrossProfileWidgetProvidersScreen
import com.bintianqi.owndroid.dpm.DelegatedAdmins import com.bintianqi.owndroid.dpm.DelegatedAdmins
import com.bintianqi.owndroid.dpm.DelegatedAdminsScreen import com.bintianqi.owndroid.dpm.DelegatedAdminsScreen
import com.bintianqi.owndroid.dpm.DeleteWorkProfile import com.bintianqi.owndroid.dpm.DeleteWorkProfile
@@ -115,9 +112,7 @@ import com.bintianqi.owndroid.dpm.DhizukuServerSettingsScreen
import com.bintianqi.owndroid.dpm.DisableAccountManagement import com.bintianqi.owndroid.dpm.DisableAccountManagement
import com.bintianqi.owndroid.dpm.DisableAccountManagementScreen import com.bintianqi.owndroid.dpm.DisableAccountManagementScreen
import com.bintianqi.owndroid.dpm.DisableMeteredData import com.bintianqi.owndroid.dpm.DisableMeteredData
import com.bintianqi.owndroid.dpm.DisableMeteredDataScreen
import com.bintianqi.owndroid.dpm.DisableUserControl import com.bintianqi.owndroid.dpm.DisableUserControl
import com.bintianqi.owndroid.dpm.DisableUserControlScreen
import com.bintianqi.owndroid.dpm.EnableSystemApp import com.bintianqi.owndroid.dpm.EnableSystemApp
import com.bintianqi.owndroid.dpm.EnableSystemAppScreen import com.bintianqi.owndroid.dpm.EnableSystemAppScreen
import com.bintianqi.owndroid.dpm.FrpPolicy import com.bintianqi.owndroid.dpm.FrpPolicy
@@ -125,13 +120,11 @@ import com.bintianqi.owndroid.dpm.FrpPolicyScreen
import com.bintianqi.owndroid.dpm.HardwareMonitor import com.bintianqi.owndroid.dpm.HardwareMonitor
import com.bintianqi.owndroid.dpm.HardwareMonitorScreen import com.bintianqi.owndroid.dpm.HardwareMonitorScreen
import com.bintianqi.owndroid.dpm.Hide import com.bintianqi.owndroid.dpm.Hide
import com.bintianqi.owndroid.dpm.HideScreen
import com.bintianqi.owndroid.dpm.InstallExistingApp import com.bintianqi.owndroid.dpm.InstallExistingApp
import com.bintianqi.owndroid.dpm.InstallExistingAppScreen import com.bintianqi.owndroid.dpm.InstallExistingAppScreen
import com.bintianqi.owndroid.dpm.InstallSystemUpdate import com.bintianqi.owndroid.dpm.InstallSystemUpdate
import com.bintianqi.owndroid.dpm.InstallSystemUpdateScreen import com.bintianqi.owndroid.dpm.InstallSystemUpdateScreen
import com.bintianqi.owndroid.dpm.KeepUninstalledPackages import com.bintianqi.owndroid.dpm.KeepUninstalledPackages
import com.bintianqi.owndroid.dpm.KeepUninstalledPackagesScreen
import com.bintianqi.owndroid.dpm.Keyguard import com.bintianqi.owndroid.dpm.Keyguard
import com.bintianqi.owndroid.dpm.KeyguardDisabledFeatures import com.bintianqi.owndroid.dpm.KeyguardDisabledFeatures
import com.bintianqi.owndroid.dpm.KeyguardDisabledFeaturesScreen import com.bintianqi.owndroid.dpm.KeyguardDisabledFeaturesScreen
@@ -157,6 +150,8 @@ import com.bintianqi.owndroid.dpm.OrganizationOwnedProfile
import com.bintianqi.owndroid.dpm.OrganizationOwnedProfileScreen import com.bintianqi.owndroid.dpm.OrganizationOwnedProfileScreen
import com.bintianqi.owndroid.dpm.OverrideApn import com.bintianqi.owndroid.dpm.OverrideApn
import com.bintianqi.owndroid.dpm.OverrideApnScreen import com.bintianqi.owndroid.dpm.OverrideApnScreen
import com.bintianqi.owndroid.dpm.PackageFunctionScreen
import com.bintianqi.owndroid.dpm.PackageFunctionScreenWithoutResult
import com.bintianqi.owndroid.dpm.Password import com.bintianqi.owndroid.dpm.Password
import com.bintianqi.owndroid.dpm.PasswordInfo import com.bintianqi.owndroid.dpm.PasswordInfo
import com.bintianqi.owndroid.dpm.PasswordInfoScreen import com.bintianqi.owndroid.dpm.PasswordInfoScreen
@@ -166,9 +161,8 @@ import com.bintianqi.owndroid.dpm.PermissionPolicyScreen
import com.bintianqi.owndroid.dpm.PermissionsManager import com.bintianqi.owndroid.dpm.PermissionsManager
import com.bintianqi.owndroid.dpm.PermissionsManagerScreen import com.bintianqi.owndroid.dpm.PermissionsManagerScreen
import com.bintianqi.owndroid.dpm.PermittedAccessibilityServices import com.bintianqi.owndroid.dpm.PermittedAccessibilityServices
import com.bintianqi.owndroid.dpm.PermittedAccessibilityServicesScreen import com.bintianqi.owndroid.dpm.PermittedAsAndImPackages
import com.bintianqi.owndroid.dpm.PermittedInputMethods import com.bintianqi.owndroid.dpm.PermittedInputMethods
import com.bintianqi.owndroid.dpm.PermittedInputMethodsScreen
import com.bintianqi.owndroid.dpm.PreferentialNetworkService import com.bintianqi.owndroid.dpm.PreferentialNetworkService
import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceScreen import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceScreen
import com.bintianqi.owndroid.dpm.PrivateDns import com.bintianqi.owndroid.dpm.PrivateDns
@@ -195,7 +189,6 @@ import com.bintianqi.owndroid.dpm.SupportMessageScreen
import com.bintianqi.owndroid.dpm.Suspend import com.bintianqi.owndroid.dpm.Suspend
import com.bintianqi.owndroid.dpm.SuspendPersonalApp import com.bintianqi.owndroid.dpm.SuspendPersonalApp
import com.bintianqi.owndroid.dpm.SuspendPersonalAppScreen import com.bintianqi.owndroid.dpm.SuspendPersonalAppScreen
import com.bintianqi.owndroid.dpm.SuspendScreen
import com.bintianqi.owndroid.dpm.SystemManager import com.bintianqi.owndroid.dpm.SystemManager
import com.bintianqi.owndroid.dpm.SystemManagerScreen import com.bintianqi.owndroid.dpm.SystemManagerScreen
import com.bintianqi.owndroid.dpm.SystemOptions import com.bintianqi.owndroid.dpm.SystemOptions
@@ -276,6 +269,9 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
val lifecycleOwner = LocalLifecycleOwner.current val lifecycleOwner = LocalLifecycleOwner.current
fun navigateUp() { navController.navigateUp() } fun navigateUp() { navController.navigateUp() }
fun navigate(destination: Any) { navController.navigate(destination) } fun navigate(destination: Any) { navController.navigate(destination) }
fun choosePackage() {
navController.navigate(ApplicationsList(false))
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if(!Privilege.status.value.activated) { if(!Privilege.status.value.activated) {
navController.navigate(WorkModes(false)) { navController.navigate(WorkModes(false)) {
@@ -306,7 +302,9 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
composable<DhizukuServerSettings> { DhizukuServerSettingsScreen(::navigateUp) } composable<DhizukuServerSettings> { DhizukuServerSettingsScreen(::navigateUp) }
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) } composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) }
composable<AddDelegatedAdmin>{ AddDelegatedAdminScreen(it.toRoute(), ::navigateUp) } composable<AddDelegatedAdmin>{
AddDelegatedAdminScreen(vm.chosenPackage, ::choosePackage, it.toRoute(), ::navigateUp)
}
composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) } composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) }
composable<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) } composable<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) }
composable<SupportMessage> { SupportMessageScreen(::navigateUp) } composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
@@ -331,7 +329,9 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
composable<PermissionPolicy> { PermissionPolicyScreen(::navigateUp) } composable<PermissionPolicy> { PermissionPolicyScreen(::navigateUp) }
composable<MtePolicy> { MtePolicyScreen(::navigateUp) } composable<MtePolicy> { MtePolicyScreen(::navigateUp) }
composable<NearbyStreamingPolicy> { NearbyStreamingPolicyScreen(::navigateUp) } composable<NearbyStreamingPolicy> { NearbyStreamingPolicyScreen(::navigateUp) }
composable<LockTaskMode> { LockTaskModeScreen(::navigateUp) } composable<LockTaskMode> {
LockTaskModeScreen(vm.chosenPackage, ::choosePackage, ::navigateUp)
}
composable<CaCert> { CaCertScreen(::navigateUp) } composable<CaCert> { CaCertScreen(::navigateUp) }
composable<SecurityLogging> { SecurityLoggingScreen(::navigateUp) } composable<SecurityLogging> { SecurityLoggingScreen(::navigateUp) }
composable<DisableAccountManagement> { DisableAccountManagementScreen(::navigateUp) } composable<DisableAccountManagement> { DisableAccountManagementScreen(::navigateUp) }
@@ -346,12 +346,16 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) } composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) }
composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) } composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) }
composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) } composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) }
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp, ::navigate) } composable<QueryNetworkStats> {
NetworkStatsScreen(vm.chosenPackage, ::choosePackage, ::navigateUp, ::navigate)
}
composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) { composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) {
NetworkStatsViewerScreen(it.toRoute(), ::navigateUp) NetworkStatsViewerScreen(it.toRoute(), ::navigateUp)
} }
composable<PrivateDns> { PrivateDnsScreen(::navigateUp) } composable<PrivateDns> { PrivateDnsScreen(::navigateUp) }
composable<AlwaysOnVpnPackage> { AlwaysOnVpnPackageScreen(::navigateUp) } composable<AlwaysOnVpnPackage> {
AlwaysOnVpnPackageScreen(vm.chosenPackage, ::choosePackage, ::navigateUp)
}
composable<RecommendedGlobalProxy> { RecommendedGlobalProxyScreen(::navigateUp) } composable<RecommendedGlobalProxy> { RecommendedGlobalProxyScreen(::navigateUp) }
composable<NetworkLogging> { NetworkLoggingScreen(::navigateUp) } composable<NetworkLogging> { NetworkLoggingScreen(::navigateUp) }
composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) } composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) }
@@ -368,14 +372,25 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
composable<DeleteWorkProfile> { DeleteWorkProfileScreen(::navigateUp) } composable<DeleteWorkProfile> { DeleteWorkProfileScreen(::navigateUp) }
composable<ApplicationsList> { composable<ApplicationsList> {
AppChooserScreen(it.toRoute(), { dest -> val canSwitchView = (it.toRoute() as ApplicationsList).canSwitchView
if(dest == null) navigateUp() else navigate(ApplicationDetails(dest)) AppChooserScreen(
canSwitchView, vm.installedPackages, vm.refreshPackagesProgress, { name ->
if (canSwitchView) {
if (name == null) {
navigateUp()
} else {
navigate(ApplicationDetails(name))
}
} else {
if (name != null) vm.chosenPackage.trySend(name)
navigateUp()
}
}, { }, {
SP.applicationsListView = false SP.applicationsListView = false
navController.navigate(ApplicationsFeatures) { navController.navigate(ApplicationsFeatures) {
popUpTo(Home) popUpTo(Home)
} }
}) }, vm::refreshPackageList)
} }
composable<ApplicationsFeatures> { composable<ApplicationsFeatures> {
ApplicationsFeaturesScreen(::navigateUp, ::navigate) { ApplicationsFeaturesScreen(::navigateUp, ::navigate) {
@@ -385,24 +400,78 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
} }
} }
} }
composable<ApplicationDetails> { ApplicationDetailsScreen(it.toRoute(), ::navigateUp, ::navigate) } composable<ApplicationDetails> {
composable<Suspend> { SuspendScreen(::navigateUp) } ApplicationDetailsScreen(it.toRoute(), vm, ::navigateUp, ::navigate)
composable<Hide> { HideScreen(::navigateUp) } }
composable<BlockUninstall> { BlockUninstallScreen(::navigateUp) } composable<Suspend> {
composable<DisableUserControl> { DisableUserControlScreen(::navigateUp) } PackageFunctionScreen(R.string.suspend, vm.suspendedPackages, vm::getSuspendedPackaged,
composable<PermissionsManager> { PermissionsManagerScreen(::navigateUp, it.toRoute()) } vm::setPackageSuspended, ::navigateUp, vm.chosenPackage, ::choosePackage,
composable<DisableMeteredData> { DisableMeteredDataScreen(::navigateUp) } R.string.info_suspend_app)
composable<ClearAppStorage> { ClearAppStorageScreen(::navigateUp) } }
composable<UninstallApp> { UninstallAppScreen(::navigateUp) } composable<Hide> {
composable<KeepUninstalledPackages> { KeepUninstalledPackagesScreen(::navigateUp) } PackageFunctionScreen(R.string.hide, vm.hiddenPackages, vm::getHiddenPackages,
composable<InstallExistingApp> { InstallExistingAppScreen(::navigateUp) } vm::setPackageHidden, ::navigateUp, vm.chosenPackage, ::choosePackage)
composable<CrossProfilePackages> { CrossProfilePackagesScreen(::navigateUp) } }
composable<CrossProfileWidgetProviders> { CrossProfileWidgetProvidersScreen(::navigateUp) } composable<BlockUninstall> {
composable<CredentialManagerPolicy> { CredentialManagerPolicyScreen(::navigateUp) } PackageFunctionScreenWithoutResult(R.string.block_uninstall, vm.ubPackages,
composable<PermittedAccessibilityServices> { PermittedAccessibilityServicesScreen(::navigateUp) } vm::getUbPackages, vm::setPackageUb, ::navigateUp, vm.chosenPackage, ::choosePackage)
composable<PermittedInputMethods> { PermittedInputMethodsScreen(::navigateUp) } }
composable<EnableSystemApp> { EnableSystemAppScreen(::navigateUp) } composable<DisableUserControl> {
composable<SetDefaultDialer> { SetDefaultDialerScreen(::navigateUp) } PackageFunctionScreenWithoutResult(R.string.disable_user_control, vm.ucdPackages,
vm::getUcdPackages, vm::setPackageUcd, ::navigateUp, vm.chosenPackage,
::choosePackage, R.string.info_disable_user_control)
}
composable<PermissionsManager> {
PermissionsManagerScreen(vm.packagePermissions, vm::getPackagePermissions,
vm::setPackagePermission, ::navigateUp, it.toRoute(), vm.chosenPackage, ::choosePackage)
}
composable<DisableMeteredData> {
PackageFunctionScreen(R.string.disable_metered_data, vm.mddPackages,
vm::getMddPackages, vm::setPackageMdd, ::navigateUp, vm.chosenPackage, ::choosePackage)
}
composable<ClearAppStorage> {
ClearAppStorageScreen(vm.chosenPackage, ::choosePackage, vm::clearAppData, ::navigateUp)
}
composable<UninstallApp> {
UninstallAppScreen(vm.chosenPackage, ::choosePackage, vm::uninstallPackage, ::navigateUp)
}
composable<KeepUninstalledPackages> {
PackageFunctionScreenWithoutResult(R.string.keep_uninstalled_packages, vm.kuPackages,
vm::getKuPackages, vm::setPackageKu, ::navigateUp, vm.chosenPackage,
::choosePackage, R.string.info_keep_uninstalled_apps)
}
composable<InstallExistingApp> {
InstallExistingAppScreen(vm.chosenPackage, ::choosePackage,
vm::installExistingApp, ::navigateUp)
}
composable<CrossProfilePackages> {
PackageFunctionScreenWithoutResult(R.string.cross_profile_apps, vm.cpPackages,
vm::getCpPackages, vm::setPackageCp, ::navigateUp, vm.chosenPackage, ::choosePackage)
}
composable<CrossProfileWidgetProviders> {
PackageFunctionScreen(R.string.cross_profile_widget, vm.cpwProviders,
vm::getCpwProviders, vm::setCpwProvider, ::navigateUp, vm.chosenPackage, ::choosePackage)
}
composable<CredentialManagerPolicy> {
CredentialManagerPolicyScreen(vm.chosenPackage, ::choosePackage,
vm.cmPackages, vm::getCmPolicy, vm::setCmPackage, vm::setCmPolicy, ::navigateUp)
}
composable<PermittedAccessibilityServices> {
PermittedAsAndImPackages(R.string.permitted_accessibility_services,
R.string.system_accessibility_always_allowed, vm.chosenPackage, ::choosePackage,
vm.pasPackages, vm::getPasPackages, vm::setPasPackage, vm::setPasPolicy, ::navigateUp)
}
composable<PermittedInputMethods> {
PermittedAsAndImPackages(R.string.permitted_ime, R.string.system_ime_always_allowed,
vm.chosenPackage, ::choosePackage, vm.pimPackages, vm::getPimPackages,
vm::setPimPackage, vm::setPimPolicy, ::navigateUp)
}
composable<EnableSystemApp> {
EnableSystemAppScreen(vm.chosenPackage, ::choosePackage, vm::enableSystemApp, ::navigateUp)
}
composable<SetDefaultDialer> {
SetDefaultDialerScreen(vm.chosenPackage, ::choosePackage, vm::setDefaultDialer, ::navigateUp)
}
composable<UserRestriction> { composable<UserRestriction> {
UserRestrictionScreen(::navigateUp) { UserRestrictionScreen(::navigateUp) {
@@ -497,7 +566,10 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) {
}, },
contentWindowInsets = WindowInsets.ime contentWindowInsets = WindowInsets.ime
) { ) {
Column(Modifier.fillMaxSize().padding(it).verticalScroll(rememberScrollState())) { Column(Modifier
.fillMaxSize()
.padding(it)
.verticalScroll(rememberScrollState())) {
if(privilege.device || privilege.profile) { if(privilege.device || privilege.profile) {
HomePageItem(R.string.system, R.drawable.android_fill0) { onNavigate(SystemManager) } HomePageItem(R.string.system, R.drawable.android_fill0) { onNavigate(SystemManager) }
HomePageItem(R.string.network, R.drawable.wifi_fill0) { onNavigate(Network) } HomePageItem(R.string.network, R.drawable.wifi_fill0) { onNavigate(Network) }

View File

@@ -1,10 +1,41 @@
package com.bintianqi.owndroid package com.bintianqi.owndroid
import android.app.Application import android.app.Application
import android.app.PendingIntent
import android.app.admin.PackagePolicy
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.os.Build.VERSION
import androidx.annotation.RequiresApi
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.application
import androidx.lifecycle.viewModelScope
import com.bintianqi.owndroid.Privilege.DAR
import com.bintianqi.owndroid.Privilege.DPM
import com.bintianqi.owndroid.dpm.AppStatus
import com.bintianqi.owndroid.dpm.getPackageInstaller
import com.bintianqi.owndroid.dpm.isValidPackageName
import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage
import com.bintianqi.owndroid.dpm.permissionList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.util.concurrent.Executors
class MyViewModel(application: Application): AndroidViewModel(application) { class MyViewModel(application: Application): AndroidViewModel(application) {
val PM = application.packageManager
val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme)) val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme))
fun changeTheme(newTheme: ThemeSettings) { fun changeTheme(newTheme: ThemeSettings) {
theme.value = newTheme theme.value = newTheme
@@ -12,6 +43,340 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
SP.darkTheme = newTheme.darkTheme SP.darkTheme = newTheme.darkTheme
SP.blackTheme = newTheme.blackTheme SP.blackTheme = newTheme.blackTheme
} }
val chosenPackage = Channel<String>(1, BufferOverflow.DROP_LATEST)
val installedPackages = MutableStateFlow(emptyList<AppInfo>())
val refreshPackagesProgress = MutableStateFlow(0F)
fun refreshPackageList() {
viewModelScope.launch(Dispatchers.IO) {
installedPackages.value = emptyList()
val apps = PM.getInstalledApplications(getInstalledAppsFlags)
apps.forEachIndexed { index, info ->
installedPackages.update {
it + getAppInfo(info)
}
refreshPackagesProgress.value = (index + 1).toFloat() / apps.size
}
}
}
fun getAppInfo(info: ApplicationInfo) =
AppInfo(info.packageName, info.loadLabel(PM).toString(), info.loadIcon(PM), info.flags)
fun getAppInfo(name: String): AppInfo {
return try {
getAppInfo(PM.getApplicationInfo(name, getInstalledAppsFlags))
} catch (_: PackageManager.NameNotFoundException) {
AppInfo(name, "???", Color.Transparent.toArgb().toDrawable(), 0)
}
}
val suspendedPackages = MutableStateFlow(emptyList<AppInfo>())
@RequiresApi(24)
fun getSuspendedPackaged() {
val packages = PM.getInstalledApplications(getInstalledAppsFlags).filter {
DPM.isPackageSuspended(DAR, it.packageName)
}
suspendedPackages.value = packages.map { getAppInfo(it) }
}
@RequiresApi(24)
fun setPackageSuspended(name: String, status: Boolean): Boolean {
val result = DPM.setPackagesSuspended(DAR, arrayOf(name), status)
getSuspendedPackaged()
return result.isEmpty()
}
val hiddenPackages = MutableStateFlow(emptyList<AppInfo>())
fun getHiddenPackages() {
viewModelScope.launch {
hiddenPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter {
DPM.isApplicationHidden(DAR, it.packageName)
}.map { getAppInfo(it) }
}
}
fun setPackageHidden(name: String, status: Boolean): Boolean {
val result = DPM.setApplicationHidden(DAR, name, status)
getHiddenPackages()
return result
}
// Uninstall blocked packages
val ubPackages = MutableStateFlow(emptyList<AppInfo>())
fun getUbPackages() {
viewModelScope.launch {
ubPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter {
DPM.isUninstallBlocked(DAR, it.packageName)
}.map { getAppInfo(it) }
}
}
fun setPackageUb(name: String, status: Boolean) {
DPM.setUninstallBlocked(DAR, name, status)
getUbPackages()
}
// User control disabled packages
val ucdPackages = MutableStateFlow(emptyList<AppInfo>())
@RequiresApi(30)
fun getUcdPackages() {
ucdPackages.value = DPM.getUserControlDisabledPackages(DAR).map {
getAppInfo(it)
}
}
@RequiresApi(30)
fun setPackageUcd(name: String, status: Boolean) {
DPM.setUserControlDisabledPackages(
DAR,
ucdPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) }
)
getUcdPackages()
}
val packagePermissions = MutableStateFlow(emptyMap<String, Int>())
@RequiresApi(23)
fun getPackagePermissions(name: String) {
if (name.isValidPackageName) {
packagePermissions.value = permissionList().associate {
it.permission to DPM.getPermissionGrantState(DAR, name, it.permission)
}
} else {
packagePermissions.value = emptyMap()
}
}
@RequiresApi(23)
fun setPackagePermission(name: String, permission: String, status: Int): Boolean {
val result = DPM.setPermissionGrantState(DAR, name, permission, status)
getPackagePermissions(name)
return result
}
// Metered data disabled packages
val mddPackages = MutableStateFlow(emptyList<AppInfo>())
@RequiresApi(28)
fun getMddPackages() {
mddPackages.value = DPM.getMeteredDataDisabledPackages(DAR).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) }
)
getMddPackages()
return result.isEmpty()
}
// Keep uninstalled packages
val kuPackages = MutableStateFlow(emptyList<AppInfo>())
@RequiresApi(28)
fun getKuPackages() {
kuPackages.value = DPM.getKeepUninstalledPackages(DAR)?.map { getAppInfo(it) } ?: emptyList()
}
@RequiresApi(28)
fun setPackageKu(name: String, status: Boolean) {
DPM.setKeepUninstalledPackages(
DAR, kuPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) }
)
getKuPackages()
}
// Cross profile packages
val cpPackages = MutableStateFlow(emptyList<AppInfo>())
@RequiresApi(30)
fun getCpPackages() {
cpPackages.value = DPM.getCrossProfilePackages(DAR).map { getAppInfo(it) }
}
@RequiresApi(30)
fun setPackageCp(name: String, status: Boolean) {
DPM.setCrossProfilePackages(
DAR,
cpPackages.value.map { it.name }.toSet().run { if (status) plus(name) else minus(name) }
)
getCpPackages()
}
// Cross-profile widget providers
val cpwProviders = MutableStateFlow(emptyList<AppInfo>())
fun getCpwProviders() {
cpwProviders.value = DPM.getCrossProfileWidgetProviders(DAR).map { getAppInfo(it) }
}
fun setCpwProvider(name: String, status: Boolean): Boolean {
val result = if (status) {
DPM.addCrossProfileWidgetProvider(DAR, name)
} else {
DPM.removeCrossProfileWidgetProvider(DAR, name)
}
getCpwProviders()
return result
}
@RequiresApi(28)
fun clearAppData(name: String, callback: (Boolean) -> Unit) {
DPM.clearApplicationUserData(DAR, name, Executors.newSingleThreadExecutor()) { _, result ->
callback(result)
}
}
fun uninstallPackage(packageName: String, onComplete: (String?) -> Unit) {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999)
if(statusExtra == PackageInstaller.STATUS_PENDING_USER_ACTION) {
@SuppressWarnings("UnsafeIntentLaunch")
context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT) as Intent?)
} else {
context.unregisterReceiver(this)
if(statusExtra == PackageInstaller.STATUS_SUCCESS) {
onComplete(null)
} else {
onComplete(parsePackageInstallerMessage(context, intent))
}
}
}
}
ContextCompat.registerReceiver(
application, receiver, IntentFilter(AppInstallerViewModel.ACTION), null,
null, ContextCompat.RECEIVER_EXPORTED
)
val pi = if(VERSION.SDK_INT >= 34) {
PendingIntent.getBroadcast(
application, 0, Intent(AppInstallerViewModel.ACTION),
PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE
).intentSender
} else {
PendingIntent.getBroadcast(application, 0, Intent(AppInstallerViewModel.ACTION), PendingIntent.FLAG_MUTABLE).intentSender
}
application.getPackageInstaller().uninstall(packageName, pi)
}
@RequiresApi(28)
fun installExistingApp(name: String): Boolean {
return DPM.installExistingPackage(DAR, name)
}
// Credential manager policy
val cmPackages = MutableStateFlow(emptyList<AppInfo>())
@RequiresApi(34)
fun getCmPolicy(): Int {
return DPM.credentialManagerPolicy?.let { policy ->
cmPackages.value = policy.packageNames.map { getAppInfo(it) }
policy.policyType
} ?: -1
}
fun setCmPackage(name: String, status: Boolean) {
cmPackages.update { list ->
if (status) list + getAppInfo(name) else list.dropWhile { it.name == name }
}
}
@RequiresApi(34)
fun setCmPolicy(type: Int) {
DPM.credentialManagerPolicy = if (type != -1 && cmPackages.value.isNotEmpty()) {
PackagePolicy(type, cmPackages.value.map { it.name }.toSet())
} else null
getCmPolicy()
}
// Permitted input method
val pimPackages = MutableStateFlow(emptyList<AppInfo>())
fun getPimPackages(): Boolean {
return DPM.getPermittedInputMethods(DAR).let { packages ->
pimPackages.value = packages?.map { getAppInfo(it) } ?: emptyList()
packages == null
}
}
fun setPimPackage(name: String, status: Boolean) {
pimPackages.update { packages ->
if (status) packages + getAppInfo(name) else packages.dropWhile { it.name == name }
}
}
fun setPimPolicy(allowAll: Boolean): Boolean {
val result = DPM.setPermittedInputMethods(
DAR, if (allowAll) null else pimPackages.value.map { it.name })
getPimPackages()
return result
}
// Permitted accessibility services
val pasPackages = MutableStateFlow(emptyList<AppInfo>())
fun getPasPackages(): Boolean {
return DPM.getPermittedAccessibilityServices(DAR).let { packages ->
pasPackages.value = packages?.map { getAppInfo(it) } ?: emptyList()
packages == null
}
}
fun setPasPackage(name: String, status: Boolean) {
pasPackages.update { packages ->
if (status) packages + getAppInfo(name) else packages.dropWhile { it.name == name }
}
}
fun setPasPolicy(allowAll: Boolean): Boolean {
val result = DPM.setPermittedAccessibilityServices(
DAR, if (allowAll) null else pasPackages.value.map { it.name })
getPasPackages()
return result
}
fun enableSystemApp(name: String) {
DPM.enableSystemApp(DAR, name)
}
val appStatus = MutableStateFlow(AppStatus(false, false, false, false, false, false))
fun getAppStatus(name: String) {
appStatus.value = AppStatus(
if (VERSION.SDK_INT >= 24) DPM.isPackageSuspended(DAR, name) else false,
DPM.isApplicationHidden(DAR, name),
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
)
}
// 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)) }
}
fun adSetPackageHidden(name: String, status: Boolean) {
DPM.setApplicationHidden(DAR, name, status)
appStatus.update { it.copy(hide = DPM.isApplicationHidden(DAR, name)) }
}
fun adSetPackageUb(name: String, status: Boolean) {
DPM.setUninstallBlocked(DAR, name, status)
appStatus.update { it.copy(uninstallBlocked = DPM.isUninstallBlocked(DAR, name)) }
}
@RequiresApi(30)
fun adSetPackageUcd(name: String, status: Boolean) {
DPM.setUserControlDisabledPackages(DAR,
DPM.getUserControlDisabledPackages(DAR).run { if (status) plus(name) else minus(name) })
appStatus.update {
it.copy(userControlDisabled = name in DPM.getUserControlDisabledPackages(DAR))
}
}
@RequiresApi(28)
fun adSetPackageMdd(name: String, status: Boolean) {
DPM.setMeteredDataDisabledPackages(DAR,
DPM.getMeteredDataDisabledPackages(DAR).run { if (status) plus(name) else minus(name) })
appStatus.update {
it.copy(meteredDataDisabled = name in DPM.getMeteredDataDisabledPackages(DAR))
}
}
@RequiresApi(28)
fun adSetPackageKu(name: String, status: Boolean) {
DPM.setKeepUninstalledPackages(DAR,
DPM.getKeepUninstalledPackages(DAR)?.run { if (status) plus(name) else minus(name) } ?: emptyList())
appStatus.update {
it.copy(keepUninstalled = DPM.getKeepUninstalledPackages(DAR)?.contains(name) == true )
}
}
@RequiresApi(34)
fun setDefaultDialer(name: String): Boolean {
return try {
DPM.setDefaultDialerApplication(name)
true
} catch (e: IllegalArgumentException) {
e.printStackTrace()
false
}
}
} }
data class ThemeSettings( data class ThemeSettings(

View File

@@ -1,16 +1,9 @@
package com.bintianqi.owndroid package com.bintianqi.owndroid
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@@ -44,10 +37,8 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -62,35 +53,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.google.accompanist.drawablepainter.rememberDrawablePainter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
class PackageChooserActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vm by viewModels<MyViewModel>()
enableEdgeToEdge()
setContent {
val theme by vm.theme.collectAsStateWithLifecycle()
OwnDroidTheme(theme) {
AppChooserScreen(ApplicationsList(false), {
setResult(0, Intent().putExtra("package", it))
finish()
}, {})
}
}
}
}
val installedApps = MutableStateFlow(emptyList<AppInfo>())
data class AppInfo( data class AppInfo(
val name: String, val name: String,
val label: String, val label: String,
@@ -105,11 +71,14 @@ private fun searchInString(query: String, content: String)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Unit, onSwitchView: () -> Unit) { fun AppChooserScreen(
val packages by installedApps.collectAsStateWithLifecycle() canSwitchView: Boolean, packageList: MutableStateFlow<List<AppInfo>>,
val coroutine = rememberCoroutineScope() refreshProgress: MutableStateFlow<Float>, onChoosePackage: (String?) -> Unit,
onSwitchView: () -> Unit, onRefresh: () -> Unit
) {
val packages by packageList.collectAsStateWithLifecycle()
val context = LocalContext.current val context = LocalContext.current
var progress by remember { mutableFloatStateOf(1F) } val progress by refreshProgress.collectAsStateWithLifecycle()
var system by rememberSaveable { mutableStateOf(false) } var system by rememberSaveable { mutableStateOf(false) }
var query by rememberSaveable { mutableStateOf("") } var query by rememberSaveable { mutableStateOf("") }
var searchMode by rememberSaveable { mutableStateOf(false) } var searchMode by rememberSaveable { mutableStateOf(false) }
@@ -119,7 +88,7 @@ fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Uni
} }
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if(packages.size <= 1) getInstalledApps(coroutine, context) { progress = it } if(packages.size <= 1) onRefresh()
} }
Scaffold( Scaffold(
topBar = { topBar = {
@@ -135,20 +104,17 @@ fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Uni
}) { }) {
Icon(painter = painterResource(R.drawable.filter_alt_fill0), contentDescription = null) Icon(painter = painterResource(R.drawable.filter_alt_fill0), contentDescription = null)
} }
IconButton( IconButton(onRefresh, enabled = progress == 1F) {
{ getInstalledApps(coroutine, context) { progress = it } },
enabled = progress == 1F
) {
Icon(painter = painterResource(R.drawable.refresh_fill0), contentDescription = null) Icon(painter = painterResource(R.drawable.refresh_fill0), contentDescription = null)
} }
if(params.canSwitchView) IconButton(onSwitchView) { if (canSwitchView) IconButton(onSwitchView) {
Icon(Icons.AutoMirrored.Default.List, null) Icon(Icons.AutoMirrored.Default.List, null)
} }
} }
}, },
title = { title = {
if(searchMode) { if(searchMode) {
val fr = FocusRequester() val fr = remember { FocusRequester() }
LaunchedEffect(Unit) { fr.requestFocus() } LaunchedEffect(Unit) { fr.requestFocus() }
OutlinedTextField( OutlinedTextField(
value = query, value = query,
@@ -213,23 +179,5 @@ fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Uni
} }
} }
fun getInstalledApps(scope: CoroutineScope, context: Context, onProgressUpdated: (Float) -> Unit) {
installedApps.value = emptyList()
scope.launch(Dispatchers.IO) {
val pm = context.packageManager
val apps = pm.getInstalledApplications(getInstalledAppsFlags)
for(pkg in apps) {
val label = pkg.loadLabel(pm).toString()
val icon = pkg.loadIcon(pm)
withContext(Dispatchers.Main) {
installedApps.update {
it + AppInfo(pkg.packageName, label, icon, pkg.flags)
}
onProgressUpdated(installedApps.value.size.toFloat() / apps.size)
}
}
}
}
val getInstalledAppsFlags = val getInstalledAppsFlags =
if(Build.VERSION.SDK_INT >= 24) PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_UNINSTALLED_PACKAGES else 0 if(Build.VERSION.SDK_INT >= 24) PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_UNINSTALLED_PACKAGES else 0

View File

@@ -233,28 +233,38 @@ fun AppLockSettingsScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.app_lo
var confirmPassword by remember { mutableStateOf("") } var confirmPassword by remember { mutableStateOf("") }
var allowBiometrics by remember { mutableStateOf(SP.biometricsUnlock) } var allowBiometrics by remember { mutableStateOf(SP.biometricsUnlock) }
var lockWhenLeaving by remember { mutableStateOf(SP.lockWhenLeaving) } var lockWhenLeaving by remember { mutableStateOf(SP.lockWhenLeaving) }
val fr = FocusRequester() val fr = remember { FocusRequester() }
val alreadySet = !SP.lockPasswordHash.isNullOrEmpty() val alreadySet = !SP.lockPasswordHash.isNullOrEmpty()
val isInputLegal = password.length !in 1..3 && (alreadySet || (password.isNotEmpty() && password.isNotBlank())) val isInputLegal = password.length !in 1..3 && (alreadySet || (password.isNotEmpty() && password.isNotBlank()))
Column(Modifier.widthIn(max = 300.dp).align(Alignment.CenterHorizontally)) { Column(Modifier
.widthIn(max = 300.dp)
.align(Alignment.CenterHorizontally)) {
OutlinedTextField( OutlinedTextField(
password, { password = it }, Modifier.fillMaxWidth().padding(vertical = 4.dp), password, { password = it }, Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
label = { Text(stringResource(R.string.password)) }, label = { Text(stringResource(R.string.password)) },
supportingText = { Text(stringResource(if(alreadySet) R.string.leave_empty_to_remain_unchanged else R.string.minimum_length_4)) }, supportingText = { Text(stringResource(if(alreadySet) R.string.leave_empty_to_remain_unchanged else R.string.minimum_length_4)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next),
keyboardActions = KeyboardActions { fr.requestFocus() } keyboardActions = KeyboardActions { fr.requestFocus() }
) )
OutlinedTextField( OutlinedTextField(
confirmPassword, { confirmPassword = it }, Modifier.fillMaxWidth().focusRequester(fr), confirmPassword, { confirmPassword = it }, Modifier
.fillMaxWidth()
.focusRequester(fr),
label = { Text(stringResource(R.string.confirm_password)) }, label = { Text(stringResource(R.string.confirm_password)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { fm.clearFocus() } keyboardActions = KeyboardActions { fm.clearFocus() }
) )
if(VERSION.SDK_INT >= 28) Row(Modifier.fillMaxWidth().padding(vertical = 6.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) { if(VERSION.SDK_INT >= 28) Row(Modifier
.fillMaxWidth()
.padding(vertical = 6.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) {
Text(stringResource(R.string.allow_biometrics)) Text(stringResource(R.string.allow_biometrics))
Switch(allowBiometrics, { allowBiometrics = it }) Switch(allowBiometrics, { allowBiometrics = it })
} }
Row(Modifier.fillMaxWidth().padding(bottom = 6.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) { Row(Modifier
.fillMaxWidth()
.padding(bottom = 6.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) {
Text(stringResource(R.string.lock_when_leaving)) Text(stringResource(R.string.lock_when_leaving))
Switch(lockWhenLeaving, { lockWhenLeaving = it }) Switch(lockWhenLeaving, { lockWhenLeaving = it })
} }
@@ -302,7 +312,9 @@ fun ApiSettings(onNavigateUp: () -> Unit) {
var key by remember { mutableStateOf("") } var key by remember { mutableStateOf("") }
OutlinedTextField( OutlinedTextField(
value = key, onValueChange = { key = it }, label = { Text(stringResource(R.string.api_key)) }, value = key, onValueChange = { key = it }, label = { Text(stringResource(R.string.api_key)) },
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp), readOnly = true, modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp), readOnly = true,
trailingIcon = { trailingIcon = {
IconButton( IconButton(
onClick = { onClick = {
@@ -316,7 +328,9 @@ fun ApiSettings(onNavigateUp: () -> Unit) {
} }
) )
Button( Button(
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp),
onClick = { onClick = {
SP.apiKey = key SP.apiKey = key
context.showOperationResultToast(true) context.showOperationResultToast(true)

View File

@@ -4,13 +4,11 @@ import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.Saver
@@ -107,13 +105,6 @@ inline fun <reified T> serializableNavTypePair() =
Json.encodeToString(value) Json.encodeToString(value)
} }
class ChoosePackageContract: ActivityResultContract<Nothing?, String?>() {
override fun createIntent(context: Context, input: Nothing?): Intent =
Intent(context, PackageChooserActivity::class.java)
override fun parseResult(resultCode: Int, intent: Intent?): String? =
intent?.getStringExtra("package")
}
fun exportLogs(context: Context, uri: Uri) { fun exportLogs(context: Context, uri: Uri) {
context.contentResolver.openOutputStream(uri)?.use { output -> context.contentResolver.openOutputStream(uri)?.use { output ->
val proc = Runtime.getRuntime().exec("logcat -d") val proc = Runtime.getRuntime().exec("logcat -d")

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,6 @@ import com.bintianqi.owndroid.createShortcuts
import com.rosan.dhizuku.api.Dhizuku import com.rosan.dhizuku.api.Dhizuku
import com.rosan.dhizuku.api.DhizukuBinderWrapper import com.rosan.dhizuku.api.DhizukuBinderWrapper
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive

View File

@@ -127,7 +127,6 @@ import androidx.compose.ui.unit.dp
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ChoosePackageContract
import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.HorizontalPadding
import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.Privilege
import com.bintianqi.owndroid.R import com.bintianqi.owndroid.R
@@ -153,6 +152,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -886,7 +886,10 @@ fun NetworkStats.toBucketList(): List<NetworkStats.Bucket> {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@RequiresApi(23) @RequiresApi(23)
@Composable @Composable
fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkStatsViewer) -> Unit) { fun NetworkStatsScreen(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkStatsViewer) -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
val privilege by Privilege.status.collectAsStateWithLifecycle() val privilege by Privilege.status.collectAsStateWithLifecycle()
val fm = LocalFocusManager.current val fm = LocalFocusManager.current
@@ -1053,16 +1056,14 @@ fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkSta
) { ) {
var uidText by rememberSaveable { mutableStateOf(context.getString(NetworkStatsUID.All.strRes)) } var uidText by rememberSaveable { mutableStateOf(context.getString(NetworkStatsUID.All.strRes)) }
var readOnly by rememberSaveable { mutableStateOf(true) } var readOnly by rememberSaveable { mutableStateOf(true) }
if(!readOnly && uidText.toIntOrNull() != null) uid = uidText.toInt() if (!readOnly && uidText.toIntOrNull() != null) uid = uidText.toInt()
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { if (VERSION.SDK_INT >= 24) LaunchedEffect(Unit) {
it ?: return@rememberLauncherForActivityResult val pkg = chosenPackage.receive()
if(VERSION.SDK_INT >= 24 && readOnly) { try {
try { uid = context.packageManager.getPackageUid(pkg, 0)
uid = context.packageManager.getPackageUid(it, 0) uidText = "$uid ($pkg)"
uidText = "$it ($uid)" } catch(_: NameNotFoundException) {
} catch(_: NameNotFoundException) { context.showOperationResultToast(false)
context.showOperationResultToast(false)
}
} }
} }
OutlinedTextField( OutlinedTextField(
@@ -1093,7 +1094,7 @@ fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkSta
onClick = { onClick = {
readOnly = true readOnly = true
activeTextField = NetworkStatsActiveTextField.None activeTextField = NetworkStatsActiveTextField.None
choosePackage.launch(null) onChoosePackage()
} }
) )
DropdownMenuItem( DropdownMenuItem(
@@ -1457,15 +1458,18 @@ fun PrivateDnsScreen(onNavigateUp: () -> Unit) {
@RequiresApi(24) @RequiresApi(24)
@Composable @Composable
fun AlwaysOnVpnPackageScreen(onNavigateUp: () -> Unit) { fun AlwaysOnVpnPackageScreen(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, onNavigateUp: () -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
var lockdown by rememberSaveable { mutableStateOf(false) } var lockdown by rememberSaveable { mutableStateOf(false) }
var pkgName by rememberSaveable { mutableStateOf("") } var pkgName by rememberSaveable { mutableStateOf("") }
val focusMgr = LocalFocusManager.current fun refresh() {
val refresh = { pkgName = Privilege.DPM.getAlwaysOnVpnPackage(Privilege.DAR) ?: "" } pkgName = Privilege.DPM.getAlwaysOnVpnPackage(Privilege.DAR) ?: ""
LaunchedEffect(Unit) { refresh() } }
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result -> LaunchedEffect(Unit) {
result?.let { pkgName = it } refresh()
pkgName = chosenPackage.receive()
} }
val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean -> val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean ->
try { try {
@@ -1483,21 +1487,8 @@ fun AlwaysOnVpnPackageScreen(onNavigateUp: () -> Unit) {
} }
} }
MyScaffold(R.string.always_on_vpn, onNavigateUp) { MyScaffold(R.string.always_on_vpn, onNavigateUp) {
OutlinedTextField( PackageNameTextField(pkgName, onChoosePackage,
value = pkgName, Modifier.padding(vertical = 4.dp)) { pkgName = it }
onValueChange = { pkgName = it },
label = { Text(stringResource(R.string.package_name)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
trailingIcon = {
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable { choosePackage.launch(null) }
.padding(3.dp))
},
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
)
SwitchItem(R.string.enable_lockdown, state = lockdown, onCheckedChange = { lockdown = it }, padding = false) SwitchItem(R.string.enable_lockdown, state = lockdown, onCheckedChange = { lockdown = it }, padding = false)
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
Button( Button(
@@ -2067,7 +2058,7 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) {
keyboardActions = KeyboardActions { fm.clearFocus() } keyboardActions = KeyboardActions { fm.clearFocus() }
) )
if(VERSION.SDK_INT >= 33) Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), Arrangement.SpaceBetween) { if(VERSION.SDK_INT >= 33) Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), Arrangement.SpaceBetween) {
val fr = FocusRequester() val fr = remember { FocusRequester() }
OutlinedTextField( OutlinedTextField(
mtuV4, { mtuV4 = it }, Modifier.fillMaxWidth(0.49F), mtuV4, { mtuV4 = it }, Modifier.fillMaxWidth(0.49F),
label = { Text("MTU (IPv4)") }, label = { Text("MTU (IPv4)") },
@@ -2206,7 +2197,7 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) {
if(dialog != 0) { if(dialog != 0) {
var address by remember { mutableStateOf((if(dialog == 1) proxyAddress else mmsProxyAddress)) } var address by remember { mutableStateOf((if(dialog == 1) proxyAddress else mmsProxyAddress)) }
var port by remember { mutableStateOf((if(dialog == 1) proxyPort else mmsProxyPort)) } var port by remember { mutableStateOf((if(dialog == 1) proxyPort else mmsProxyPort)) }
val fr = FocusRequester() val fr = remember { FocusRequester() }
AlertDialog( AlertDialog(
title = { Text(if(dialog == 1) "Proxy" else "MMS proxy") }, title = { Text(if(dialog == 1) "Proxy" else "MMS proxy") },
text = { text = {

View File

@@ -6,7 +6,6 @@ import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.PersistableBundle import android.os.PersistableBundle
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.StringRes import androidx.annotation.StringRes
@@ -84,10 +83,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ChoosePackageContract
import com.bintianqi.owndroid.DHIZUKU_CLIENTS_FILE import com.bintianqi.owndroid.DHIZUKU_CLIENTS_FILE
import com.bintianqi.owndroid.DhizukuClientInfo import com.bintianqi.owndroid.DhizukuClientInfo
import com.bintianqi.owndroid.DhizukuPermissions import com.bintianqi.owndroid.DhizukuPermissions
@@ -113,6 +110,7 @@ import com.google.accompanist.drawablepainter.rememberDrawablePainter
import com.rosan.dhizuku.api.Dhizuku import com.rosan.dhizuku.api.Dhizuku
import com.rosan.dhizuku.api.DhizukuRequestPermissionListener import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -731,30 +729,19 @@ fun DelegatedAdminsScreen(onNavigateUp: () -> Unit, onNavigate: (AddDelegatedAdm
@RequiresApi(26) @RequiresApi(26)
@Composable @Composable
fun AddDelegatedAdminScreen(data: AddDelegatedAdmin, onNavigateUp: () -> Unit) { fun AddDelegatedAdminScreen(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
data: AddDelegatedAdmin, onNavigateUp: () -> Unit
) {
val updateMode = data.pkg.isNotEmpty() val updateMode = data.pkg.isNotEmpty()
val fm = LocalFocusManager.current
var input by remember { mutableStateOf(data.pkg) } var input by remember { mutableStateOf(data.pkg) }
val scopes = remember { mutableStateListOf(*data.scopes.toTypedArray()) } val scopes = remember { mutableStateListOf(*data.scopes.toTypedArray()) }
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result -> LaunchedEffect(Unit) {
result?.let { input = it } input = chosenPackage.receive()
} }
MySmallTitleScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, onNavigateUp, 0.dp) { MySmallTitleScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, onNavigateUp, 0.dp) {
OutlinedTextField( PackageNameTextField(input, onChoosePackage,
value = input, onValueChange = { input = it }, Modifier.padding(HorizontalPadding, 8.dp)) { input = it }
label = { Text(stringResource(R.string.package_name)) },
trailingIcon = {
if(!updateMode) IconButton({ choosePackage.launch(null) }) {
Icon(painterResource(R.drawable.list_fill0), null)
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { fm.clearFocus() },
readOnly = updateMode,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = HorizontalPadding)
)
DelegatedScope.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach { scope -> DelegatedScope.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach { scope ->
val checked = scope in scopes val checked = scope in scopes
Row( Row(

View File

@@ -112,13 +112,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ChoosePackageContract
import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.HorizontalPadding
import com.bintianqi.owndroid.NotificationUtils import com.bintianqi.owndroid.NotificationUtils
import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.Privilege
@@ -143,6 +141,7 @@ import com.bintianqi.owndroid.ui.Notes
import com.bintianqi.owndroid.ui.SwitchItem import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.uriToStream import com.bintianqi.owndroid.uriToStream
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -1134,7 +1133,9 @@ fun NearbyStreamingPolicyScreen(onNavigateUp: () -> Unit) {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@RequiresApi(28) @RequiresApi(28)
@Composable @Composable
fun LockTaskModeScreen(onNavigateUp: () -> Unit) { fun LockTaskModeScreen(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, onNavigateUp: () -> Unit
) {
val coroutine = rememberCoroutineScope() val coroutine = rememberCoroutineScope()
val pagerState = rememberPagerState { 3 } val pagerState = rememberPagerState { 3 }
var tabIndex by remember { mutableIntStateOf(0) } var tabIndex by remember { mutableIntStateOf(0) }
@@ -1177,8 +1178,8 @@ fun LockTaskModeScreen(onNavigateUp: () -> Unit) {
.padding(horizontal = HorizontalPadding) .padding(horizontal = HorizontalPadding)
.padding(bottom = 80.dp) .padding(bottom = 80.dp)
) { ) {
if(page == 0) StartLockTaskMode() if(page == 0) StartLockTaskMode(chosenPackage, onChoosePackage)
else LockTaskPackages() else LockTaskPackages(chosenPackage, onChoosePackage)
} }
} else { } else {
Column( Column(
@@ -1197,33 +1198,20 @@ fun LockTaskModeScreen(onNavigateUp: () -> Unit) {
@RequiresApi(28) @RequiresApi(28)
@Composable @Composable
private fun ColumnScope.StartLockTaskMode() { private fun ColumnScope.StartLockTaskMode(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
var startLockTaskApp by rememberSaveable { mutableStateOf("") } var startLockTaskApp by rememberSaveable { mutableStateOf("") }
var startLockTaskActivity by rememberSaveable { mutableStateOf("") } var startLockTaskActivity by rememberSaveable { mutableStateOf("") }
var specifyActivity by rememberSaveable { mutableStateOf(false) } var specifyActivity by rememberSaveable { mutableStateOf(false) }
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result -> LaunchedEffect(Unit) {
result?.let { startLockTaskApp = it } startLockTaskApp = chosenPackage.receive()
} }
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField( PackageNameTextField(startLockTaskApp, onChoosePackage,
value = startLockTaskApp, Modifier.padding(vertical = 3.dp), { startLockTaskApp = it })
onValueChange = { startLockTaskApp = it },
label = { Text(stringResource(R.string.package_name)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
trailingIcon = {
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable { choosePackage.launch(null) }
.padding(3.dp))
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 3.dp)
)
CheckBoxItem(R.string.specify_activity, specifyActivity) { specifyActivity = it } CheckBoxItem(R.string.specify_activity, specifyActivity) { specifyActivity = it }
AnimatedVisibility(specifyActivity) { AnimatedVisibility(specifyActivity) {
OutlinedTextField( OutlinedTextField(
@@ -1264,37 +1252,23 @@ private fun ColumnScope.StartLockTaskMode() {
@RequiresApi(26) @RequiresApi(26)
@Composable @Composable
private fun ColumnScope.LockTaskPackages() { private fun ColumnScope.LockTaskPackages(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
val focusMgr = LocalFocusManager.current
val lockTaskPackages = remember { mutableStateListOf<String>() } val lockTaskPackages = remember { mutableStateListOf<String>() }
var input by rememberSaveable { mutableStateOf("") } var input by rememberSaveable { mutableStateOf("") }
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result -> LaunchedEffect(Unit) {
result?.let { input = it } lockTaskPackages.addAll(Privilege.DPM.getLockTaskPackages(Privilege.DAR))
input = chosenPackage.receive()
} }
LaunchedEffect(Unit) { lockTaskPackages.addAll(Privilege.DPM.getLockTaskPackages(Privilege.DAR)) }
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
if(lockTaskPackages.isEmpty()) Text(text = stringResource(R.string.none)) if(lockTaskPackages.isEmpty()) Text(text = stringResource(R.string.none))
for(i in lockTaskPackages) { for(i in lockTaskPackages) {
ListItem(i) { lockTaskPackages -= i } ListItem(i) { lockTaskPackages -= i }
} }
OutlinedTextField( PackageNameTextField(input, onChoosePackage,
value = input, Modifier.padding(vertical = 3.dp), { input = it })
onValueChange = { input = it },
label = { Text(stringResource(R.string.package_name)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
trailingIcon = {
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable { choosePackage.launch(null) }
.padding(3.dp))
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 3.dp)
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button( Button(
onClick = { onClick = {