From 6c92c7dcbef7d1b3542c309450b93026ee9e551a Mon Sep 17 00:00:00 2001 From: BinTianqi Date: Sun, 6 Apr 2025 15:32:11 +0800 Subject: [PATCH] Add Privilege class --- .github/workflows/build.yml | 4 +- .../owndroid/AppInstallerActivity.kt | 7 +- .../com/bintianqi/owndroid/MainActivity.kt | 81 +++++----- .../bintianqi/owndroid/ManageSpaceActivity.kt | 3 +- .../java/com/bintianqi/owndroid/Privilege.kt | 39 +++++ .../java/com/bintianqi/owndroid/Receiver.kt | 33 +--- .../java/com/bintianqi/owndroid/Settings.kt | 4 +- .../com/bintianqi/owndroid/SharedPrefs.kt | 3 +- .../main/java/com/bintianqi/owndroid/Utils.kt | 20 +-- .../bintianqi/owndroid/dpm/Applications.kt | 47 +++--- .../java/com/bintianqi/owndroid/dpm/DPM.kt | 51 +++++-- .../com/bintianqi/owndroid/dpm/Network.kt | 72 ++++----- .../com/bintianqi/owndroid/dpm/Password.kt | 39 ++--- .../com/bintianqi/owndroid/dpm/Permissions.kt | 141 ++++-------------- .../com/bintianqi/owndroid/dpm/Shizuku.kt | 60 ++++---- .../java/com/bintianqi/owndroid/dpm/System.kt | 106 ++++++------- .../bintianqi/owndroid/dpm/UserRestriction.kt | 12 +- .../java/com/bintianqi/owndroid/dpm/Users.kt | 34 ++--- .../com/bintianqi/owndroid/dpm/WorkProfile.kt | 22 +-- app/src/main/res/values-ru/strings.xml | 6 +- app/src/main/res/values-tr/strings.xml | 23 +-- app/src/main/res/values-zh-rCN/strings.xml | 6 +- app/src/main/res/values/strings.xml | 7 +- 23 files changed, 339 insertions(+), 481 deletions(-) create mode 100644 app/src/main/java/com/bintianqi/owndroid/Privilege.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f919704..d7db538 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build APK on: workflow_dispatch: push: - branches: ["master"] + branches: ["dev"] paths-ignore: - '**.md' tags-ignore: @@ -24,7 +24,7 @@ jobs: - name: Set up JDK 21 uses: actions/setup-java@v4 with: - distribution: 'adopt' + distribution: 'temurin' java-version: '21' - name: Get short commit SHA diff --git a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt index ef68667..c5b28ae 100644 --- a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt @@ -80,6 +80,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.net.URLDecoder +import androidx.core.net.toUri class AppInstallerActivity:FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -116,11 +117,11 @@ private fun AppInstaller( installing: Boolean = false, options: SessionParamsOptions = SessionParamsOptions(), onOptionsChange: (SessionParamsOptions) -> Unit = {}, - packages: Set = setOf(Uri.parse("https://example.com")), + packages: Set = setOf("https://example.com".toUri()), onPackageRemove: (Uri) -> Unit = {}, onPackageChoose: (List) -> Unit = {}, onStartInstall: () -> Unit = {}, - writtenPackages: Set = setOf(Uri.parse("https://example.com")), + writtenPackages: Set = setOf("https://example.com".toUri()), writingPackage: Uri? = null, result: Intent? = null, onResultDialogClose: () -> Unit = {} @@ -309,7 +310,7 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat intent.getParcelableExtra(Intent.EXTRA_STREAM)?.let { uri -> packages.update { it + uri } } intent.getParcelableArrayExtra(Intent.EXTRA_STREAM)?.forEach { uri -> packages.update { it + (uri as Uri) } } intent.clipData?.let { clipData -> - for(i in 0..(clipData.itemCount - 1)) { + for(i in 0..clipData.itemCount) { packages.update { it + clipData.getItemAt(i).uri } } } diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index e230016..a41af75 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -35,9 +35,6 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -108,8 +105,6 @@ import com.bintianqi.owndroid.dpm.DelegatedAdmins import com.bintianqi.owndroid.dpm.DelegatedAdminsScreen import com.bintianqi.owndroid.dpm.DeleteWorkProfile import com.bintianqi.owndroid.dpm.DeleteWorkProfileScreen -import com.bintianqi.owndroid.dpm.DeviceAdmin -import com.bintianqi.owndroid.dpm.DeviceAdminScreen import com.bintianqi.owndroid.dpm.DeviceInfo import com.bintianqi.owndroid.dpm.DeviceInfoScreen import com.bintianqi.owndroid.dpm.DeviceOwner @@ -241,9 +236,6 @@ import com.bintianqi.owndroid.dpm.dhizukuErrorStatus import com.bintianqi.owndroid.dpm.dhizukuPermissionGranted import com.bintianqi.owndroid.dpm.getDPM import com.bintianqi.owndroid.dpm.getReceiver -import com.bintianqi.owndroid.dpm.isDeviceAdmin -import com.bintianqi.owndroid.dpm.isDeviceOwner -import com.bintianqi.owndroid.dpm.isProfileOwner import com.bintianqi.owndroid.dpm.setDefaultAffiliationID import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.theme.OwnDroidTheme @@ -259,7 +251,6 @@ val backToHomeStateFlow = MutableStateFlow(false) @ExperimentalMaterial3Api class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { - registerActivityResult(this) enableEdgeToEdge() WindowCompat.setDecorFitsSystemWindows(window, false) super.onCreate(savedInstanceState) @@ -288,6 +279,7 @@ class MainActivity : FragmentActivity() { dhizukuErrorStatus.value = 1 } } + updatePrivilege(this) } } @@ -325,9 +317,8 @@ fun Home(vm: MyViewModel) { composable { PermissionsScreen(::navigateUp, { navController.navigate(it) }) { navController.navigate(ShizukuScreen, it) } } - composable { ShizukuScreen(it.arguments!!, ::navigateUp) { navController.navigate(it) } } + composable { ShizukuScreen(it.arguments!!, ::navigateUp) { dest -> navController.navigate(dest) } } composable(mapOf(serializableNavTypePair>())) { AccountsScreen(it.toRoute(), ::navigateUp) } - composable { DeviceAdminScreen(::navigateUp) } composable { ProfileOwnerScreen(::navigateUp) } composable { DeviceOwnerScreen(::navigateUp) } composable { DelegatedAdminsScreen(::navigateUp, ::navigate) } @@ -385,8 +376,8 @@ fun Home(vm: MyViewModel) { composable { DeleteWorkProfileScreen(::navigateUp) } composable { - AppChooserScreen(it.toRoute(), { - if(it == null) navigateUp() else navigate(ApplicationDetails(it)) + AppChooserScreen(it.toRoute(), { dest -> + if(dest == null) navigateUp() else navigate(ApplicationDetails(dest)) }, { SharedPrefs(context).applicationsListView = false navController.navigate(ApplicationsFeatures) { @@ -489,7 +480,7 @@ fun Home(vm: MyViewModel) { LaunchedEffect(Unit) { val dpm = context.getDPM() val sp = SharedPrefs(context) - val profileNotActivated = !sp.managedProfileActivated && context.isProfileOwner && (VERSION.SDK_INT < 24 || dpm.isManagedProfile(receiver)) + val profileNotActivated = !sp.managedProfileActivated && myPrivilege.value.work if(profileNotActivated) { dpm.setProfileEnabled(receiver) sp.managedProfileActivated = true @@ -505,24 +496,14 @@ fun Home(vm: MyViewModel) { private fun HomeScreen(onNavigate: (Any) -> Unit) { val context = LocalContext.current val dpm = context.getDPM() - val receiver = context.getReceiver() - var activated by remember { mutableStateOf(false) } - var activateType by remember { mutableStateOf("") } - val deviceAdmin = context.isDeviceAdmin - val deviceOwner = context.isDeviceOwner - val profileOwner = context.isProfileOwner - val refreshStatus by dhizukuErrorStatus.collectAsState() - LaunchedEffect(refreshStatus) { - activated = context.isProfileOwner || context.isDeviceOwner - activateType = if(SharedPrefs(context).dhizuku) context.getString(R.string.dhizuku) + " - " else "" - activateType += context.getString( - if(deviceOwner) { R.string.device_owner } - else if(profileOwner) { - if(VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)) R.string.work_profile_owner else R.string.profile_owner - } - else if(deviceAdmin) R.string.device_admin else R.string.click_to_activate - ) - } + val privilege by myPrivilege.collectAsStateWithLifecycle() + val activateType = (if(privilege.dhizuku) context.getString(R.string.dhizuku) + " - " else "") + + context.getString( + if(privilege.device) R.string.device_owner + else if(privilege.work) R.string.work_profile_owner + else if(privilege.profile) R.string.profile_owner + else R.string.click_to_activate + ) Scaffold { Column(modifier = Modifier.padding(it).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 25.dp)) @@ -541,6 +522,7 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) { .padding(vertical = 16.dp), verticalAlignment = Alignment.CenterVertically ) { + val activated = privilege.device || privilege.profile Icon( painterResource(if(activated) R.drawable.check_circle_fill1 else R.drawable.block_fill0), null, Modifier.padding(start = 14.dp), colorScheme.onPrimary @@ -555,23 +537,32 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) { if(activateType != "") { Text(text = activateType, color = colorScheme.onPrimary) } } } - HomePageItem(R.string.system, R.drawable.android_fill0) { onNavigate(SystemManager) } - if(deviceOwner || profileOwner) { HomePageItem(R.string.network, R.drawable.wifi_fill0) { onNavigate(Network) } } + 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) } + } if( - (VERSION.SDK_INT < 24 && !deviceOwner) || (dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) || - (profileOwner && dpm.isManagedProfile(receiver)) - ) + privilege.work || (VERSION.SDK_INT < 24 && !privilege.device) || + dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) ) { - HomePageItem(R.string.work_profile, R.drawable.work_fill0) { onNavigate(WorkProfile) } + HomePageItem(R.string.work_profile, R.drawable.work_fill0) { + onNavigate( + if(VERSION.SDK_INT < 24 || + dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) + ) WorkProfile else CreateWorkProfile + ) + } } - if(deviceOwner || profileOwner) HomePageItem(R.string.applications, R.drawable.apps_fill0) { - onNavigate(if(SharedPrefs(context).applicationsListView) ApplicationsList(true) else ApplicationsFeatures) + if(privilege.device || privilege.profile) { + HomePageItem(R.string.applications, R.drawable.apps_fill0) { + onNavigate(if(SharedPrefs(context).applicationsListView) ApplicationsList(true) else ApplicationsFeatures) + } + if(VERSION.SDK_INT >= 24) { + HomePageItem(R.string.user_restriction, R.drawable.person_off) { onNavigate(UserRestriction) } + } + HomePageItem(R.string.users,R.drawable.manage_accounts_fill0) { onNavigate(Users) } + HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0) { onNavigate(Password) } } - if(VERSION.SDK_INT >= 24 && (profileOwner || deviceOwner)) { - HomePageItem(R.string.user_restriction, R.drawable.person_off) { onNavigate(UserRestriction) } - } - HomePageItem(R.string.users,R.drawable.manage_accounts_fill0) { onNavigate(Users) } - if(deviceOwner || profileOwner) HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0) { onNavigate(Password) } HomePageItem(R.string.settings, R.drawable.settings_fill0) { onNavigate(Settings) } Spacer(Modifier.padding(vertical = 20.dp)) } diff --git a/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt b/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt index 505b002..9885a86 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt @@ -18,6 +18,7 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bintianqi.owndroid.ui.theme.OwnDroidTheme import kotlin.system.exitProcess +import androidx.core.content.edit class ManageSpaceActivity: FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -62,7 +63,7 @@ class ManageSpaceActivity: FragmentActivity() { dataDir.resolve("shared_prefs").deleteRecursively() } else { val sharedPref = applicationContext.getSharedPreferences("data", MODE_PRIVATE) - sharedPref.edit().clear().apply() + sharedPref.edit { clear() } } this.showOperationResultToast(true) finish() diff --git a/app/src/main/java/com/bintianqi/owndroid/Privilege.kt b/app/src/main/java/com/bintianqi/owndroid/Privilege.kt new file mode 100644 index 0000000..8b19866 --- /dev/null +++ b/app/src/main/java/com/bintianqi/owndroid/Privilege.kt @@ -0,0 +1,39 @@ +package com.bintianqi.owndroid + +import android.content.Context +import android.os.Binder +import android.os.Build +import com.bintianqi.owndroid.dpm.getDPM +import com.bintianqi.owndroid.dpm.getReceiver +import com.bintianqi.owndroid.dpm.isDeviceOwner +import com.bintianqi.owndroid.dpm.isProfileOwner +import kotlinx.coroutines.flow.MutableStateFlow + +class Privilege( + val device: Boolean = false, // Device owner + val profile: Boolean = false, // Profile owner + val dhizuku: Boolean = false, + val work: Boolean = false, // Work profile + val org: Boolean = false, // Organization-owned work profile + val affiliated: Boolean = false +) { + val primary = Binder.getCallingUid() / 100000 == 0 // Primary user +} + +val myPrivilege = MutableStateFlow(Privilege()) + +fun updatePrivilege(context: Context) { + val dpm = context.getDPM() + val receiver = context.getReceiver() + val profile = context.isProfileOwner + val work = profile && Build.VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver) + myPrivilege.value = Privilege( + device = context.isDeviceOwner, + profile = profile, + dhizuku = SharedPrefs(context).dhizuku, + work = work, + org = work && Build.VERSION.SDK_INT >= 30 && dpm.isOrganizationOwnedDeviceWithManagedProfile, + affiliated = Build.VERSION.SDK_INT >= 28 && dpm.isAffiliatedUser + ) +} + diff --git a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt index 551da4b..30b2017 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt @@ -6,21 +6,15 @@ import android.app.admin.DeviceAdminReceiver import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.pm.ShortcutInfo -import android.content.pm.ShortcutManager -import android.graphics.drawable.Icon import android.os.Build.VERSION import android.os.PersistableBundle import android.os.UserHandle import android.os.UserManager import android.widget.Toast import androidx.core.app.NotificationCompat -import androidx.core.graphics.drawable.toBitmap import com.bintianqi.owndroid.dpm.handleNetworkLogs -import com.bintianqi.owndroid.dpm.isDeviceOwner -import com.bintianqi.owndroid.dpm.isProfileOwner +import com.bintianqi.owndroid.dpm.handlePrivilegeChange import com.bintianqi.owndroid.dpm.processSecurityLogs -import com.bintianqi.owndroid.dpm.setDefaultAffiliationID import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -38,35 +32,18 @@ class Receiver : DeviceAdminReceiver() { dpm.setLockTaskPackages(receiver, arrayOf()) dpm.setLockTaskPackages(receiver, packages) } - if(!context.isDeviceOwner && !context.isProfileOwner) SharedPrefs(context).isApiEnabled = false } override fun onEnabled(context: Context, intent: Intent) { super.onEnabled(context, intent) - if(context.isProfileOwner || context.isDeviceOwner){ - setDefaultAffiliationID(context) - Toast.makeText(context, context.getString(R.string.onEnabled), Toast.LENGTH_SHORT).show() - if(VERSION.SDK_INT >= 25) { - val sm = context.getSystemService(ShortcutManager::class.java) - val lockIntent = Intent("com.bintianqi.owndroid.action.LOCK") - .setComponent(ComponentName(context, ShortcutsReceiverActivity::class.java)) - val shortcut = ShortcutInfo.Builder(context, "LockScreen") - .setShortLabel(context.getString(R.string.lock_now)) - .setIcon(Icon.createWithBitmap(context.getDrawable(R.drawable.screen_lock_portrait_fill0)?.toBitmap())) - .setIntent(lockIntent) - sm.addDynamicShortcuts(listOf(shortcut.build())) - } - } + updatePrivilege(context) + handlePrivilegeChange(context) } override fun onDisabled(context: Context, intent: Intent) { super.onDisabled(context, intent) - Toast.makeText(context, R.string.onDisabled, Toast.LENGTH_SHORT).show() - SharedPrefs(context).isDefaultAffiliationIdSet = false - if(VERSION.SDK_INT >= 25) { - val sm = context.getSystemService(ShortcutManager::class.java) - sm.removeDynamicShortcuts(listOf("LockScreen")) - } + updatePrivilege(context) + handlePrivilegeChange(context) } override fun onProfileProvisioningComplete(context: Context, intent: Intent) { diff --git a/app/src/main/java/com/bintianqi/owndroid/Settings.kt b/app/src/main/java/com/bintianqi/owndroid/Settings.kt index 52b204d..976b30c 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Settings.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Settings.kt @@ -2,7 +2,6 @@ package com.bintianqi.owndroid import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Build.VERSION import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -45,6 +44,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.core.content.edit +import androidx.core.net.toUri import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.Notes @@ -285,7 +285,7 @@ fun AboutScreen(onNavigateUp: () -> Unit) { } fun shareLink(inputContext: Context, link: String) { - val uri = Uri.parse(link) + val uri = link.toUri() val intent = Intent(Intent.ACTION_VIEW, uri) inputContext.startActivity(Intent.createChooser(intent, "Open in browser"), null) } diff --git a/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt b/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt index 0e9a32b..07d5b1f 100644 --- a/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt +++ b/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt @@ -1,13 +1,14 @@ package com.bintianqi.owndroid import android.content.Context +import android.content.SharedPreferences import android.os.Build import androidx.core.content.edit import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty class SharedPrefs(context: Context) { - val sharedPrefs = context.getSharedPreferences("data", Context.MODE_PRIVATE) + val sharedPrefs: SharedPreferences = context.getSharedPreferences("data", Context.MODE_PRIVATE) var managedProfileActivated by BooleanSharedPref("managed_profile_activated") var dhizuku by BooleanSharedPref("dhizuku_mode") var isDefaultAffiliationIdSet by BooleanSharedPref("default_affiliation_id_set") diff --git a/app/src/main/java/com/bintianqi/owndroid/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/Utils.kt index d46766e..0bda227 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Utils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Utils.kt @@ -1,25 +1,20 @@ package com.bintianqi.owndroid import android.annotation.SuppressLint -import android.app.admin.DevicePolicyManager import android.content.ClipData import android.content.ClipboardManager -import android.content.ComponentName import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.activity.result.contract.ActivityResultContract -import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.annotation.StringRes import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import androidx.navigation.NavType -import com.bintianqi.owndroid.dpm.addDeviceAdmin import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.FileNotFoundException @@ -60,15 +55,6 @@ fun writeClipBoard(context: Context, string: String):Boolean{ return true } -fun registerActivityResult(context: ComponentActivity) { - addDeviceAdmin = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - val dpm = context.applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager - if(dpm.isAdminActive(ComponentName(context.applicationContext, Receiver::class.java))) { - backToHomeStateFlow.value = true - } - } -} - fun formatFileSize(bytes: Long): String { val kb = 1024 val mb = kb * 1024 @@ -91,15 +77,11 @@ fun parseTimestamp(timestamp: Long): String { return formatter.format(instant) } -fun parseDate(date: Date) - = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(date) +fun parseDate(date: Date): String = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(date) val Long.humanReadableDate: String get() = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(Date(this)) -val Long.humanReadableDateTime: String - get() = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(Date(this)) - fun formatDate(pattern: String, value: Long): String = SimpleDateFormat(pattern, Locale.getDefault()).format(Date(value)) diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt index 81ee54c..204a01d 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt @@ -76,6 +76,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bintianqi.owndroid.AppInfo import com.bintianqi.owndroid.AppInstallerActivity import com.bintianqi.owndroid.AppInstallerViewModel @@ -84,6 +85,7 @@ import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.R import com.bintianqi.owndroid.getInstalledAppsFlags import com.bintianqi.owndroid.installedApps +import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.ErrorDialog import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem @@ -179,14 +181,11 @@ fun ApplicationsFeaturesScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Un .verticalScroll(rememberScrollState()) .padding(bottom = 80.dp) ) { - val dpm = context.getDPM() - val receiver = context.getReceiver() - val deviceOwner = context.isDeviceOwner - val profileOwner = context.isProfileOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() if(VERSION.SDK_INT >= 24) FunctionItem(R.string.suspend, icon = R.drawable.block_fill0) { onNavigate(Suspend) } FunctionItem(R.string.hide, icon = R.drawable.visibility_off_fill0) { onNavigate(Hide) } FunctionItem(R.string.block_uninstall, icon = R.drawable.delete_forever_fill0) { onNavigate(BlockUninstall) } - if(VERSION.SDK_INT >= 30 && (deviceOwner || (VERSION.SDK_INT >= 33 && profileOwner))) { + if(VERSION.SDK_INT >= 30 && (privilege.device || (VERSION.SDK_INT >= 33 && privilege.profile))) { FunctionItem(R.string.disable_user_control, icon = R.drawable.do_not_touch_fill0) { onNavigate(DisableUserControl) } } if(VERSION.SDK_INT >= 23) { @@ -202,19 +201,19 @@ fun ApplicationsFeaturesScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Un context.startActivity(Intent(context, AppInstallerActivity::class.java)) } FunctionItem(R.string.uninstall_app, icon = R.drawable.delete_fill0) { onNavigate(UninstallApp) } - if(VERSION.SDK_INT >= 28 && deviceOwner) { + if(VERSION.SDK_INT >= 28 && privilege.device) { FunctionItem(R.string.keep_uninstalled_packages, icon = R.drawable.delete_fill0) { onNavigate(KeepUninstalledPackages) } } if(VERSION.SDK_INT >= 28) FunctionItem(R.string.install_existing_app, icon = R.drawable.install_mobile_fill0) { onNavigate(InstallExistingApp) } - if(VERSION.SDK_INT >= 30 && profileOwner && dpm.isManagedProfile(receiver)) { + if(VERSION.SDK_INT >= 30 && privilege.work) { FunctionItem(R.string.cross_profile_apps, icon = R.drawable.work_fill0) { onNavigate(CrossProfilePackages) } } - if(profileOwner) { + if(privilege.work) { FunctionItem(R.string.cross_profile_widget, icon = R.drawable.widgets_fill0) { onNavigate(CrossProfileWidgetProviders) } } - if(VERSION.SDK_INT >= 34 && deviceOwner) { + if(VERSION.SDK_INT >= 34 && privilege.device) { FunctionItem(R.string.credential_manager_policy, icon = R.drawable.license_fill0) { onNavigate(CredentialManagerPolicy) } } FunctionItem(R.string.permitted_accessibility_services, icon = R.drawable.settings_accessibility_fill0) { @@ -222,7 +221,7 @@ fun ApplicationsFeaturesScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Un } FunctionItem(R.string.permitted_ime, icon = R.drawable.keyboard_fill0) { onNavigate(PermittedInputMethods) } FunctionItem(R.string.enable_system_app, icon = R.drawable.enable_fill0) { onNavigate(EnableSystemApp) } - if(VERSION.SDK_INT >= 34 && (deviceOwner || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 34 && (privilege.device || privilege.work)) { FunctionItem(R.string.set_default_dialer, icon = R.drawable.call_fill0) { onNavigate(SetDefaultDialer) } } } @@ -445,9 +444,9 @@ fun DisableUserControlScreen(onNavigateUp: () -> Unit) { } LaunchedEffect(Unit) { refresh() } MyLazyScaffold(R.string.disable_user_control, onNavigateUp) { - items(packages, { it.name }) { - ApplicationItem(it) { - dpm.setUserControlDisabledPackages(receiver, packages.minus(it).map { it.name }) + items(packages, { it.name }) { info -> + ApplicationItem(info) { + dpm.setUserControlDisabledPackages(receiver, packages.minus(info).map { it.name }) refresh() } } @@ -477,6 +476,7 @@ fun PermissionsManagerScreen(onNavigateUp: () -> Unit, param: PermissionsManager val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() + val privilege by myPrivilege.collectAsStateWithLifecycle() var packageName by remember { mutableStateOf(packageNameParam ?: "") } var selectedPermission by remember { mutableStateOf(null) } val statusMap = remember { mutableStateMapOf() } @@ -551,7 +551,7 @@ fun PermissionsManagerScreen(onNavigateUp: () -> Unit, param: PermissionsManager Column { Text(selectedPermission!!.permission) Spacer(Modifier.padding(vertical = 4.dp)) - if(!(VERSION.SDK_INT >= 31 && selectedPermission!!.profileOwnerRestricted && context.isProfileOwner)) { + if(!(VERSION.SDK_INT >= 31 && selectedPermission!!.profileOwnerRestricted && privilege.profile)) { GrantPermissionItem(R.string.granted, PERMISSION_GRANT_STATE_GRANTED) } GrantPermissionItem(R.string.denied, PERMISSION_GRANT_STATE_DENIED) @@ -581,9 +581,9 @@ fun DisableMeteredDataScreen(onNavigateUp: () -> Unit) { } LaunchedEffect(Unit) { refresh() } MyLazyScaffold(R.string.disable_metered_data, onNavigateUp) { - items(packages, { it.name }) { - ApplicationItem(it) { - dpm.setMeteredDataDisabledPackages(receiver, packages.minus(it).map { it.name }) + items(packages, { it.name }) { info -> + ApplicationItem(info) { + dpm.setMeteredDataDisabledPackages(receiver, packages.minus(info).map { it.name }) refresh() } } @@ -732,9 +732,9 @@ fun KeepUninstalledPackagesScreen(onNavigateUp: () -> Unit) { } LaunchedEffect(Unit) { refresh() } MyLazyScaffold(R.string.keep_uninstalled_packages, onNavigateUp) { - items(packages, { it.name }) { - ApplicationItem(it) { - dpm.setKeepUninstalledPackages(receiver, packages.minus(it).map { it.name }) + items(packages, { it.name }) { info -> + ApplicationItem(info) { + dpm.setKeepUninstalledPackages(receiver, packages.minus(info).map { it.name }) refresh() } } @@ -798,9 +798,9 @@ fun CrossProfilePackagesScreen(onNavigateUp: () -> Unit) { } LaunchedEffect(Unit) { refresh() } MyLazyScaffold(R.string.cross_profile_apps, onNavigateUp) { - items(packages, { it.name }) { - ApplicationItem(it) { - dpm.setCrossProfilePackages(receiver, packages.minus(it).map { it.name }.toSet()) + items(packages, { it.name }) { info -> + ApplicationItem(info) { + dpm.setCrossProfilePackages(receiver, packages.minus(info).map { it.name }.toSet()) refresh() } } @@ -1062,6 +1062,7 @@ fun EnableSystemAppScreen(onNavigateUp: () -> Unit) { ) { Text(stringResource(R.string.enable)) } + Notes(R.string.enable_system_app) } } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt index b0ae0ca..3b1a698 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt @@ -14,17 +14,21 @@ import android.content.Context import android.content.Intent import android.content.pm.IPackageInstaller import android.content.pm.PackageInstaller +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager +import android.graphics.drawable.Icon import android.os.Build.VERSION -import android.os.UserManager import android.util.Log -import androidx.activity.result.ActivityResultLauncher import androidx.annotation.DrawableRes import androidx.annotation.RequiresApi import androidx.annotation.StringRes +import androidx.core.graphics.drawable.toBitmap import com.bintianqi.owndroid.R import com.bintianqi.owndroid.Receiver import com.bintianqi.owndroid.SharedPrefs +import com.bintianqi.owndroid.ShortcutsReceiverActivity import com.bintianqi.owndroid.backToHomeStateFlow +import com.bintianqi.owndroid.myPrivilege import com.rosan.dhizuku.api.Dhizuku import com.rosan.dhizuku.api.Dhizuku.binderWrapper import com.rosan.dhizuku.api.DhizukuBinderWrapper @@ -39,8 +43,6 @@ import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonArray import java.io.OutputStream -lateinit var addDeviceAdmin: ActivityResultLauncher - val Context.isDeviceOwner: Boolean get() { val dpm = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager @@ -59,11 +61,6 @@ val Context.isProfileOwner: Boolean return dpm.isProfileOwnerApp("com.bintianqi.owndroid") } -val Context.isDeviceAdmin: Boolean - get() { - return getDPM().isAdminActive(getReceiver()) - } - val Context.dpcPackageName: String get() { return if(SharedPrefs(this).dhizuku) { @@ -73,10 +70,6 @@ val Context.dpcPackageName: String } } -fun DevicePolicyManager.isOrgProfile(receiver: ComponentName): Boolean { - return VERSION.SDK_INT >= 30 && this.isProfileOwnerApp("com.bintianqi.owndroid") && isManagedProfile(receiver) && isOrganizationOwnedDeviceWithManagedProfile -} - @SuppressLint("PrivateApi") private fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? { try { @@ -544,10 +537,10 @@ fun parseSecurityEventData(event: SecurityLog.SecurityEvent): JsonElement? { fun setDefaultAffiliationID(context: Context) { if(VERSION.SDK_INT < 26) return val sp = SharedPrefs(context) + val privilege = myPrivilege.value if(!sp.isDefaultAffiliationIdSet) { try { - val um = context.getSystemService(Context.USER_SERVICE) as UserManager - if(context.isDeviceOwner || (!um.isSystemUser && context.isProfileOwner)) { + if(privilege.device || (!privilege.primary && privilege.profile)) { val dpm = context.getDPM() val receiver = context.getReceiver() val affiliationIDs = dpm.getAffiliationIds(receiver) @@ -596,3 +589,31 @@ fun parsePackageInstallerMessage(context: Context, result: Intent): String { else -> "" } + statusMessage.let { if(it == null) "" else "\n$it" } } + + +fun handlePrivilegeChange(context: Context) { + val privilege = myPrivilege.value + val activated = privilege.device || privilege.profile + if(activated) { + if(!privilege.dhizuku) { + setDefaultAffiliationID(context) + if(VERSION.SDK_INT >= 25) { + val sm = context.getSystemService(ShortcutManager::class.java) + val lockIntent = Intent("com.bintianqi.owndroid.action.LOCK") + .setComponent(ComponentName(context, ShortcutsReceiverActivity::class.java)) + val shortcut = ShortcutInfo.Builder(context, "LockScreen") + .setShortLabel(context.getString(R.string.lock_now)) + .setIcon(Icon.createWithBitmap(context.getDrawable(R.drawable.screen_lock_portrait_fill0)?.toBitmap())) + .setIntent(lockIntent) + sm.addDynamicShortcuts(listOf(shortcut.build())) + } + } + } else { + SharedPrefs(context).isDefaultAffiliationIdSet = false + if(VERSION.SDK_INT >= 25) { + val sm = context.getSystemService(ShortcutManager::class.java) + sm.removeDynamicShortcuts(listOf("LockScreen")) + } + SharedPrefs(context).isApiEnabled = false + } +} diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt index 1f429f7..3ea452c 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt @@ -26,7 +26,6 @@ import android.net.IpConfiguration import android.net.LinkAddress import android.net.ProxyInfo import android.net.StaticIpConfiguration -import android.net.Uri import android.net.wifi.WifiConfiguration import android.net.wifi.WifiManager import android.net.wifi.WifiSsid @@ -124,14 +123,16 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign 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.R -import com.bintianqi.owndroid.SharedPrefs import com.bintianqi.owndroid.formatDate import com.bintianqi.owndroid.formatFileSize import com.bintianqi.owndroid.humanReadableDate +import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.ErrorDialog @@ -163,29 +164,24 @@ import kotlin.reflect.jvm.jvmErasure @Composable fun NetworkScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - val deviceOwner = context.isDeviceOwner - val profileOwner = context.isProfileOwner - val dhizuku = SharedPrefs(context).dhizuku + val privilege by myPrivilege.collectAsStateWithLifecycle() MyScaffold(R.string.network, onNavigateUp, 0.dp) { - if(!dhizuku) FunctionItem(R.string.wifi, icon = R.drawable.wifi_fill0) { onNavigate(WiFi) } + if(!privilege.dhizuku) FunctionItem(R.string.wifi, icon = R.drawable.wifi_fill0) { onNavigate(WiFi) } if(VERSION.SDK_INT >= 30) { FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(NetworkOptions) } } - if(VERSION.SDK_INT >= 23 && !dhizuku && (deviceOwner || profileOwner)) + if(VERSION.SDK_INT >= 23 && !privilege.dhizuku && (privilege.device || privilege.profile)) FunctionItem(R.string.network_stats, icon = R.drawable.query_stats_fill0) { onNavigate(QueryNetworkStats) } - if(VERSION.SDK_INT >= 29 && deviceOwner) { + if(VERSION.SDK_INT >= 29 && privilege.device) { FunctionItem(R.string.private_dns, icon = R.drawable.dns_fill0) { onNavigate(PrivateDns) } } if(VERSION.SDK_INT >= 24) { FunctionItem(R.string.always_on_vpn, icon = R.drawable.vpn_key_fill0) { onNavigate(AlwaysOnVpnPackage) } } - if(deviceOwner) { + if(privilege.device) { FunctionItem(R.string.recommended_global_proxy, icon = R.drawable.vpn_key_fill0) { onNavigate(RecommendedGlobalProxy) } } - if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || (profileOwner && dpm.isManagedProfile(receiver)))) { + if(VERSION.SDK_INT >= 26 && !privilege.dhizuku && (privilege.device || privilege.work)) { FunctionItem(R.string.network_logging, icon = R.drawable.description_fill0) { onNavigate(NetworkLogging) } } if(VERSION.SDK_INT >= 31) { @@ -194,7 +190,7 @@ fun NetworkScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { if(VERSION.SDK_INT >= 33) { FunctionItem(R.string.preferential_network_service, icon = R.drawable.globe_fill0) { onNavigate(PreferentialNetworkService) } } - if(VERSION.SDK_INT >= 28 && deviceOwner) { + if(VERSION.SDK_INT >= 28 && privilege.device) { FunctionItem(R.string.override_apn, icon = R.drawable.cell_tower_fill0) { onNavigate(OverrideApn) } } } @@ -207,10 +203,10 @@ fun NetworkOptionsScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() - val deviceOwner = context.isDeviceOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() var dialog by remember { mutableIntStateOf(0) } MyScaffold(R.string.options, onNavigateUp, 0.dp) { - if(VERSION.SDK_INT>=30 && (deviceOwner || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 30 && (privilege.device || privilege.org)) { SwitchItem(R.string.lockdown_admin_configured_network, icon = R.drawable.wifi_password_fill0, getState = { dpm.hasLockdownAdminConfiguredNetworks(receiver) }, onCheckedChange = { dpm.setConfiguredNetworksLockdownState(receiver,it) }, onClickBlank = { dialog = 1 } @@ -265,9 +261,8 @@ fun WifiScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNavigateTo } HorizontalPager(state = pagerState, verticalAlignment = Alignment.Top) { page -> if(page == 0) { - val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager - val deviceOwner = context.isDeviceOwner - val orgProfileOwner = context.getDPM().isOrgProfile(context.getReceiver()) + val wm = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + val privilege by myPrivilege.collectAsStateWithLifecycle() @Suppress("DEPRECATION") Column( modifier = Modifier.fillMaxSize().padding(top = 12.dp) ) { @@ -299,10 +294,10 @@ fun WifiScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNavigateTo Text(stringResource(R.string.reconnect)) } } - if(VERSION.SDK_INT >= 24 && (deviceOwner || orgProfileOwner)) { + if(VERSION.SDK_INT >= 24 && (privilege.device || privilege.org)) { FunctionItem(R.string.wifi_mac_address) { wifiMacDialog = true } } - if(VERSION.SDK_INT >= 33 && (deviceOwner || orgProfileOwner)) { + if(VERSION.SDK_INT >= 33 && (privilege.device || privilege.org)) { FunctionItem(R.string.min_wifi_security_level) { onNavigate(WifiSecurityLevel) } FunctionItem(R.string.wifi_ssid_policy) { onNavigate(WifiSsidPolicyScreen) } } @@ -315,7 +310,6 @@ fun WifiScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNavigateTo } } if(wifiMacDialog && VERSION.SDK_INT >= 24) { - val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() AlertDialog( @@ -344,7 +338,7 @@ fun WifiScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNavigateTo @Composable private fun SavedNetworks(onNavigateToUpdateNetwork: (Bundle) -> Unit) { val context = LocalContext.current - val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager + val wm = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager val configuredNetworks = remember { mutableStateListOf() } var networkDetailsDialog by remember { mutableIntStateOf(-1) } // -1:Hidden, 0+:Index of configuredNetworks val coroutine = rememberCoroutineScope() @@ -688,7 +682,7 @@ private fun AddNetworkScreen(wifiConfig: WifiConfiguration? = null, onNavigateUp } Button( onClick = { - val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager + val wm = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager try { val config = WifiConfiguration() config.status = status @@ -899,7 +893,7 @@ fun NetworkStats.toBucketList(): List { @Composable fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkStatsViewer) -> Unit) { val context = LocalContext.current - val deviceOwner = context.isDeviceOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() val fm = LocalFocusManager.current val nsm = context.getSystemService(NetworkStatsManager::class.java) val coroutine = rememberCoroutineScope() @@ -971,7 +965,7 @@ fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkSta NetworkStatsTarget.entries.forEach { if( VERSION.SDK_INT >= it.minApi && - (deviceOwner || it != NetworkStatsTarget.Device) && + (privilege.device || it != NetworkStatsTarget.Device) && ((queryType == 1 && (it == NetworkStatsTarget.Device || it == NetworkStatsTarget.User)) || (queryType == 2 && (it == NetworkStatsTarget.Uid || it == NetworkStatsTarget.UidTag || it == NetworkStatsTarget.UidTagState))) ) DropdownMenuItem( @@ -1120,7 +1114,7 @@ fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkSta if(VERSION.SDK_INT >= 24 && (target == NetworkStatsTarget.UidTag || target == NetworkStatsTarget.UidTagState)) ExposedDropdownMenuBox( activeTextField == NetworkStatsActiveTextField.Tag, - { activeTextField == if(it) NetworkStatsActiveTextField.Tag else NetworkStatsActiveTextField.None } + { activeTextField = if(it) NetworkStatsActiveTextField.Tag else NetworkStatsActiveTextField.None } ) { var tagText by rememberSaveable { mutableStateOf(context.getString(R.string.all)) } var readOnly by rememberSaveable { mutableStateOf(true) } @@ -1596,7 +1590,7 @@ fun RecommendedGlobalProxyScreen(onNavigateUp: () -> Unit) { Toast.makeText(context, R.string.invalid_config, Toast.LENGTH_SHORT).show() return@Button } - val uri = Uri.parse(proxyUri) + val uri = proxyUri.toUri() val port: Int try { port = proxyPort.toInt() @@ -1827,8 +1821,8 @@ fun AddPreferentialNetworkServiceConfigScreen(route: AddPreferentialNetworkServi title = R.string.block_non_matching_networks, state = blockNonMatching, onCheckedChange = { blockNonMatching = it }, padding = false ) - val includedUidsLegal = includedUids.lines().filter { it.isNotBlank() }.let { - it.isEmpty() || (it.all { it.toIntOrNull() != null } && excludedUids.isBlank()) + val includedUidsLegal = includedUids.lines().filter { it.isNotBlank() }.let { uid -> + uid.isEmpty() || (uid.all { it.toIntOrNull() != null } && excludedUids.isBlank()) } OutlinedTextField( value = includedUids, onValueChange = { includedUids = it }, minLines = 2, @@ -1837,8 +1831,8 @@ fun AddPreferentialNetworkServiceConfigScreen(route: AddPreferentialNetworkServi isError = !includedUidsLegal, modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp) ) - val excludedUidsLegal = excludedUids.lines().filter { it.isNotBlank() }.let { - it.isEmpty() || (it.all { it.toIntOrNull() != null } && includedUids.isBlank()) + val excludedUidsLegal = excludedUids.lines().filter { it.isNotBlank() }.let { uid -> + uid.isEmpty() || (uid.all { it.toIntOrNull() != null } && includedUids.isBlank()) } OutlinedTextField( value = excludedUids, onValueChange = { excludedUids = it }, minLines = 2, @@ -1852,7 +1846,7 @@ fun AddPreferentialNetworkServiceConfigScreen(route: AddPreferentialNetworkServi try { val config = PreferentialNetworkServiceConfig.Builder().apply { setEnabled(enabled) - if(enabled) setNetworkId(id.toInt()) + if(enabled) setNetworkId(id) setFallbackToDefaultConnectionAllowed(allowFallback) setExcludedUids(excludedUids.lines().filter { it.isNotBlank() }.map { it.toInt() }.toIntArray()) setIncludedUids(includedUids.lines().filter { it.isNotBlank() }.map { it.toInt() }.toIntArray()) @@ -1957,7 +1951,7 @@ private val apnTypes = listOf( @Serializable object AddApnSetting -@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalLayoutApi::class) @RequiresApi(28) @Composable fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) { @@ -2099,14 +2093,14 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) { OutlinedTextField( mtuV4, { mtuV4 = it }, Modifier.fillMaxWidth(0.49F), label = { Text("MTU (IPv4)") }, - isError = !mtuV4.isEmpty() && mtuV4.toIntOrNull() == null, + isError = mtuV4.isNotEmpty() && mtuV4.toIntOrNull() == null, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next), keyboardActions = KeyboardActions { fr.requestFocus() } ) OutlinedTextField( mtuV6, { mtuV6 = it }, Modifier.focusRequester(fr).fillMaxWidth(0.96F), label = { Text("MTU (IPv6)") }, - isError = !mtuV6.isEmpty() && mtuV6.toIntOrNull() == null, + isError = mtuV6.isNotEmpty() && mtuV6.toIntOrNull() == null, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), keyboardActions = KeyboardActions { fm.clearFocus() } ) @@ -2194,7 +2188,7 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) { setMmsProxyAddress(mmsProxyAddress) mmsProxyPort.toIntOrNull()?.let { setMmsProxyPort(it) } } - setMmsc(Uri.parse(mmsc)) + setMmsc(mmsc.toUri()) if(VERSION.SDK_INT >= 33) { mtuV4.toIntOrNull()?.let { setMtuV4(it) } mtuV6.toIntOrNull()?.let { setMtuV6(it) } @@ -2238,7 +2232,7 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) { AlertDialog( title = { Text(if(dialog == 1) "Proxy" else "MMS proxy") }, text = { - val fm = LocalFocusManager.current + val focusManager = LocalFocusManager.current Column { OutlinedTextField( address, { address = it }, Modifier.fillMaxWidth().padding(bottom = 4.dp), @@ -2253,7 +2247,7 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) { isError = port.isNotEmpty() && port.toIntOrNull() == null, label = { Text(stringResource(R.string.port)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions { fm.clearFocus() } + keyboardActions = KeyboardActions { focusManager.clearFocus() } ) } }, diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt index 59b450b..f660d38 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt @@ -65,9 +65,11 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.R import com.bintianqi.owndroid.SharedPrefs +import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem @@ -86,38 +88,30 @@ import kotlinx.serialization.Serializable @Composable fun PasswordScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { val context = LocalContext.current - val deviceAdmin = context.isDeviceAdmin - val deviceOwner = context.isDeviceOwner - val profileOwner = context.isProfileOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() var dialog by remember { mutableIntStateOf(0) } MyScaffold(R.string.password_and_keyguard, onNavigateUp, 0.dp) { FunctionItem(R.string.password_info, icon = R.drawable.info_fill0) { onNavigate(PasswordInfo) } if(SharedPrefs(context).displayDangerousFeatures) { - if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) { + if(VERSION.SDK_INT >= 26) { FunctionItem(R.string.reset_password_token, icon = R.drawable.key_vertical_fill0) { onNavigate(ResetPasswordToken) } } - if(deviceAdmin || deviceOwner || profileOwner) { - FunctionItem(R.string.reset_password, icon = R.drawable.lock_reset_fill0) { onNavigate(ResetPassword) } - } + FunctionItem(R.string.reset_password, icon = R.drawable.lock_reset_fill0) { onNavigate(ResetPassword) } } - if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) { + if(VERSION.SDK_INT >= 31) { FunctionItem(R.string.required_password_complexity, icon = R.drawable.password_fill0) { onNavigate(RequiredPasswordComplexity) } } - if(deviceAdmin) { - FunctionItem(R.string.disable_keyguard_features, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(KeyguardDisabledFeatures) } - } - if(deviceOwner) { + FunctionItem(R.string.disable_keyguard_features, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(KeyguardDisabledFeatures) } + if(privilege.device) { FunctionItem(R.string.max_time_to_lock, icon = R.drawable.schedule_fill0) { dialog = 1 } FunctionItem(R.string.pwd_expiration_timeout, icon = R.drawable.lock_clock_fill0) { dialog = 3 } FunctionItem(R.string.max_pwd_fail, icon = R.drawable.no_encryption_fill0) { dialog = 4 } } - if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) { + if(VERSION.SDK_INT >= 26) { FunctionItem(R.string.required_strong_auth_timeout, icon = R.drawable.fingerprint_off_fill0) { dialog = 2 } } - if(deviceAdmin){ - FunctionItem(R.string.pwd_history, icon = R.drawable.history_fill0) { dialog = 5 } - } - if(VERSION.SDK_INT < 31 && (deviceOwner || profileOwner)) { + FunctionItem(R.string.pwd_history, icon = R.drawable.history_fill0) { dialog = 5 } + if(VERSION.SDK_INT < 31) { FunctionItem(R.string.required_password_quality, icon = R.drawable.password_fill0) { onNavigate(RequiredPasswordQuality) } } } @@ -217,8 +211,7 @@ fun PasswordInfoScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() - val deviceOwner = context.isDeviceOwner - val profileOwner = context.isProfileOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() var dialog by remember { mutableIntStateOf(0) } // 0:none, 1:password complexity MyScaffold(R.string.password_info, onNavigateUp, 0.dp) { if(VERSION.SDK_INT >= 29) { @@ -231,10 +224,8 @@ fun PasswordInfoScreen(onNavigateUp: () -> Unit) { } InfoItem(R.string.current_password_complexity, text, true) { dialog = 1 } } - if(deviceOwner || profileOwner) { - InfoItem(R.string.password_sufficient, dpm.isActivePasswordSufficient.yesOrNo) - } - if(VERSION.SDK_INT >= 28 && profileOwner && dpm.isManagedProfile(receiver)) { + InfoItem(R.string.password_sufficient, dpm.isActivePasswordSufficient.yesOrNo) + if(VERSION.SDK_INT >= 28 && privilege.work) { InfoItem(R.string.unified_password, dpm.isUsingUnifiedPassword(receiver).yesOrNo) } } @@ -367,7 +358,7 @@ fun ResetPasswordScreen(onNavigateUp: () -> Unit) { focusMgr.clearFocus() }, colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - enabled = tokenByteArray.size >=32 && password.length !in 1..3 && (context.isDeviceOwner || context.isProfileOwner), + enabled = tokenByteArray.size >=32 && password.length !in 1..3, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.reset_password_with_token)) diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt index 3b217ba..e4a5fff 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt @@ -1,18 +1,14 @@ package com.bintianqi.owndroid.dpm -import android.annotation.SuppressLint import android.app.admin.DevicePolicyManager -import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context -import android.content.Intent import android.content.pm.PackageManager import android.os.Binder import android.os.Build.VERSION import android.os.Bundle import android.os.IBinder import android.os.RemoteException -import android.os.UserManager import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.annotation.Keep @@ -21,7 +17,6 @@ import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.SelectionContainer @@ -43,13 +38,16 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.core.os.bundleOf +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bintianqi.owndroid.ChoosePackageContract import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.R import com.bintianqi.owndroid.SharedPrefs import com.bintianqi.owndroid.backToHomeStateFlow +import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.* +import com.bintianqi.owndroid.updatePrivilege import com.bintianqi.owndroid.writeClipBoard import com.bintianqi.owndroid.yesOrNo import com.rosan.dhizuku.api.Dhizuku @@ -61,19 +59,15 @@ import rikka.sui.Sui @Serializable object Permissions -@SuppressLint("NewApi") @Composable fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNavigateToShizuku: (Bundle) -> Unit) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() - val deviceAdmin = context.isDeviceAdmin - val deviceOwner = context.isDeviceOwner - val profileOwner = context.isProfileOwner - val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager + val privilege by myPrivilege.collectAsStateWithLifecycle() var dialog by remember { mutableIntStateOf(0) } var bindingShizuku by remember { mutableStateOf(false) } - val enrollmentSpecificId = if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) dpm.enrollmentSpecificId else "" + val enrollmentSpecificId = if(VERSION.SDK_INT >= 31 && (privilege.device || privilege.profile)) dpm.enrollmentSpecificId else "" MyScaffold(R.string.permissions, onNavigateUp, 0.dp) { if(!dpm.isDeviceOwnerApp(context.packageName)) { SwitchItem( @@ -83,19 +77,15 @@ fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNav onClickBlank = { dialog = 4 } ) } - FunctionItem( - R.string.device_admin, stringResource(if(deviceAdmin) R.string.activated else R.string.deactivated), - operation = { onNavigate(DeviceAdmin) } - ) - if(profileOwner || !userManager.isSystemUser) { + if(privilege.profile || !privilege.primary) { FunctionItem( - R.string.profile_owner, stringResource(if(profileOwner) R.string.activated else R.string.deactivated), + R.string.profile_owner, stringResource(if(privilege.profile) R.string.activated else R.string.deactivated), operation = { onNavigate(ProfileOwner) } ) } - if(!profileOwner && userManager.isSystemUser) { + if(!privilege.profile && privilege.primary) { FunctionItem( - R.string.device_owner, stringResource(if(deviceOwner) R.string.activated else R.string.deactivated), + R.string.device_owner, stringResource(if(privilege.device) R.string.activated else R.string.deactivated), operation = { onNavigate(DeviceOwner) } ) } @@ -130,25 +120,24 @@ fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNav Toast.makeText(context, R.string.shizuku_not_started, Toast.LENGTH_SHORT).show() } } - if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) - FunctionItem(R.string.delegated_admins) { onNavigate(DelegatedAdmins) } + if(VERSION.SDK_INT >= 26) FunctionItem(R.string.delegated_admins) { onNavigate(DelegatedAdmins) } FunctionItem(R.string.device_info, icon = R.drawable.perm_device_information_fill0) { onNavigate(DeviceInfo) } - if(VERSION.SDK_INT >= 24 && (profileOwner || (VERSION.SDK_INT >= 26 && deviceOwner))) { + if(VERSION.SDK_INT >= 24 && (privilege.profile || (VERSION.SDK_INT >= 26 && privilege.device))) { FunctionItem(R.string.org_name, icon = R.drawable.corporate_fare_fill0) { dialog = 2 } } - if(VERSION.SDK_INT >= 31 && (profileOwner || deviceOwner)) { + if(VERSION.SDK_INT >= 31) { FunctionItem(R.string.org_id, icon = R.drawable.corporate_fare_fill0) { dialog = 3 } } if(enrollmentSpecificId != "") { FunctionItem(R.string.enrollment_specific_id, icon = R.drawable.id_card_fill0) { dialog = 1 } } - if(VERSION.SDK_INT >= 24 && (deviceOwner || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 24 && (privilege.device || privilege.org)) { FunctionItem(R.string.lock_screen_info, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(LockScreenInfo) } } - if(VERSION.SDK_INT >= 24 && deviceAdmin) { + if(VERSION.SDK_INT >= 24) { FunctionItem(R.string.support_messages, icon = R.drawable.chat_fill0) { onNavigate(SupportMessage) } } - if(VERSION.SDK_INT >= 28 && (deviceOwner || profileOwner)) { + if(VERSION.SDK_INT >= 28) { FunctionItem(R.string.transfer_ownership, icon = R.drawable.admin_panel_settings_fill0) { onNavigate(TransferOwnership) } } } @@ -174,7 +163,7 @@ fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNav text = { val focusMgr = LocalFocusManager.current LaunchedEffect(Unit) { - if(dialog == 1) input = dpm.enrollmentSpecificId + if(dialog == 1 && VERSION.SDK_INT >= 31) input = dpm.enrollmentSpecificId } Column { if(dialog != 4) OutlinedTextField( @@ -220,8 +209,8 @@ fun PermissionsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNav TextButton( onClick = { try { - if(dialog == 2) dpm.setOrganizationName(receiver, input) - if(dialog == 3) dpm.setOrganizationId(input) + if(dialog == 2 && VERSION.SDK_INT >= 24) dpm.setOrganizationName(receiver, input) + if(dialog == 3 && VERSION.SDK_INT >= 31) dpm.setOrganizationId(input) dialog = 0 } catch(_: IllegalStateException) { Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() @@ -250,6 +239,7 @@ private fun toggleDhizukuMode(status: Boolean, context: Context) { if(dhizukuPermissionGranted()) { sp.dhizuku = true Dhizuku.init(context) + updatePrivilege(context) backToHomeStateFlow.value = true } else { Dhizuku.requestPermission(object: DhizukuRequestPermissionListener() { @@ -258,6 +248,7 @@ private fun toggleDhizukuMode(status: Boolean, context: Context) { if(grantResult == PackageManager.PERMISSION_GRANTED) { sp.dhizuku = true Dhizuku.init(context) + updatePrivilege(context) backToHomeStateFlow.value = true } else { dhizukuErrorStatus.value = 2 @@ -311,65 +302,6 @@ fun LockScreenInfoScreen(onNavigateUp: () -> Unit) { } } -@Serializable object DeviceAdmin - -@Composable -fun DeviceAdminScreen(onNavigateUp: () -> Unit) { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - var deactivateDialog by remember { mutableStateOf(false) } - val deviceAdmin = context.isDeviceAdmin - MyScaffold(R.string.device_admin, onNavigateUp) { - Text(text = stringResource(if(context.isDeviceAdmin) R.string.activated else R.string.deactivated), style = typography.titleLarge) - Spacer(Modifier.padding(vertical = 5.dp)) - AnimatedVisibility(deviceAdmin) { - Button( - onClick = { deactivateDialog = true }, - enabled = !context.isProfileOwner && !context.isDeviceOwner, - colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError) - ) { - Text(stringResource(R.string.deactivate)) - } - } - AnimatedVisibility(!deviceAdmin) { - Column { - Button(onClick = { activateDeviceAdmin(context, receiver) }, modifier = Modifier.fillMaxWidth()) { - Text(stringResource(R.string.activate_jump)) - } - Spacer(Modifier.padding(vertical = 5.dp)) - SelectionContainer { - Text(text = stringResource(R.string.activate_device_admin_command)) - } - CopyTextButton(R.string.copy_command, stringResource(R.string.activate_device_admin_command)) - } - } - } - if(deactivateDialog) { - AlertDialog( - title = { Text(stringResource(R.string.deactivate)) }, - onDismissRequest = { deactivateDialog = false }, - dismissButton = { - TextButton( - onClick = { deactivateDialog = false } - ) { - Text(stringResource(R.string.cancel)) - } - }, - confirmButton = { - TextButton( - onClick = { - dpm.removeActiveAdmin(receiver) - deactivateDialog = false - } - ) { - Text(stringResource(R.string.confirm)) - } - } - ) - } -} - @Serializable object ProfileOwner @Composable @@ -378,7 +310,8 @@ fun ProfileOwnerScreen(onNavigateUp: () -> Unit) { val dpm = context.getDPM() val receiver = context.getReceiver() var deactivateDialog by remember { mutableStateOf(false) } - val profileOwner = context.isProfileOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() + val profileOwner = privilege.profile MyScaffold(R.string.profile_owner, onNavigateUp) { Text(stringResource(if(profileOwner) R.string.activated else R.string.deactivated), style = typography.titleLarge) Spacer(Modifier.padding(vertical = 5.dp)) @@ -432,7 +365,8 @@ fun DeviceOwnerScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() var deactivateDialog by remember { mutableStateOf(false) } - val deviceOwner = context.isDeviceOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() + val deviceOwner = privilege.device MyScaffold(R.string.device_owner, onNavigateUp) { Text(text = stringResource(if(deviceOwner) R.string.activated else R.string.deactivated), style = typography.titleLarge) Spacer(Modifier.padding(vertical = 5.dp)) @@ -644,15 +578,15 @@ fun AddDelegatedAdminScreen(data: AddDelegatedAdmin, onNavigateUp: () -> Unit) { fun DeviceInfoScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() - val receiver = context.getReceiver() + val privilege by myPrivilege.collectAsStateWithLifecycle() var dialog by remember { mutableIntStateOf(0) } MyScaffold(R.string.device_info, onNavigateUp, 0.dp) { - if(VERSION.SDK_INT>=34 && (context.isDeviceOwner || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT>=34 && (privilege.device || privilege.org)) { InfoItem(R.string.financed_device, dpm.isDeviceFinanced.yesOrNo) } if(VERSION.SDK_INT >= 33) { val dpmRole = dpm.devicePolicyManagementRoleHolderPackage - InfoItem(R.string.dpmrh, if(dpmRole == null) stringResource(R.string.none) else dpmRole) + InfoItem(R.string.dpmrh, dpmRole ?: stringResource(R.string.none)) } val encryptionStatus = mutableMapOf( DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE to R.string.es_inactive, @@ -670,7 +604,7 @@ fun DeviceInfoScreen(onNavigateUp: () -> Unit) { } val adminList = dpm.activeAdmins if(adminList != null) { - InfoItem(R.string.activated_device_admin, adminList.map { it.flattenToShortString() }.joinToString("\n")) + InfoItem(R.string.activated_device_admin, adminList.joinToString("\n") { it.flattenToShortString() }) } } if(dialog != 0) AlertDialog( @@ -766,6 +700,7 @@ fun SupportMessageScreen(onNavigateUp: () -> Unit) { @Composable fun TransferOwnershipScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current + val privilege by myPrivilege.collectAsStateWithLifecycle() val focusMgr = LocalFocusManager.current var input by remember { mutableStateOf("") } val componentName = ComponentName.unflattenFromString(input) @@ -793,7 +728,7 @@ fun TransferOwnershipScreen(onNavigateUp: () -> Unit) { text = { Text(stringResource( R.string.transfer_ownership_warning, - stringResource(if(context.isDeviceOwner) R.string.device_owner else R.string.profile_owner), + stringResource(if(privilege.device) R.string.device_owner else R.string.profile_owner), ComponentName.unflattenFromString(input)!!.packageName )) }, @@ -805,11 +740,12 @@ fun TransferOwnershipScreen(onNavigateUp: () -> Unit) { try { dpm.transferOwnership(receiver, componentName!!, null) context.showOperationResultToast(true) + updatePrivilege(context) dialog = false - backToHomeStateFlow.value = true + onNavigateUp() } catch(e: Exception) { e.printStackTrace() - Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() + context.showOperationResultToast(false) } }, colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error) @@ -825,14 +761,3 @@ fun TransferOwnershipScreen(onNavigateUp: () -> Unit) { onDismissRequest = { dialog = false } ) } - -private fun activateDeviceAdmin(inputContext:Context,inputComponent:ComponentName) { - try { - val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) - intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, inputComponent) - intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, inputContext.getString(R.string.activate_device_admin_here)) - addDeviceAdmin.launch(intent) - } catch(_:ActivityNotFoundException) { - Toast.makeText(inputContext, R.string.unsupported, Toast.LENGTH_SHORT).show() - } -} diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt index 05535fc..19698b1 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Shizuku.kt @@ -19,7 +19,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material3.Button -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -36,9 +35,12 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bintianqi.owndroid.IUserService import com.bintianqi.owndroid.R +import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.ui.MySmallTitleScaffold +import com.bintianqi.owndroid.updatePrivilege import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @@ -46,17 +48,13 @@ import rikka.shizuku.Shizuku @Serializable object ShizukuScreen -@OptIn(ExperimentalMaterial3Api::class) @Composable fun ShizukuScreen(navArgs: Bundle, onNavigateUp: () -> Unit, onNavigateToAccountsViewer: (Accounts) -> Unit) { val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - val coScope = rememberCoroutineScope() + val privilege by myPrivilege.collectAsStateWithLifecycle() + val coroutine = rememberCoroutineScope() val outputTextScrollState = rememberScrollState() var outputText by rememberSaveable { mutableStateOf("") } - var showDeviceOwnerButton by remember { mutableStateOf(!context.isDeviceOwner) } - var showOrgProfileOwnerButton by remember { mutableStateOf(true) } val binder = navArgs.getBinder("binder")!! var service by remember { mutableStateOf(null) } LaunchedEffect(Unit) { @@ -70,7 +68,7 @@ fun ShizukuScreen(navArgs: Bundle, onNavigateUp: () -> Unit, onNavigateToAccount Button( onClick = { - coScope.launch{ + coroutine.launch{ outputText = service!!.execute("dpm list-owners") outputTextScrollState.animateScrollTo(0) } @@ -81,7 +79,7 @@ fun ShizukuScreen(navArgs: Bundle, onNavigateUp: () -> Unit, onNavigateToAccount } Button( onClick = { - coScope.launch{ + coroutine.launch{ outputText = service!!.execute("pm list users") outputTextScrollState.animateScrollTo(0) } @@ -100,7 +98,7 @@ fun ShizukuScreen(navArgs: Bundle, onNavigateUp: () -> Unit, onNavigateToAccount onNavigateToAccountsViewer(Accounts(accounts)) } catch(_: Exception) { outputText = service!!.execute("dumpsys account") - coScope.launch{ + coroutine.launch{ outputTextScrollState.animateScrollTo(0) } } @@ -111,39 +109,37 @@ fun ShizukuScreen(navArgs: Bundle, onNavigateUp: () -> Unit, onNavigateToAccount } Spacer(Modifier.padding(vertical = 5.dp)) - AnimatedVisibility(showDeviceOwnerButton, modifier = Modifier.align(Alignment.CenterHorizontally)) { + AnimatedVisibility(!privilege.device, modifier = Modifier.align(Alignment.CenterHorizontally)) { Button( onClick = { - coScope.launch{ + coroutine.launch{ outputText = service!!.execute(context.getString(R.string.dpm_activate_do_command)) outputTextScrollState.animateScrollTo(0) delay(500) - showDeviceOwnerButton = !context.isDeviceOwner + updatePrivilege(context) } } ) { Text(text = stringResource(R.string.activate_device_owner)) } } - - if(VERSION.SDK_INT >= 30 && context.isProfileOwner && dpm.isManagedProfile(receiver) && !dpm.isOrganizationOwnedDeviceWithManagedProfile) { - AnimatedVisibility(showOrgProfileOwnerButton) { - Button( - onClick = { - coScope.launch{ - val userID = Binder.getCallingUid() / 100000 - outputText = service!!.execute( - "dpm mark-profile-owner-on-organization-owned-device --user $userID com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver" - ) - outputTextScrollState.animateScrollTo(0) - delay(500) - showOrgProfileOwnerButton = !dpm.isOrganizationOwnedDeviceWithManagedProfile - } - }, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) { - Text(text = stringResource(R.string.activate_org_profile)) - } + + AnimatedVisibility(VERSION.SDK_INT >= 30 && privilege.work && !privilege.org) { + Button( + onClick = { + coroutine.launch{ + val userID = Binder.getCallingUid() / 100000 + outputText = service!!.execute( + "dpm mark-profile-owner-on-organization-owned-device --user $userID com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver" + ) + outputTextScrollState.animateScrollTo(0) + delay(500) + updatePrivilege(context) + } + }, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) { + Text(text = stringResource(R.string.activate_org_profile)) } } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt index 12267c7..e4fd9f9 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt @@ -116,6 +116,7 @@ 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 @@ -123,6 +124,7 @@ import com.bintianqi.owndroid.R import com.bintianqi.owndroid.SharedPrefs import com.bintianqi.owndroid.formatFileSize import com.bintianqi.owndroid.humanReadableDate +import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.parseDate import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.CheckBoxItem @@ -160,62 +162,54 @@ fun SystemManagerScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { val dpm = context.getDPM() val receiver = context.getReceiver() val sp = SharedPrefs(context) - val dhizuku = sp.dhizuku - val deviceOwner = context.isDeviceOwner - val profileOwner = context.isProfileOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() var dialog by remember { mutableIntStateOf(0) } MyScaffold(R.string.system, onNavigateUp, 0.dp) { - if(deviceOwner || profileOwner) { - FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SystemOptions) } - } + FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SystemOptions) } FunctionItem(R.string.keyguard, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(Keyguard) } - if(VERSION.SDK_INT >= 24 && deviceOwner && !dhizuku) + if(VERSION.SDK_INT >= 24 && privilege.device && !privilege.dhizuku) FunctionItem(R.string.hardware_monitor, icon = R.drawable.memory_fill0) { onNavigate(HardwareMonitor) } - if(VERSION.SDK_INT >= 24 && deviceOwner) { + if(VERSION.SDK_INT >= 24 && privilege.device) { FunctionItem(R.string.reboot, icon = R.drawable.restart_alt_fill0) { dialog = 1 } } - if(deviceOwner && VERSION.SDK_INT >= 24 && (VERSION.SDK_INT < 28 || dpm.isAffiliatedUser)) { + if(VERSION.SDK_INT >= 24 && privilege.device && (VERSION.SDK_INT < 28 || dpm.isAffiliatedUser)) { FunctionItem(R.string.bug_report, icon = R.drawable.bug_report_fill0) { dialog = 2 } } - if(VERSION.SDK_INT >= 28 && (deviceOwner || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 28 && (privilege.device || privilege.org)) { FunctionItem(R.string.change_time, icon = R.drawable.schedule_fill0) { onNavigate(ChangeTime) } FunctionItem(R.string.change_timezone, icon = R.drawable.schedule_fill0) { onNavigate(ChangeTimeZone) } } /*if(VERSION.SDK_INT >= 28 && (deviceOwner || profileOwner)) FunctionItem(R.string.key_pairs, icon = R.drawable.key_vertical_fill0) { navCtrl.navigate("KeyPairs") }*/ - if(VERSION.SDK_INT >= 35 && (deviceOwner || (profileOwner && dpm.isAffiliatedUser))) + if(VERSION.SDK_INT >= 35 && (privilege.device || (privilege.profile && privilege.affiliated))) FunctionItem(R.string.content_protection_policy, icon = R.drawable.search_fill0) { onNavigate(ContentProtectionPolicy) } - if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) { + if(VERSION.SDK_INT >= 23) { FunctionItem(R.string.permission_policy, icon = R.drawable.key_fill0) { onNavigate(PermissionPolicy) } } - if(VERSION.SDK_INT >= 34 && deviceOwner) { + if(VERSION.SDK_INT >= 34 && privilege.device) { FunctionItem(R.string.mte_policy, icon = R.drawable.memory_fill0) { onNavigate(MtePolicy) } } - if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) { + if(VERSION.SDK_INT >= 31) { FunctionItem(R.string.nearby_streaming_policy, icon = R.drawable.share_fill0) { onNavigate(NearbyStreamingPolicy) } } - if(VERSION.SDK_INT >= 28 && deviceOwner) { + if(VERSION.SDK_INT >= 28 && privilege.device) { FunctionItem(R.string.lock_task_mode, icon = R.drawable.lock_fill0) { onNavigate(LockTaskMode) } } - if(deviceOwner || profileOwner) { - FunctionItem(R.string.ca_cert, icon = R.drawable.license_fill0) { onNavigate(CaCert) } - } - if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || dpm.isOrgProfile(receiver))) { + FunctionItem(R.string.ca_cert, icon = R.drawable.license_fill0) { onNavigate(CaCert) } + if(VERSION.SDK_INT >= 26 && !privilege.dhizuku && (privilege.device || privilege.org)) { FunctionItem(R.string.security_logging, icon = R.drawable.description_fill0) { onNavigate(SecurityLogging) } } - if(deviceOwner || profileOwner) { - FunctionItem(R.string.disable_account_management, icon = R.drawable.account_circle_fill0) { onNavigate(DisableAccountManagement) } - } - if(VERSION.SDK_INT >= 23 && (deviceOwner || dpm.isOrgProfile(receiver))) { + FunctionItem(R.string.disable_account_management, icon = R.drawable.account_circle_fill0) { onNavigate(DisableAccountManagement) } + if(VERSION.SDK_INT >= 23 && (privilege.device || privilege.org)) { FunctionItem(R.string.system_update_policy, icon = R.drawable.system_update_fill0) { onNavigate(SetSystemUpdatePolicy) } } - if(VERSION.SDK_INT >= 29 && (deviceOwner || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 29 && (privilege.device || privilege.org)) { FunctionItem(R.string.install_system_update, icon = R.drawable.system_update_fill0) { onNavigate(InstallSystemUpdate) } } - if(VERSION.SDK_INT >= 30 && (deviceOwner || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 30 && (privilege.device || privilege.org)) { FunctionItem(R.string.frp_policy, icon = R.drawable.device_reset_fill0) { onNavigate(FrpPolicy) } } - if(sp.displayDangerousFeatures && context.isDeviceAdmin && !(VERSION.SDK_INT >= 24 && profileOwner && dpm.isManagedProfile(receiver))) { + if(sp.displayDangerousFeatures && !privilege.work) { FunctionItem(R.string.wipe_data, icon = R.drawable.device_reset_fill0) { onNavigate(WipeData) } } } @@ -253,27 +247,21 @@ fun SystemOptionsScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() - val deviceOwner = context.isDeviceOwner - val profileOwner = context.isProfileOwner - val um = context.getSystemService(Context.USER_SERVICE) as UserManager + val privilege by myPrivilege.collectAsStateWithLifecycle() var dialog by remember { mutableIntStateOf(0) } MyScaffold(R.string.options, onNavigateUp, 0.dp) { - if(deviceOwner || profileOwner) { - SwitchItem(R.string.disable_cam, icon = R.drawable.photo_camera_fill0, - getState = { dpm.getCameraDisabled(null) }, onCheckedChange = { dpm.setCameraDisabled(receiver,it) } - ) - } - if(deviceOwner || profileOwner) { - SwitchItem(R.string.disable_screen_capture, icon = R.drawable.screenshot_fill0, - getState = { dpm.getScreenCaptureDisabled(null) }, onCheckedChange = { dpm.setScreenCaptureDisabled(receiver,it) } - ) - } - if(VERSION.SDK_INT >= 34 && (deviceOwner || (profileOwner && dpm.isAffiliatedUser))) { + SwitchItem(R.string.disable_cam, icon = R.drawable.photo_camera_fill0, + getState = { dpm.getCameraDisabled(null) }, onCheckedChange = { dpm.setCameraDisabled(receiver,it) } + ) + SwitchItem(R.string.disable_screen_capture, icon = R.drawable.screenshot_fill0, + getState = { dpm.getScreenCaptureDisabled(null) }, onCheckedChange = { dpm.setScreenCaptureDisabled(receiver,it) } + ) + if(VERSION.SDK_INT >= 34 && (privilege.device || (privilege.profile && privilege.affiliated))) { SwitchItem(R.string.disable_status_bar, icon = R.drawable.notifications_fill0, getState = { dpm.isStatusBarDisabled}, onCheckedChange = { dpm.setStatusBarDisabled(receiver,it) } ) } - if(deviceOwner || (VERSION.SDK_INT >= 23 && profileOwner && um.isSystemUser) || dpm.isOrgProfile(receiver)) { + if(privilege.device || privilege.org) { if(VERSION.SDK_INT >= 30) { SwitchItem(R.string.auto_time, icon = R.drawable.schedule_fill0, getState = { dpm.getAutoTimeEnabled(receiver) }, onCheckedChange = { dpm.setAutoTimeEnabled(receiver,it) } @@ -286,30 +274,28 @@ fun SystemOptionsScreen(onNavigateUp: () -> Unit) { getState = { dpm.autoTimeRequired }, onCheckedChange = { dpm.setAutoTimeRequired(receiver,it) }, padding = false) } } - if(deviceOwner || profileOwner) { - SwitchItem(R.string.master_mute, icon = R.drawable.volume_off_fill0, - getState = { dpm.isMasterVolumeMuted(receiver) }, onCheckedChange = { dpm.setMasterVolumeMuted(receiver,it) } - ) - } - if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) { + SwitchItem(R.string.master_mute, icon = R.drawable.volume_off_fill0, + getState = { dpm.isMasterVolumeMuted(receiver) }, onCheckedChange = { dpm.setMasterVolumeMuted(receiver,it) } + ) + if(VERSION.SDK_INT >= 26) { SwitchItem(R.string.backup_service, icon = R.drawable.backup_fill0, getState = { dpm.isBackupServiceEnabled(receiver) }, onCheckedChange = { dpm.setBackupServiceEnabled(receiver,it) }, onClickBlank = { dialog = 1 } ) } - if(VERSION.SDK_INT >= 24 && profileOwner && dpm.isManagedProfile(receiver)) { + if(VERSION.SDK_INT >= 24 && privilege.work) { SwitchItem(R.string.disable_bt_contact_share, icon = R.drawable.account_circle_fill0, getState = { dpm.getBluetoothContactSharingDisabled(receiver) }, onCheckedChange = { dpm.setBluetoothContactSharingDisabled(receiver,it) } ) } - if(VERSION.SDK_INT >= 30 && deviceOwner) { + if(VERSION.SDK_INT >= 30 && privilege.device) { SwitchItem(R.string.common_criteria_mode , icon =R.drawable.security_fill0, getState = { dpm.isCommonCriteriaModeEnabled(receiver) }, onCheckedChange = { dpm.setCommonCriteriaModeEnabled(receiver,it) }, onClickBlank = { dialog = 2 } ) } - if(VERSION.SDK_INT >= 31 && (deviceOwner || dpm.isOrgProfile(receiver)) && dpm.canUsbDataSignalingBeDisabled()) { + if(VERSION.SDK_INT >= 31 && (privilege.device || privilege.org) && dpm.canUsbDataSignalingBeDisabled()) { SwitchItem( R.string.disable_usb_signal, icon = R.drawable.usb_fill0, getState = { !dpm.isUsbDataSignalingEnabled }, onCheckedChange = { dpm.isUsbDataSignalingEnabled = !it }, @@ -340,24 +326,21 @@ fun KeyguardScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() - val deviceOwner = context.isDeviceOwner - val profileOwner = context.isProfileOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() MyScaffold(R.string.keyguard, onNavigateUp) { - if(VERSION.SDK_INT >= 23) { + if(VERSION.SDK_INT >= 23 && (privilege.device || (VERSION.SDK_INT >= 28 && privilege.profile && privilege.affiliated))) { Row( horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() ) { Button( onClick = { context.showOperationResultToast(dpm.setKeyguardDisabled(receiver, true)) }, - enabled = deviceOwner || (VERSION.SDK_INT >= 28 && profileOwner && dpm.isAffiliatedUser), modifier = Modifier.fillMaxWidth(0.49F) ) { Text(stringResource(R.string.disable)) } Button( onClick = { context.showOperationResultToast(dpm.setKeyguardDisabled(receiver, false)) }, - enabled = deviceOwner || (VERSION.SDK_INT >= 28 && profileOwner && dpm.isAffiliatedUser), modifier = Modifier.fillMaxWidth(0.96F) ) { Text(stringResource(R.string.enable)) @@ -369,7 +352,7 @@ fun KeyguardScreen(onNavigateUp: () -> Unit) { if(VERSION.SDK_INT >= 23) Text(text = stringResource(R.string.lock_now), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 2.dp)) var flag by remember { mutableIntStateOf(0) } - if(VERSION.SDK_INT >= 26 && profileOwner && dpm.isManagedProfile(receiver)) { + if(VERSION.SDK_INT >= 26 && privilege.work) { CheckBoxItem( R.string.evict_credential_encryption_key, flag and FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY != 0 @@ -380,12 +363,11 @@ fun KeyguardScreen(onNavigateUp: () -> Unit) { onClick = { if(VERSION.SDK_INT >= 26) dpm.lockNow(flag) else dpm.lockNow() }, - enabled = context.isDeviceAdmin, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.lock_now)) } - if(VERSION.SDK_INT >= 26 && profileOwner && dpm.isManagedProfile(receiver)) { + if(VERSION.SDK_INT >= 26 && privilege.work) { Notes(R.string.info_evict_credential_encryption_key) } } @@ -393,7 +375,6 @@ fun KeyguardScreen(onNavigateUp: () -> Unit) { @Serializable object HardwareMonitor -@OptIn(ExperimentalMaterial3Api::class) @RequiresApi(24) @Composable fun HardwareMonitorScreen(onNavigateUp: () -> Unit) { @@ -1332,7 +1313,7 @@ fun CaCertScreen(onNavigateUp: () -> Unit) { text = { if(dialog == 3) Text(stringResource(R.string.uninstall_all_user_ca_cert)) else { - var text = "" + var text: String val sha256 = MessageDigest.getInstance("SHA-256").digest(caCertByteArray).toHexString() try { val cf = CertificateFactory.getInstance("X.509") @@ -1634,6 +1615,7 @@ fun WipeDataScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager val dpm = context.getDPM() + val privilege by myPrivilege.collectAsStateWithLifecycle() val focusMgr = LocalFocusManager.current var flag by remember { mutableIntStateOf(0) } var warning by remember { mutableStateOf(false) } @@ -1642,7 +1624,7 @@ fun WipeDataScreen(onNavigateUp: () -> Unit) { var reason by remember { mutableStateOf("") } MyScaffold(R.string.wipe_data, onNavigateUp) { CheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) { flag = flag xor WIPE_EXTERNAL_STORAGE } - if(VERSION.SDK_INT >= 22 && context.isDeviceOwner) CheckBoxItem( + if(VERSION.SDK_INT >= 22 && privilege.device) CheckBoxItem( R.string.wipe_reset_protection_data, flag and WIPE_RESET_PROTECTION_DATA != 0) { flag = flag xor WIPE_RESET_PROTECTION_DATA } if(VERSION.SDK_INT >= 28) CheckBoxItem(R.string.wipe_euicc, flag and WIPE_EUICC != 0) { flag = flag xor WIPE_EUICC } if(VERSION.SDK_INT >= 29) CheckBoxItem(R.string.wipe_silently, silent) { silent = it } @@ -1667,7 +1649,7 @@ fun WipeDataScreen(onNavigateUp: () -> Unit) { Text("WipeData") } } - if (VERSION.SDK_INT >= 34 && context.isDeviceOwner) { + if (VERSION.SDK_INT >= 34 && privilege.device) { Button( onClick = { focusMgr.clearFocus() diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt index 5f988e0..e603326 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt @@ -10,11 +10,13 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bintianqi.owndroid.R +import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.SwitchItem @@ -33,14 +35,12 @@ data class Restriction( @RequiresApi(24) @Composable fun UserRestrictionScreen(onNavigateUp: () -> Unit, onNavigate: (Int, List) -> Unit) { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() + val privilege by myPrivilege.collectAsStateWithLifecycle() MyScaffold(R.string.user_restriction, onNavigateUp, 0.dp) { Spacer(Modifier.padding(vertical = 2.dp)) Text(text = stringResource(R.string.switch_to_disable_feature), modifier = Modifier.padding(start = 16.dp)) - if(context.isProfileOwner) { Text(text = stringResource(R.string.profile_owner_is_restricted), modifier = Modifier.padding(start = 16.dp)) } - if(context.isProfileOwner && dpm.isManagedProfile(receiver)) { + if(privilege.profile) { Text(text = stringResource(R.string.profile_owner_is_restricted), modifier = Modifier.padding(start = 16.dp)) } + if(privilege.work) { Text(text = stringResource(R.string.some_features_invalid_in_work_profile), modifier = Modifier.padding(start = 16.dp)) } Spacer(Modifier.padding(vertical = 2.dp)) diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt index 4731ca5..1b53b40 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt @@ -64,8 +64,10 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.R +import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.parseTimestamp import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem @@ -89,28 +91,25 @@ fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() - val deviceOwner = context.isDeviceOwner - val profileOwner = context.isProfileOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() var dialog by remember { mutableIntStateOf(0) } MyScaffold(R.string.users, onNavigateUp, 0.dp) { - if(VERSION.SDK_INT >= 28 && profileOwner && dpm.isAffiliatedUser) { + if(VERSION.SDK_INT >= 28 && privilege.profile && privilege.affiliated) { FunctionItem(R.string.logout, icon = R.drawable.logout_fill0) { dialog = 2 } } FunctionItem(R.string.user_info, icon = R.drawable.person_fill0) { onNavigate(UserInfo) } - if(deviceOwner && VERSION.SDK_INT >= 28) { + if(VERSION.SDK_INT >= 28 && privilege.device) { FunctionItem(R.string.secondary_users, icon = R.drawable.list_fill0) { dialog = 1 } FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(UsersOptions) } } - if(deviceOwner) { + if(privilege.device) { FunctionItem(R.string.user_operation, icon = R.drawable.sync_alt_fill0) { onNavigate(UserOperation) } } - if(VERSION.SDK_INT >= 24 && deviceOwner) { + if(VERSION.SDK_INT >= 24 && privilege.device) { FunctionItem(R.string.create_user, icon = R.drawable.person_add_fill0) { onNavigate(CreateUser) } } - if(deviceOwner || profileOwner) { - FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) } - } - if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) { + FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) } + if(VERSION.SDK_INT >= 23) { var changeUserIconDialog by remember { mutableStateOf(false) } var bitmap: Bitmap? by remember { mutableStateOf(null) } val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { @@ -123,12 +122,12 @@ fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { Toast.makeText(context, R.string.select_an_image, Toast.LENGTH_SHORT).show() launcher.launch("image/*") } - if(changeUserIconDialog == true) ChangeUserIconDialog(bitmap!!) { changeUserIconDialog = false } + if(changeUserIconDialog) ChangeUserIconDialog(bitmap!!) { changeUserIconDialog = false } } - if(VERSION.SDK_INT >= 28 && deviceOwner) { + if(VERSION.SDK_INT >= 28 && privilege.device) { FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) } } - if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) { + if(VERSION.SDK_INT >= 26) { FunctionItem(R.string.affiliation_id, icon = R.drawable.id_card_fill0) { onNavigate(AffiliationId) } } } @@ -190,6 +189,7 @@ fun UserInfoScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() + val privilege by myPrivilege.collectAsStateWithLifecycle() val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager val user = Process.myUserHandle() var infoDialog by remember { mutableIntStateOf(0) } @@ -205,10 +205,8 @@ fun UserInfoScreen(onNavigateUp: () -> Unit) { } if (VERSION.SDK_INT >= 28) { InfoItem(R.string.logout_enabled, dpm.isLogoutEnabled.yesOrNo) - if(context.isDeviceOwner || context.isProfileOwner) { - InfoItem(R.string.ephemeral_user, dpm.isEphemeralUser(receiver).yesOrNo) - } - InfoItem(R.string.affiliated_user, dpm.isAffiliatedUser.yesOrNo) + InfoItem(R.string.ephemeral_user, dpm.isEphemeralUser(receiver).yesOrNo) + InfoItem(R.string.affiliated_user, privilege.affiliated.yesOrNo) } InfoItem(R.string.user_id, (Binder.getCallingUid() / 100000).toString()) InfoItem(R.string.user_serial_number, userManager.getSerialNumberForUser(Process.myUserHandle()).toString()) @@ -447,7 +445,7 @@ fun AffiliationIdScreen(onNavigateUp: () -> Unit) { Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { - list.removeAll(listOf("")) + list.removeAll(setOf("")) dpm.setAffiliationIds(receiver, list.toSet()) context.showOperationResultToast(true) refreshIds() diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt index 560783f..c44c4ca 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt @@ -53,7 +53,9 @@ 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.R +import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.CopyTextButton @@ -69,26 +71,16 @@ import kotlinx.serialization.Serializable @Composable fun WorkProfileScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - val profileOwner = context.isProfileOwner + val privilege by myPrivilege.collectAsStateWithLifecycle() MyScaffold(R.string.work_profile, onNavigateUp, 0.dp) { - if(VERSION.SDK_INT >= 30 && profileOwner && dpm.isManagedProfile(receiver)) { + if(VERSION.SDK_INT >= 30) { FunctionItem(R.string.org_owned_work_profile, icon = R.drawable.corporate_fare_fill0) { onNavigate(OrganizationOwnedProfile) } } - if(VERSION.SDK_INT < 24 || dpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)) { - FunctionItem(R.string.create_work_profile, icon = R.drawable.work_fill0) { onNavigate(CreateWorkProfile) } - } - if(dpm.isOrgProfile(receiver)) { + if(privilege.org) { FunctionItem(R.string.suspend_personal_app, icon = R.drawable.block_fill0) { onNavigate(SuspendPersonalApp) } } - if(profileOwner && (VERSION.SDK_INT < 24 || dpm.isManagedProfile(receiver))) { - FunctionItem(R.string.intent_filter, icon = R.drawable.filter_alt_fill0) { onNavigate(CrossProfileIntentFilter) } - } - if(profileOwner && (VERSION.SDK_INT < 24 || dpm.isManagedProfile(receiver))) { - FunctionItem(R.string.delete_work_profile, icon = R.drawable.delete_forever_fill0) { onNavigate(DeleteWorkProfile) } - } + FunctionItem(R.string.intent_filter, icon = R.drawable.filter_alt_fill0) { onNavigate(CrossProfileIntentFilter) } + FunctionItem(R.string.delete_work_profile, icon = R.drawable.delete_forever_fill0) { onNavigate(DeleteWorkProfile) } } } diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a8eed7d..615afe6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -73,8 +73,6 @@ Нажмите для активации - Администратор устройства - Активировать... Владелец профиля Владелец устройства Делегированные администраторы @@ -116,9 +114,7 @@ Активируйте администратора устройства здесь. - OwnDroid: Включено - OwnDroid: Отключено - OwnDroid: Рабочий профиль успешно создан + Рабочий профиль успешно создан Не удалось инициализировать Dhizuku diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 12a75a2..079afed 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,7 +1,5 @@ - OwnDroid - Devre Dışı Etkin Devre Dışı Bırak @@ -64,7 +62,6 @@ Takma Ad Bilinmeyen Hata İzin Reddedildi - API Hata Durum Düzenle @@ -75,8 +72,6 @@ Etkinleştirmek için Tıklayın - Cihaz Yöneticisi - Etkinleştir... Profil Sahibi Cihaz Sahibi Yetkilendirilmiş Yöneticiler @@ -91,9 +86,6 @@ Yetkilendirilmiş Yönetici Ekle Dhizuku Devre Dışı Bırakılacak Cihaz Politikasını Sıfırla - dpm set-active-admin com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver - dpm set-profile-owner --user %1$s com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver - dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver Cihaz Bilgisi Cihaz Kimliği Doğrulamasını Destekler Benzersiz Cihaz Doğrulamasını Destekler @@ -121,22 +113,17 @@ Cihaz Yöneticisini Burada Etkinleştir. - OwnDroid: Etkin - OwnDroid: Devre Dışı - OwnDroid: İş Profili Başarıyla Oluşturuldu + İş Profili Başarıyla Oluşturuldu - Dhizuku Dhizuku Başlatılamadı Dhizuku İzni Verilmedi Dhizuku Modu Devre Dışı - Shizuku Sahip Listesi Kullanıcı Listesi Hesap Listesi Shizuku Başlatılmadı. - dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver Cihaz Sahibini Etkinleştir Kuruluşa Ait İş Profilini Etkinleştir Hesaplar @@ -246,7 +233,6 @@ Wi-Fi MAC Adresi - Wi-Fi Bağlantıyı Kes Yeniden Bağlan Kayıtlı Ağlar @@ -277,13 +263,11 @@ Hedef Cihaz Kullanıcı - UID UID Etiketi UID Etiket Durumu Ağ Türü Mobil Ethernet - VPN Abone Kimliği Tümü Kaldırılmış @@ -357,9 +341,6 @@ Hesap Adı Hesabı Koru Kuruluşa Ait İş Profili - - dpm mark-profile-owner-on-organization-owned-device --user %1$s com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver - Şifrelemeyi Atla Oluştur Kişisel Uygulamayı Askıya Al @@ -440,7 +421,6 @@ Bluetooth Paylaşımı Konumu Paylaş Konumu Yapılandır - NFC Giden Işın USB Dosya Aktarımı Fiziksel Medyayı Bağla @@ -501,7 +481,6 @@ Çıkış Yapma Etkin Geçici Kullanıcı Bağlı Kullanıcı - User ID Kullanıcı Seri Numarası İkincil Kullanıcılar İkincil Kullanıcı Yok diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c3fee51..979865a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -70,8 +70,6 @@ 点击以激活 - Device admin - 激活... Profile owner Device owner 委托管理员 @@ -112,9 +110,7 @@ 在这里激活Device admin - OwnDroid:已启用 - OwnDroid:已禁用 - OwnDroid:创建工作资料成功 + 创建工作资料成功 Dhizuku初始化失败 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 62827ca..358fbb9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -75,8 +75,6 @@ Click to activate - Device admin - Activate... Profile owner Device owner Delegated admins @@ -91,7 +89,6 @@ Add delegated admin Dhizuku will be deactivated Reset device policy - dpm set-active-admin com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver dpm set-profile-owner --user %1$s com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver Device info @@ -121,9 +118,7 @@ Activate Device admin here. - OwnDroid: Enabled - OwnDroid: Disabled - OwnDroid: Create work profile success + Create work profile success Dhizuku