mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-24 03:16:00 +00:00
Optimize Applications and PackageChooser
This commit is contained in:
@@ -34,7 +34,6 @@ import com.rosan.dhizuku.server_api.DhizukuService
|
||||
import com.rosan.dhizuku.shared.DhizukuVariables
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
private const val TAG = "DhizukuServer"
|
||||
|
||||
@@ -79,7 +79,6 @@ import com.bintianqi.owndroid.dpm.AutoTimePolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.AutoTimeZonePolicy
|
||||
import com.bintianqi.owndroid.dpm.AutoTimeZonePolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.BlockUninstall
|
||||
import com.bintianqi.owndroid.dpm.BlockUninstallScreen
|
||||
import com.bintianqi.owndroid.dpm.CaCert
|
||||
import com.bintianqi.owndroid.dpm.CaCertScreen
|
||||
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.CrossProfileIntentFilterScreen
|
||||
import com.bintianqi.owndroid.dpm.CrossProfilePackages
|
||||
import com.bintianqi.owndroid.dpm.CrossProfilePackagesScreen
|
||||
import com.bintianqi.owndroid.dpm.CrossProfileWidgetProviders
|
||||
import com.bintianqi.owndroid.dpm.CrossProfileWidgetProvidersScreen
|
||||
import com.bintianqi.owndroid.dpm.DelegatedAdmins
|
||||
import com.bintianqi.owndroid.dpm.DelegatedAdminsScreen
|
||||
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.DisableAccountManagementScreen
|
||||
import com.bintianqi.owndroid.dpm.DisableMeteredData
|
||||
import com.bintianqi.owndroid.dpm.DisableMeteredDataScreen
|
||||
import com.bintianqi.owndroid.dpm.DisableUserControl
|
||||
import com.bintianqi.owndroid.dpm.DisableUserControlScreen
|
||||
import com.bintianqi.owndroid.dpm.EnableSystemApp
|
||||
import com.bintianqi.owndroid.dpm.EnableSystemAppScreen
|
||||
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.HardwareMonitorScreen
|
||||
import com.bintianqi.owndroid.dpm.Hide
|
||||
import com.bintianqi.owndroid.dpm.HideScreen
|
||||
import com.bintianqi.owndroid.dpm.InstallExistingApp
|
||||
import com.bintianqi.owndroid.dpm.InstallExistingAppScreen
|
||||
import com.bintianqi.owndroid.dpm.InstallSystemUpdate
|
||||
import com.bintianqi.owndroid.dpm.InstallSystemUpdateScreen
|
||||
import com.bintianqi.owndroid.dpm.KeepUninstalledPackages
|
||||
import com.bintianqi.owndroid.dpm.KeepUninstalledPackagesScreen
|
||||
import com.bintianqi.owndroid.dpm.Keyguard
|
||||
import com.bintianqi.owndroid.dpm.KeyguardDisabledFeatures
|
||||
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.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
|
||||
@@ -166,9 +161,8 @@ import com.bintianqi.owndroid.dpm.PermissionPolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.PermissionsManager
|
||||
import com.bintianqi.owndroid.dpm.PermissionsManagerScreen
|
||||
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.PermittedInputMethodsScreen
|
||||
import com.bintianqi.owndroid.dpm.PreferentialNetworkService
|
||||
import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceScreen
|
||||
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.SuspendPersonalApp
|
||||
import com.bintianqi.owndroid.dpm.SuspendPersonalAppScreen
|
||||
import com.bintianqi.owndroid.dpm.SuspendScreen
|
||||
import com.bintianqi.owndroid.dpm.SystemManager
|
||||
import com.bintianqi.owndroid.dpm.SystemManagerScreen
|
||||
import com.bintianqi.owndroid.dpm.SystemOptions
|
||||
@@ -276,6 +269,9 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
fun navigateUp() { navController.navigateUp() }
|
||||
fun navigate(destination: Any) { navController.navigate(destination) }
|
||||
fun choosePackage() {
|
||||
navController.navigate(ApplicationsList(false))
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if(!Privilege.status.value.activated) {
|
||||
navController.navigate(WorkModes(false)) {
|
||||
@@ -306,7 +302,9 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
composable<DhizukuServerSettings> { DhizukuServerSettingsScreen(::navigateUp) }
|
||||
|
||||
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<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) }
|
||||
composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
|
||||
@@ -331,7 +329,9 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
composable<PermissionPolicy> { PermissionPolicyScreen(::navigateUp) }
|
||||
composable<MtePolicy> { MtePolicyScreen(::navigateUp) }
|
||||
composable<NearbyStreamingPolicy> { NearbyStreamingPolicyScreen(::navigateUp) }
|
||||
composable<LockTaskMode> { LockTaskModeScreen(::navigateUp) }
|
||||
composable<LockTaskMode> {
|
||||
LockTaskModeScreen(vm.chosenPackage, ::choosePackage, ::navigateUp)
|
||||
}
|
||||
composable<CaCert> { CaCertScreen(::navigateUp) }
|
||||
composable<SecurityLogging> { SecurityLoggingScreen(::navigateUp) }
|
||||
composable<DisableAccountManagement> { DisableAccountManagementScreen(::navigateUp) }
|
||||
@@ -346,12 +346,16 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) }
|
||||
composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::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>>())) {
|
||||
NetworkStatsViewerScreen(it.toRoute(), ::navigateUp)
|
||||
}
|
||||
composable<PrivateDns> { PrivateDnsScreen(::navigateUp) }
|
||||
composable<AlwaysOnVpnPackage> { AlwaysOnVpnPackageScreen(::navigateUp) }
|
||||
composable<AlwaysOnVpnPackage> {
|
||||
AlwaysOnVpnPackageScreen(vm.chosenPackage, ::choosePackage, ::navigateUp)
|
||||
}
|
||||
composable<RecommendedGlobalProxy> { RecommendedGlobalProxyScreen(::navigateUp) }
|
||||
composable<NetworkLogging> { NetworkLoggingScreen(::navigateUp) }
|
||||
composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) }
|
||||
@@ -368,14 +372,25 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
composable<DeleteWorkProfile> { DeleteWorkProfileScreen(::navigateUp) }
|
||||
|
||||
composable<ApplicationsList> {
|
||||
AppChooserScreen(it.toRoute(), { dest ->
|
||||
if(dest == null) navigateUp() else navigate(ApplicationDetails(dest))
|
||||
val canSwitchView = (it.toRoute() as ApplicationsList).canSwitchView
|
||||
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
|
||||
navController.navigate(ApplicationsFeatures) {
|
||||
popUpTo(Home)
|
||||
}
|
||||
})
|
||||
}, vm::refreshPackageList)
|
||||
}
|
||||
composable<ApplicationsFeatures> {
|
||||
ApplicationsFeaturesScreen(::navigateUp, ::navigate) {
|
||||
@@ -385,24 +400,78 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
}
|
||||
}
|
||||
}
|
||||
composable<ApplicationDetails> { ApplicationDetailsScreen(it.toRoute(), ::navigateUp, ::navigate) }
|
||||
composable<Suspend> { SuspendScreen(::navigateUp) }
|
||||
composable<Hide> { HideScreen(::navigateUp) }
|
||||
composable<BlockUninstall> { BlockUninstallScreen(::navigateUp) }
|
||||
composable<DisableUserControl> { DisableUserControlScreen(::navigateUp) }
|
||||
composable<PermissionsManager> { PermissionsManagerScreen(::navigateUp, it.toRoute()) }
|
||||
composable<DisableMeteredData> { DisableMeteredDataScreen(::navigateUp) }
|
||||
composable<ClearAppStorage> { ClearAppStorageScreen(::navigateUp) }
|
||||
composable<UninstallApp> { UninstallAppScreen(::navigateUp) }
|
||||
composable<KeepUninstalledPackages> { KeepUninstalledPackagesScreen(::navigateUp) }
|
||||
composable<InstallExistingApp> { InstallExistingAppScreen(::navigateUp) }
|
||||
composable<CrossProfilePackages> { CrossProfilePackagesScreen(::navigateUp) }
|
||||
composable<CrossProfileWidgetProviders> { CrossProfileWidgetProvidersScreen(::navigateUp) }
|
||||
composable<CredentialManagerPolicy> { CredentialManagerPolicyScreen(::navigateUp) }
|
||||
composable<PermittedAccessibilityServices> { PermittedAccessibilityServicesScreen(::navigateUp) }
|
||||
composable<PermittedInputMethods> { PermittedInputMethodsScreen(::navigateUp) }
|
||||
composable<EnableSystemApp> { EnableSystemAppScreen(::navigateUp) }
|
||||
composable<SetDefaultDialer> { SetDefaultDialerScreen(::navigateUp) }
|
||||
composable<ApplicationDetails> {
|
||||
ApplicationDetailsScreen(it.toRoute(), vm, ::navigateUp, ::navigate)
|
||||
}
|
||||
composable<Suspend> {
|
||||
PackageFunctionScreen(R.string.suspend, vm.suspendedPackages, vm::getSuspendedPackaged,
|
||||
vm::setPackageSuspended, ::navigateUp, vm.chosenPackage, ::choosePackage,
|
||||
R.string.info_suspend_app)
|
||||
}
|
||||
composable<Hide> {
|
||||
PackageFunctionScreen(R.string.hide, vm.hiddenPackages, vm::getHiddenPackages,
|
||||
vm::setPackageHidden, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||
}
|
||||
composable<BlockUninstall> {
|
||||
PackageFunctionScreenWithoutResult(R.string.block_uninstall, vm.ubPackages,
|
||||
vm::getUbPackages, vm::setPackageUb, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||
}
|
||||
composable<DisableUserControl> {
|
||||
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> {
|
||||
UserRestrictionScreen(::navigateUp) {
|
||||
@@ -497,7 +566,10 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) {
|
||||
},
|
||||
contentWindowInsets = WindowInsets.ime
|
||||
) {
|
||||
Column(Modifier.fillMaxSize().padding(it).verticalScroll(rememberScrollState())) {
|
||||
Column(Modifier
|
||||
.fillMaxSize()
|
||||
.padding(it)
|
||||
.verticalScroll(rememberScrollState())) {
|
||||
if(privilege.device || privilege.profile) {
|
||||
HomePageItem(R.string.system, R.drawable.android_fill0) { onNavigate(SystemManager) }
|
||||
HomePageItem(R.string.network, R.drawable.wifi_fill0) { onNavigate(Network) }
|
||||
|
||||
@@ -1,10 +1,41 @@
|
||||
package com.bintianqi.owndroid
|
||||
|
||||
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.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.update
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
val PM = application.packageManager
|
||||
val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme))
|
||||
fun changeTheme(newTheme: ThemeSettings) {
|
||||
theme.value = newTheme
|
||||
@@ -12,6 +43,340 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
SP.darkTheme = newTheme.darkTheme
|
||||
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(
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
package com.bintianqi.owndroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
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.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -44,10 +37,8 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
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.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
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(
|
||||
val name: String,
|
||||
val label: String,
|
||||
@@ -105,11 +71,14 @@ private fun searchInString(query: String, content: String)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Unit, onSwitchView: () -> Unit) {
|
||||
val packages by installedApps.collectAsStateWithLifecycle()
|
||||
val coroutine = rememberCoroutineScope()
|
||||
fun AppChooserScreen(
|
||||
canSwitchView: Boolean, packageList: MutableStateFlow<List<AppInfo>>,
|
||||
refreshProgress: MutableStateFlow<Float>, onChoosePackage: (String?) -> Unit,
|
||||
onSwitchView: () -> Unit, onRefresh: () -> Unit
|
||||
) {
|
||||
val packages by packageList.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
var progress by remember { mutableFloatStateOf(1F) }
|
||||
val progress by refreshProgress.collectAsStateWithLifecycle()
|
||||
var system by rememberSaveable { mutableStateOf(false) }
|
||||
var query by rememberSaveable { mutableStateOf("") }
|
||||
var searchMode by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -119,7 +88,7 @@ fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Uni
|
||||
}
|
||||
val focusMgr = LocalFocusManager.current
|
||||
LaunchedEffect(Unit) {
|
||||
if(packages.size <= 1) getInstalledApps(coroutine, context) { progress = it }
|
||||
if(packages.size <= 1) onRefresh()
|
||||
}
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -135,20 +104,17 @@ fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Uni
|
||||
}) {
|
||||
Icon(painter = painterResource(R.drawable.filter_alt_fill0), contentDescription = null)
|
||||
}
|
||||
IconButton(
|
||||
{ getInstalledApps(coroutine, context) { progress = it } },
|
||||
enabled = progress == 1F
|
||||
) {
|
||||
IconButton(onRefresh, enabled = progress == 1F) {
|
||||
Icon(painter = painterResource(R.drawable.refresh_fill0), contentDescription = null)
|
||||
}
|
||||
if(params.canSwitchView) IconButton(onSwitchView) {
|
||||
if (canSwitchView) IconButton(onSwitchView) {
|
||||
Icon(Icons.AutoMirrored.Default.List, null)
|
||||
}
|
||||
}
|
||||
},
|
||||
title = {
|
||||
if(searchMode) {
|
||||
val fr = FocusRequester()
|
||||
val fr = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { fr.requestFocus() }
|
||||
OutlinedTextField(
|
||||
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 =
|
||||
if(Build.VERSION.SDK_INT >= 24) PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_UNINSTALLED_PACKAGES else 0
|
||||
|
||||
@@ -233,28 +233,38 @@ fun AppLockSettingsScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.app_lo
|
||||
var confirmPassword by remember { mutableStateOf("") }
|
||||
var allowBiometrics by remember { mutableStateOf(SP.biometricsUnlock) }
|
||||
var lockWhenLeaving by remember { mutableStateOf(SP.lockWhenLeaving) }
|
||||
val fr = FocusRequester()
|
||||
val fr = remember { FocusRequester() }
|
||||
val alreadySet = !SP.lockPasswordHash.isNullOrEmpty()
|
||||
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(
|
||||
password, { password = it }, Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||
password, { password = it }, Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp),
|
||||
label = { Text(stringResource(R.string.password)) },
|
||||
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),
|
||||
keyboardActions = KeyboardActions { fr.requestFocus() }
|
||||
)
|
||||
OutlinedTextField(
|
||||
confirmPassword, { confirmPassword = it }, Modifier.fillMaxWidth().focusRequester(fr),
|
||||
confirmPassword, { confirmPassword = it }, Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(fr),
|
||||
label = { Text(stringResource(R.string.confirm_password)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
|
||||
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))
|
||||
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))
|
||||
Switch(lockWhenLeaving, { lockWhenLeaving = it })
|
||||
}
|
||||
@@ -302,7 +312,9 @@ fun ApiSettings(onNavigateUp: () -> Unit) {
|
||||
var key by remember { mutableStateOf("") }
|
||||
OutlinedTextField(
|
||||
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 = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
@@ -316,7 +328,9 @@ fun ApiSettings(onNavigateUp: () -> Unit) {
|
||||
}
|
||||
)
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp),
|
||||
onClick = {
|
||||
SP.apiKey = key
|
||||
context.showOperationResultToast(true)
|
||||
|
||||
@@ -4,13 +4,11 @@ import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
@@ -107,13 +105,6 @@ inline fun <reified T> serializableNavTypePair() =
|
||||
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) {
|
||||
context.contentResolver.openOutputStream(uri)?.use { output ->
|
||||
val proc = Runtime.getRuntime().exec("logcat -d")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,6 @@ import com.bintianqi.owndroid.createShortcuts
|
||||
import com.rosan.dhizuku.api.Dhizuku
|
||||
import com.rosan.dhizuku.api.DhizukuBinderWrapper
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
@@ -127,7 +127,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.ChoosePackageContract
|
||||
import com.bintianqi.owndroid.HorizontalPadding
|
||||
import com.bintianqi.owndroid.Privilege
|
||||
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.rememberPermissionState
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -886,7 +886,10 @@ fun NetworkStats.toBucketList(): List<NetworkStats.Bucket> {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(23)
|
||||
@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 privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||
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 readOnly by rememberSaveable { mutableStateOf(true) }
|
||||
if(!readOnly && uidText.toIntOrNull() != null) uid = uidText.toInt()
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) {
|
||||
it ?: return@rememberLauncherForActivityResult
|
||||
if(VERSION.SDK_INT >= 24 && readOnly) {
|
||||
try {
|
||||
uid = context.packageManager.getPackageUid(it, 0)
|
||||
uidText = "$it ($uid)"
|
||||
} catch(_: NameNotFoundException) {
|
||||
context.showOperationResultToast(false)
|
||||
}
|
||||
if (!readOnly && uidText.toIntOrNull() != null) uid = uidText.toInt()
|
||||
if (VERSION.SDK_INT >= 24) LaunchedEffect(Unit) {
|
||||
val pkg = chosenPackage.receive()
|
||||
try {
|
||||
uid = context.packageManager.getPackageUid(pkg, 0)
|
||||
uidText = "$uid ($pkg)"
|
||||
} catch(_: NameNotFoundException) {
|
||||
context.showOperationResultToast(false)
|
||||
}
|
||||
}
|
||||
OutlinedTextField(
|
||||
@@ -1093,7 +1094,7 @@ fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkSta
|
||||
onClick = {
|
||||
readOnly = true
|
||||
activeTextField = NetworkStatsActiveTextField.None
|
||||
choosePackage.launch(null)
|
||||
onChoosePackage()
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
@@ -1457,15 +1458,18 @@ fun PrivateDnsScreen(onNavigateUp: () -> Unit) {
|
||||
|
||||
@RequiresApi(24)
|
||||
@Composable
|
||||
fun AlwaysOnVpnPackageScreen(onNavigateUp: () -> Unit) {
|
||||
fun AlwaysOnVpnPackageScreen(
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, onNavigateUp: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var lockdown by rememberSaveable { mutableStateOf(false) }
|
||||
var pkgName by rememberSaveable { mutableStateOf("") }
|
||||
val focusMgr = LocalFocusManager.current
|
||||
val refresh = { pkgName = Privilege.DPM.getAlwaysOnVpnPackage(Privilege.DAR) ?: "" }
|
||||
LaunchedEffect(Unit) { refresh() }
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
||||
result?.let { pkgName = it }
|
||||
fun refresh() {
|
||||
pkgName = Privilege.DPM.getAlwaysOnVpnPackage(Privilege.DAR) ?: ""
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
refresh()
|
||||
pkgName = chosenPackage.receive()
|
||||
}
|
||||
val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean ->
|
||||
try {
|
||||
@@ -1483,21 +1487,8 @@ fun AlwaysOnVpnPackageScreen(onNavigateUp: () -> Unit) {
|
||||
}
|
||||
}
|
||||
MyScaffold(R.string.always_on_vpn, onNavigateUp) {
|
||||
OutlinedTextField(
|
||||
value = pkgName,
|
||||
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)
|
||||
)
|
||||
PackageNameTextField(pkgName, onChoosePackage,
|
||||
Modifier.padding(vertical = 4.dp)) { pkgName = it }
|
||||
SwitchItem(R.string.enable_lockdown, state = lockdown, onCheckedChange = { lockdown = it }, padding = false)
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
Button(
|
||||
@@ -2067,7 +2058,7 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) {
|
||||
keyboardActions = KeyboardActions { fm.clearFocus() }
|
||||
)
|
||||
if(VERSION.SDK_INT >= 33) Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), Arrangement.SpaceBetween) {
|
||||
val fr = FocusRequester()
|
||||
val fr = remember { FocusRequester() }
|
||||
OutlinedTextField(
|
||||
mtuV4, { mtuV4 = it }, Modifier.fillMaxWidth(0.49F),
|
||||
label = { Text("MTU (IPv4)") },
|
||||
@@ -2206,7 +2197,7 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) {
|
||||
if(dialog != 0) {
|
||||
var address by remember { mutableStateOf((if(dialog == 1) proxyAddress else mmsProxyAddress)) }
|
||||
var port by remember { mutableStateOf((if(dialog == 1) proxyPort else mmsProxyPort)) }
|
||||
val fr = FocusRequester()
|
||||
val fr = remember { FocusRequester() }
|
||||
AlertDialog(
|
||||
title = { Text(if(dialog == 1) "Proxy" else "MMS proxy") },
|
||||
text = {
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build.VERSION
|
||||
import android.os.PersistableBundle
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
@@ -84,10 +83,8 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.state.ToggleableState
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.ChoosePackageContract
|
||||
import com.bintianqi.owndroid.DHIZUKU_CLIENTS_FILE
|
||||
import com.bintianqi.owndroid.DhizukuClientInfo
|
||||
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.DhizukuRequestPermissionListener
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -731,30 +729,19 @@ fun DelegatedAdminsScreen(onNavigateUp: () -> Unit, onNavigate: (AddDelegatedAdm
|
||||
|
||||
@RequiresApi(26)
|
||||
@Composable
|
||||
fun AddDelegatedAdminScreen(data: AddDelegatedAdmin, onNavigateUp: () -> Unit) {
|
||||
fun AddDelegatedAdminScreen(
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
|
||||
data: AddDelegatedAdmin, onNavigateUp: () -> Unit
|
||||
) {
|
||||
val updateMode = data.pkg.isNotEmpty()
|
||||
val fm = LocalFocusManager.current
|
||||
var input by remember { mutableStateOf(data.pkg) }
|
||||
val scopes = remember { mutableStateListOf(*data.scopes.toTypedArray()) }
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
||||
result?.let { input = it }
|
||||
LaunchedEffect(Unit) {
|
||||
input = chosenPackage.receive()
|
||||
}
|
||||
MySmallTitleScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, onNavigateUp, 0.dp) {
|
||||
OutlinedTextField(
|
||||
value = input, onValueChange = { 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)
|
||||
)
|
||||
PackageNameTextField(input, onChoosePackage,
|
||||
Modifier.padding(HorizontalPadding, 8.dp)) { input = it }
|
||||
DelegatedScope.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach { scope ->
|
||||
val checked = scope in scopes
|
||||
Row(
|
||||
|
||||
@@ -112,13 +112,11 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.ChoosePackageContract
|
||||
import com.bintianqi.owndroid.HorizontalPadding
|
||||
import com.bintianqi.owndroid.NotificationUtils
|
||||
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.uriToStream
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -1134,7 +1133,9 @@ fun NearbyStreamingPolicyScreen(onNavigateUp: () -> Unit) {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
fun LockTaskModeScreen(onNavigateUp: () -> Unit) {
|
||||
fun LockTaskModeScreen(
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, onNavigateUp: () -> Unit
|
||||
) {
|
||||
val coroutine = rememberCoroutineScope()
|
||||
val pagerState = rememberPagerState { 3 }
|
||||
var tabIndex by remember { mutableIntStateOf(0) }
|
||||
@@ -1177,8 +1178,8 @@ fun LockTaskModeScreen(onNavigateUp: () -> Unit) {
|
||||
.padding(horizontal = HorizontalPadding)
|
||||
.padding(bottom = 80.dp)
|
||||
) {
|
||||
if(page == 0) StartLockTaskMode()
|
||||
else LockTaskPackages()
|
||||
if(page == 0) StartLockTaskMode(chosenPackage, onChoosePackage)
|
||||
else LockTaskPackages(chosenPackage, onChoosePackage)
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
@@ -1197,33 +1198,20 @@ fun LockTaskModeScreen(onNavigateUp: () -> Unit) {
|
||||
|
||||
@RequiresApi(28)
|
||||
@Composable
|
||||
private fun ColumnScope.StartLockTaskMode() {
|
||||
private fun ColumnScope.StartLockTaskMode(
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val focusMgr = LocalFocusManager.current
|
||||
var startLockTaskApp by rememberSaveable { mutableStateOf("") }
|
||||
var startLockTaskActivity by rememberSaveable { mutableStateOf("") }
|
||||
var specifyActivity by rememberSaveable { mutableStateOf(false) }
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
||||
result?.let { startLockTaskApp = it }
|
||||
LaunchedEffect(Unit) {
|
||||
startLockTaskApp = chosenPackage.receive()
|
||||
}
|
||||
Spacer(Modifier.padding(vertical = 5.dp))
|
||||
OutlinedTextField(
|
||||
value = startLockTaskApp,
|
||||
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)
|
||||
)
|
||||
PackageNameTextField(startLockTaskApp, onChoosePackage,
|
||||
Modifier.padding(vertical = 3.dp), { startLockTaskApp = it })
|
||||
CheckBoxItem(R.string.specify_activity, specifyActivity) { specifyActivity = it }
|
||||
AnimatedVisibility(specifyActivity) {
|
||||
OutlinedTextField(
|
||||
@@ -1264,37 +1252,23 @@ private fun ColumnScope.StartLockTaskMode() {
|
||||
|
||||
@RequiresApi(26)
|
||||
@Composable
|
||||
private fun ColumnScope.LockTaskPackages() {
|
||||
private fun ColumnScope.LockTaskPackages(
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val focusMgr = LocalFocusManager.current
|
||||
val lockTaskPackages = remember { mutableStateListOf<String>() }
|
||||
var input by rememberSaveable { mutableStateOf("") }
|
||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
||||
result?.let { input = it }
|
||||
LaunchedEffect(Unit) {
|
||||
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))
|
||||
if(lockTaskPackages.isEmpty()) Text(text = stringResource(R.string.none))
|
||||
for(i in lockTaskPackages) {
|
||||
ListItem(i) { lockTaskPackages -= i }
|
||||
}
|
||||
OutlinedTextField(
|
||||
value = input,
|
||||
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)
|
||||
)
|
||||
PackageNameTextField(input, onChoosePackage,
|
||||
Modifier.padding(vertical = 3.dp), { input = it })
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Button(
|
||||
onClick = {
|
||||
|
||||
Reference in New Issue
Block a user