diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41478fd..b628522 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,17 +41,10 @@ jobs: name: OwnDroid-CI-${{ env.SHORT_SHA }}-release-testkey path: app/build/outputs/apk/release/app-release.apk - - name: Export key - env: - KEY_BASE64: ${{ secrets.KEY_BASE64 }} - run: echo "$KEY_BASE64" | base64 --decode - > app/signature.jks - - name: Build APK - env: - KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} - KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} - KEY_ALIAS: ${{ secrets.KEY_ALIAS }} - run: ./gradlew build + run: | + echo "${{ secrets.KEY_BASE64 }}" | base64 --decode - > app/release.jks + ./gradlew build -PStoreFile="$(pwd)/app/release.jks" -PStorePassword="${{ secrets.KEYSTORE_PASSWORD }}" -PKeyPassword="${{ secrets.KEY_PASSWORD }}" -PKeyAlias="${{ secrets.KEY_ALIAS }}" - name: Upload Debug APK uses: actions/upload-artifact@v4 @@ -77,7 +70,7 @@ jobs: with: path: artifacts - - name: Download telegram-bot-api-binary + - name: Download telegram-bot-api run: | mkdir ./binaries wget "https://github.com/jakbin/telegram-bot-api-binary/releases/download/latest/telegram-bot-api" -O ./binaries/telegram-bot-api @@ -86,27 +79,18 @@ jobs: - name: Start API Server & Upload env: COMMIT_MESSAGE: |+ - New push to GitHub\! - ``` - ${{ github.event.head_commit.message }} - ```by `${{ github.event.head_commit.author.name }}` - See commit detail [here](${{ github.event.head_commit.url }}) +
${{ github.event.head_commit.message }}
+ See commit details here COMMIT_URL: ${{ github.event.head_commit.url }} run: | ESCAPED=`python3 -c 'import json,os,urllib.parse; msg = json.dumps(os.environ["COMMIT_MESSAGE"]); print(urllib.parse.quote(msg if len(msg) <= 1024 else json.dumps(os.environ["COMMIT_URL"])))'` cd artifacts - export DEBUG_TEST_PWD=$(find . -name "*-debug-testkey*") - mv ./$DEBUG_TEST_PWD/app-debug.apk ./$DEBUG_TEST_PWD.apk && rm -rf ./$DEBUG_TEST_PWD export RELEASE_TEST_PWD=$(find . -name "*release-testkey*") mv ./$RELEASE_TEST_PWD/app-release.apk ./$RELEASE_TEST_PWD.apk && rm -rf ./$RELEASE_TEST_PWD - export DEBUG_SIGNED_PWD=$(find . -name "*-debug-signed*") - mv ./$DEBUG_SIGNED_PWD/app-debug.apk ./$DEBUG_SIGNED_PWD.apk && rm -rf ./$DEBUG_SIGNED_PWD export RELEASE_SIGNED_PWD=$(find . -name "*release-signed*") mv ./$RELEASE_SIGNED_PWD/app-release.apk ./$RELEASE_SIGNED_PWD.apk && rm -rf ./$RELEASE_SIGNED_PWD ../binaries/telegram-bot-api --api-id=${{ secrets.TELEGRAM_API_APP_ID }} --api-hash=${{ secrets.TELEGRAM_API_HASH }} --local 2>&1 > /dev/null & export token=${{ secrets.TELEGRAM_BOT_KEY }} - curl -v "http://127.0.0.1:8081/bot$token/sendMediaGroup?chat_id=-1002216379163&media=%5B%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FdebugTest%22%7D%2C%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseTest%22%7D%2C%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FdebugSigned%22%7D%2C%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseSigned%22%2C%22parse_mode%22%3A%22MarkdownV2%22%2C%22caption%22%3A${ESCAPED}%7D%5D" \ - -F debugTest="@$DEBUG_TEST_PWD.apk" \ + curl -v "http://127.0.0.1:8081/bot$token/sendMediaGroup?chat_id=-1002216379163&media=%5B%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseTest%22%7D%2C%7B%22type%22%3A%22document%22%2C%22media%22%3A%22attach%3A%2F%2FreleaseSigned%22%2C%22parse_mode%22%3A%22HTML%22%2C%22caption%22%3A${ESCAPED}%7D%5D" \ -F releaseTest="@$RELEASE_TEST_PWD.apk" \ - -F debugSigned="@$DEBUG_SIGNED_PWD.apk" \ -F releaseSigned="@$RELEASE_SIGNED_PWD.apk" diff --git a/.gitignore b/.gitignore index 14d0ffa..38e98fa 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,4 @@ captures app/build app/release app/debug -app/signature.jks .androidide diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1b01983..d341c0b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,17 +4,13 @@ plugins { alias(libs.plugins.cc) } -var keyPassword: String? = null -var keystorePassword: String? = null -var keyAlias: String? = null - android { signingConfigs { create("defaultSignature") { - storeFile = file("signature.jks") - storePassword = System.getenv("KEYSTORE_PASSWORD") ?: "testkey" - keyPassword = System.getenv("KEY_PASSWORD") ?: "testkey" - keyAlias = System.getenv("KEY_ALIAS") ?: "testkey" + storeFile = file(project.findProperty("StoreFile") ?: "testkey.jks") + storePassword = (project.findProperty("StorePassword") as String?) ?: "testkey" + keyPassword = (project.findProperty("KeyPassword") as String?) ?: "testkey" + keyAlias = (project.findProperty("KeyAlias") as String?) ?: "testkey" } } namespace = "com.bintianqi.owndroid" @@ -27,8 +23,8 @@ android { applicationId = "com.bintianqi.owndroid" minSdk = 21 targetSdk = 34 - versionCode = 31 - versionName = "5.6" + versionCode = 32 + versionName = "6.0" multiDexEnabled = false } @@ -77,26 +73,6 @@ gradle.taskGraph.whenReady { project.tasks.findByPath(":app:lintAnalyzeDebug")?.enabled = false } -tasks.findByName("preBuild")?.dependsOn?.plusAssign("prepareSignature") - -tasks.register("prepareSignature") { - doFirst { - file("signature.jks").let { - if(!it.exists()) file("testkey.jks").copyTo(it) - } - } -} - -tasks.findByName("clean")?.dependsOn?.plusAssign("cleanKey") - -tasks.register("cleanKey") { - doFirst { - file("signature.jks").let { - if(it.exists()) it.delete() - } - } -} - dependencies { implementation(libs.androidx.activity.compose) implementation(libs.androidx.ui) @@ -106,6 +82,8 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.shizuku.provider) implementation(libs.shizuku.api) + implementation(libs.dhizuku.api) implementation(libs.androidx.biometric) implementation(libs.androidx.fragment) + implementation(libs.hiddenApiBypass) } \ No newline at end of file diff --git a/app/src/main/java/android/app/admin/IDevicePolicyManager.java b/app/src/main/java/android/app/admin/IDevicePolicyManager.java new file mode 100644 index 0000000..dd9b3b9 --- /dev/null +++ b/app/src/main/java/android/app/admin/IDevicePolicyManager.java @@ -0,0 +1,17 @@ +package android.app.admin; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; + +import androidx.annotation.Keep; + +@Keep +public interface IDevicePolicyManager extends IInterface { + @Keep + abstract class Stub extends Binder implements IDevicePolicyManager { + public static IDevicePolicyManager asInterface(IBinder obj) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/app/src/main/java/android/content/pm/IPackageInstaller.java b/app/src/main/java/android/content/pm/IPackageInstaller.java new file mode 100644 index 0000000..1dc957a --- /dev/null +++ b/app/src/main/java/android/content/pm/IPackageInstaller.java @@ -0,0 +1,17 @@ +package android.content.pm; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; + +import androidx.annotation.Keep; + +@Keep +public interface IPackageInstaller extends IInterface { + @Keep + abstract class Stub extends Binder implements IPackageInstaller { + public static IPackageInstaller asInterface(IBinder obj) { + throw new UnsupportedOperationException(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bintianqi/owndroid/AutomationReceiver.kt b/app/src/main/java/com/bintianqi/owndroid/AutomationReceiver.kt index ee32652..036d8b5 100644 --- a/app/src/main/java/com/bintianqi/owndroid/AutomationReceiver.kt +++ b/app/src/main/java/com/bintianqi/owndroid/AutomationReceiver.kt @@ -1,12 +1,11 @@ package com.bintianqi.owndroid import android.annotation.SuppressLint -import android.app.admin.DevicePolicyManager import android.content.BroadcastReceiver -import android.content.ComponentName import android.content.Context import android.content.Intent -import androidx.activity.ComponentActivity +import com.bintianqi.owndroid.dpm.getDPM +import com.bintianqi.owndroid.dpm.getReceiver class AutomationReceiver: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -25,8 +24,8 @@ fun handleTask(context: Context, intent: Intent): String { return "Wrong key" } val operation = intent.getStringExtra("operation") - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val app = intent.getStringExtra("app") val restriction = intent.getStringExtra("restriction") try { diff --git a/app/src/main/java/com/bintianqi/owndroid/InstallAppActivity.kt b/app/src/main/java/com/bintianqi/owndroid/InstallAppActivity.kt index f1bdef1..21b9023 100644 --- a/app/src/main/java/com/bintianqi/owndroid/InstallAppActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/InstallAppActivity.kt @@ -36,7 +36,7 @@ import kotlinx.coroutines.withContext import java.io.FileInputStream class InstallAppActivity: FragmentActivity() { - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) this.intent = intent } @@ -58,7 +58,7 @@ class InstallAppActivity: FragmentActivity() { ) fd?.close() withContext(Dispatchers.Main) { - status = "waiting" + status = "pending" apkInfoText = "${context.getString(R.string.package_name)}: ${apkInfo.packageName}\n" apkInfoText += "${context.getString(R.string.version_name)}: ${apkInfo.versionName}\n" apkInfoText += "${context.getString(R.string.version_code)}: ${apkInfo.versionCode}" @@ -79,7 +79,7 @@ class InstallAppActivity: FragmentActivity() { }, text = { Column { - AnimatedVisibility(status != "waiting") { + AnimatedVisibility(status != "pending") { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } Text(text = apkInfoText, modifier = Modifier.padding(top = 4.dp)) diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index a180ab2..9d5b268 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -1,27 +1,42 @@ package com.bintianqi.owndroid import android.app.admin.DevicePolicyManager -import android.content.ComponentName import android.content.Context import android.os.Build.VERSION import android.os.Bundle import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +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 @@ -37,10 +52,27 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.bintianqi.owndroid.dpm.* +import com.bintianqi.owndroid.dpm.ApplicationManage +import com.bintianqi.owndroid.dpm.DpmPermissions +import com.bintianqi.owndroid.dpm.ManagedProfile +import com.bintianqi.owndroid.dpm.Network +import com.bintianqi.owndroid.dpm.Password +import com.bintianqi.owndroid.dpm.SystemManage +import com.bintianqi.owndroid.dpm.UserManage +import com.bintianqi.owndroid.dpm.UserRestriction +import com.bintianqi.owndroid.dpm.dhizukuErrorStatus +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.toggleInstallAppActivity import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.theme.OwnDroidTheme +import com.rosan.dhizuku.api.Dhizuku +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow +import org.lsposed.hiddenapibypass.HiddenApiBypass import java.util.Locale var backToHomeStateFlow = MutableStateFlow(false) @@ -54,11 +86,13 @@ class MainActivity : FragmentActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) super.onCreate(savedInstanceState) val sharedPref = applicationContext.getSharedPreferences("data", Context.MODE_PRIVATE) + if (VERSION.SDK_INT >= 28) HiddenApiBypass.setHiddenApiExemptions("") if(sharedPref.getBoolean("auth", false)) { showAuth.value = true } val locale = applicationContext.resources?.configuration?.locale zhCN = locale == Locale.SIMPLIFIED_CHINESE || locale == Locale.CHINESE || locale == Locale.CHINA + toggleInstallAppActivity() setContent { val materialYou = remember { mutableStateOf(sharedPref.getBoolean("material_you", true)) } val blackTheme = remember { mutableStateOf(sharedPref.getBoolean("black_theme", false)) } @@ -80,6 +114,14 @@ class MainActivity : FragmentActivity() { ) { showAuth.value = true } + if (sharedPref.getBoolean("dhizuku", false)) { + if (Dhizuku.init(applicationContext)) { + if (!Dhizuku.isPermissionGranted()) { dhizukuErrorStatus.value = 2 } + } else { + sharedPref.edit().putBoolean("dhizuku", false).apply() + dhizukuErrorStatus.value = 1 + } + } } } @@ -89,11 +131,10 @@ class MainActivity : FragmentActivity() { fun Home(materialYou:MutableState, blackTheme:MutableState) { val navCtrl = rememberNavController() val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val dpm = context.getDPM() + val receiver = context.getReceiver() + val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) val focusMgr = LocalFocusManager.current - val pkgName = remember { mutableStateOf("") } val dialogStatus = remember { mutableIntStateOf(0) } val backToHome by backToHomeStateFlow.collectAsState() LaunchedEffect(backToHome) { @@ -112,43 +153,51 @@ fun Home(materialYou:MutableState, blackTheme:MutableState) { popEnterTransition = Animations.navHostPopEnterTransition, popExitTransition = Animations.navHostPopExitTransition ) { - composable(route = "HomePage") { HomePage(navCtrl, pkgName) } + composable(route = "HomePage") { HomePage(navCtrl) } composable(route = "SystemManage") { SystemManage(navCtrl) } composable(route = "ManagedProfile") { ManagedProfile(navCtrl) } composable(route = "Permissions") { DpmPermissions(navCtrl) } - composable(route = "ApplicationManage") { ApplicationManage(navCtrl, pkgName, dialogStatus) } + composable(route = "ApplicationManage") { ApplicationManage(navCtrl, dialogStatus) } composable(route = "UserRestriction") { UserRestriction(navCtrl) } composable(route = "UserManage") { UserManage(navCtrl) } composable(route = "Password") { Password(navCtrl) } composable(route = "AppSetting") { AppSetting(navCtrl, materialYou, blackTheme) } composable(route = "Network") { Network(navCtrl) } - composable(route = "PackageSelector") { PackageSelector(navCtrl, pkgName) } - composable(route = "PermissionPicker") { PermissionPicker(navCtrl) } + composable(route = "PackageSelector") { PackageSelector(navCtrl) } } LaunchedEffect(Unit) { val profileInited = sharedPref.getBoolean("ManagedProfileActivated", false) - val profileNotActivated = !profileInited && isProfileOwner(dpm) && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver))) + val profileNotActivated = !profileInited && context.isProfileOwner && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver))) if(profileNotActivated) { dpm.setProfileEnabled(receiver) sharedPref.edit().putBoolean("ManagedProfileActivated", true).apply() Toast.makeText(context, R.string.work_profile_activated, Toast.LENGTH_SHORT).show() } } + DhizukuErrorDialog() } @Composable -private fun HomePage(navCtrl:NavHostController, pkgName: MutableState) { +private fun HomePage(navCtrl:NavHostController) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) - val activateType = stringResource( - if(isDeviceOwner(dpm)) { R.string.device_owner } - else if(isProfileOwner(dpm)) { - if(VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)) R.string.work_profile_owner else R.string.profile_owner - } - else if(dpm.isAdminActive(receiver)) R.string.device_admin else R.string.click_to_activate - ) - LaunchedEffect(Unit) { pkgName.value = "" } + val dpm = context.getDPM() + val receiver = context.getReceiver() + val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) + var activateType by remember { mutableStateOf("") } + val deviceAdmin = context.isDeviceAdmin + val deviceOwner = context.isDeviceOwner + val profileOwner = context.isProfileOwner + val refreshStatus by dhizukuErrorStatus.collectAsState() + LaunchedEffect(refreshStatus) { + activateType = if(sharedPref.getBoolean("dhizuku", false)) 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 + ) + } Column(modifier = Modifier.background(colorScheme.background).statusBarsPadding().verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 25.dp)) Text( @@ -168,14 +217,14 @@ private fun HomePage(navCtrl:NavHostController, pkgName: MutableState) { ) { Spacer(modifier = Modifier.padding(start = 22.dp)) Icon( - painter = painterResource(if(dpm.isAdminActive(receiver)) R.drawable.check_circle_fill1 else R.drawable.block_fill0), + painter = painterResource(if(deviceAdmin) R.drawable.check_circle_fill1 else R.drawable.block_fill0), contentDescription = null, tint = colorScheme.onPrimary ) Spacer(modifier = Modifier.padding(start = 10.dp)) Column { Text( - text = stringResource(if(dpm.isAdminActive(receiver)) R.string.activated else R.string.deactivated), + text = stringResource(if(deviceAdmin) R.string.activated else R.string.deactivated), style = typography.headlineSmall, color = colorScheme.onPrimary, modifier = Modifier.padding(bottom = 2.dp) @@ -184,17 +233,17 @@ private fun HomePage(navCtrl:NavHostController, pkgName: MutableState) { } } HomePageItem(R.string.system_manage, R.drawable.mobile_phone_fill0, "SystemManage", navCtrl) - if(VERSION.SDK_INT >= 24 && (isDeviceOwner(dpm)) || isProfileOwner(dpm)) { HomePageItem(R.string.network, R.drawable.wifi_fill0, "Network", navCtrl) } + if(deviceOwner || profileOwner) { HomePageItem(R.string.network, R.drawable.wifi_fill0, "Network", navCtrl) } if( - (VERSION.SDK_INT < 24 && !isDeviceOwner(dpm)) || ( + (VERSION.SDK_INT < 24 && !deviceOwner) || ( VERSION.SDK_INT >= 24 && (dpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE) || - (isProfileOwner(dpm) && dpm.isManagedProfile(receiver))) + (profileOwner && dpm.isManagedProfile(receiver))) ) ) { HomePageItem(R.string.work_profile, R.drawable.work_fill0, "ManagedProfile", navCtrl) } HomePageItem(R.string.app_manager, R.drawable.apps_fill0, "ApplicationManage", navCtrl) - if(VERSION.SDK_INT>=24) { + if(VERSION.SDK_INT >= 24 && (profileOwner || deviceOwner)) { HomePageItem(R.string.user_restrict, R.drawable.person_off, "UserRestriction", navCtrl) } HomePageItem(R.string.user_manager,R.drawable.manage_accounts_fill0,"UserManage", navCtrl) @@ -229,3 +278,37 @@ fun HomePageItem(name: Int, imgVector: Int, navTo: String, navCtrl: NavHostContr ) } } + +@Composable +private fun DhizukuErrorDialog() { + val status by dhizukuErrorStatus.collectAsState() + if (status != 0) { + val context = LocalContext.current + val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) + LaunchedEffect(Unit) { + context.toggleInstallAppActivity() + delay(200) + sharedPref.edit().putBoolean("dhizuku", false).apply() + } + AlertDialog( + onDismissRequest = { dhizukuErrorStatus.value = 0 }, + confirmButton = { + TextButton(onClick = { dhizukuErrorStatus.value = 0 }) { + Text(stringResource(R.string.confirm)) + } + }, + title = { Text(stringResource(R.string.dhizuku)) }, + text = { + var text = stringResource( + when(status){ + 1 -> R.string.failed_to_init_dhizuku + 2 -> R.string.dhizuku_permission_not_granted + else -> R.string.failed_to_init_dhizuku + } + ) + if(sharedPref.getBoolean("dhizuku", false)) text += "\n" + stringResource(R.string.dhizuku_mode_disabled) + Text(text) + } + ) + } +} diff --git a/app/src/main/java/com/bintianqi/owndroid/PermissionPicker.kt b/app/src/main/java/com/bintianqi/owndroid/PermissionPicker.kt deleted file mode 100644 index b7134a7..0000000 --- a/app/src/main/java/com/bintianqi/owndroid/PermissionPicker.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.bintianqi.owndroid - -import android.Manifest -import android.os.Build.VERSION -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.navigation.NavHostController -import com.bintianqi.owndroid.dpm.selectedPermission -import com.bintianqi.owndroid.ui.NavIcon - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun PermissionPicker(navCtrl: NavHostController) { - Scaffold( - topBar = { - TopAppBar( - title = { Text(text = stringResource(R.string.permission_picker)) }, - navigationIcon = { NavIcon{ navCtrl.navigateUp() } }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.background) - ) - } - ) { paddingValues-> - LazyColumn( - modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding()) - ) { - items(permissionList()) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clickable{ - selectedPermission.value = it.permission - navCtrl.navigateUp() - } - .padding(vertical = 8.dp, horizontal = 8.dp) - ) { - Icon( - painter = painterResource(it.icon), - contentDescription = stringResource(it.label), - modifier = Modifier.padding(start = 8.dp, end = 10.dp) - ) - Column { - Text(text = stringResource(it.label)) - Text(text = it.permission, modifier = Modifier.alpha(0.8F), style = MaterialTheme.typography.bodyMedium) - } - } - } - items(1) { Spacer(Modifier.padding(vertical = 30.dp)) } - } - } -} - -private data class PermissionPickerItem( - val permission: String, - @StringRes val label: Int, - @DrawableRes val icon: Int -) - -private fun permissionList(): List{ - val list = mutableListOf() - list.add(PermissionPickerItem(Manifest.permission.READ_EXTERNAL_STORAGE, R.string.permission_READ_EXTERNAL_STORAGE, R.drawable.folder_fill0)) - list.add(PermissionPickerItem(Manifest.permission.WRITE_EXTERNAL_STORAGE, R.string.permission_WRITE_EXTERNAL_STORAGE, R.drawable.folder_fill0)) - if(VERSION.SDK_INT >= 33) { - list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_AUDIO, R.string.permission_READ_MEDIA_AUDIO, R.drawable.music_note_fill0)) - list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_VIDEO, R.string.permission_READ_MEDIA_VIDEO, R.drawable.movie_fill0)) - list.add(PermissionPickerItem(Manifest.permission.READ_MEDIA_IMAGES, R.string.permission_READ_MEDIA_IMAGES, R.drawable.image_fill0)) - } - list.add(PermissionPickerItem(Manifest.permission.CAMERA, R.string.permission_CAMERA, R.drawable.photo_camera_fill0)) - list.add(PermissionPickerItem(Manifest.permission.RECORD_AUDIO, R.string.permission_RECORD_AUDIO, R.drawable.mic_fill0)) - list.add(PermissionPickerItem(Manifest.permission.ACCESS_COARSE_LOCATION, R.string.permission_ACCESS_COARSE_LOCATION, R.drawable.location_on_fill0)) - list.add(PermissionPickerItem(Manifest.permission.ACCESS_FINE_LOCATION, R.string.permission_ACCESS_FINE_LOCATION, R.drawable.location_on_fill0)) - if(VERSION.SDK_INT >= 29) { - list.add(PermissionPickerItem(Manifest.permission.ACCESS_BACKGROUND_LOCATION, R.string.permission_ACCESS_BACKGROUND_LOCATION, R.drawable.location_on_fill0)) - } - list.add(PermissionPickerItem(Manifest.permission.READ_CONTACTS, R.string.permission_READ_CONTACTS, R.drawable.contacts_fill0)) - list.add(PermissionPickerItem(Manifest.permission.WRITE_CONTACTS, R.string.permission_WRITE_CONTACTS, R.drawable.contacts_fill0)) - list.add(PermissionPickerItem(Manifest.permission.READ_CALENDAR, R.string.permission_READ_CALENDAR, R.drawable.calendar_month_fill0)) - list.add(PermissionPickerItem(Manifest.permission.WRITE_CALENDAR, R.string.permission_WRITE_CALENDAR, R.drawable.calendar_month_fill0)) - list.add(PermissionPickerItem(Manifest.permission.CALL_PHONE, R.string.permission_CALL_PHONE, R.drawable.call_fill0)) - list.add(PermissionPickerItem(Manifest.permission.READ_PHONE_STATE, R.string.permission_READ_PHONE_STATE, R.drawable.mobile_phone_fill0)) - list.add(PermissionPickerItem(Manifest.permission.READ_SMS, R.string.permission_READ_SMS, R.drawable.sms_fill0)) - list.add(PermissionPickerItem(Manifest.permission.RECEIVE_SMS, R.string.permission_RECEIVE_SMS, R.drawable.sms_fill0)) - list.add(PermissionPickerItem(Manifest.permission.SEND_SMS, R.string.permission_SEND_SMS, R.drawable.sms_fill0)) - list.add(PermissionPickerItem(Manifest.permission.READ_CALL_LOG, R.string.permission_READ_CALL_LOG, R.drawable.call_log_fill0)) - list.add(PermissionPickerItem(Manifest.permission.WRITE_CALL_LOG, R.string.permission_WRITE_CALL_LOG, R.drawable.call_log_fill0)) - list.add(PermissionPickerItem(Manifest.permission.BODY_SENSORS, R.string.permission_BODY_SENSORS, R.drawable.sensors_fill0)) - if(VERSION.SDK_INT >= 33) { - list.add(PermissionPickerItem(Manifest.permission.BODY_SENSORS_BACKGROUND, R.string.permission_BODY_SENSORS_BACKGROUND, R.drawable.sensors_fill0)) - } - if(VERSION.SDK_INT > 29) { - list.add(PermissionPickerItem(Manifest.permission.ACTIVITY_RECOGNITION, R.string.permission_ACTIVITY_RECOGNITION, R.drawable.history_fill0)) - } - if(VERSION.SDK_INT >= 33) { - list.add(PermissionPickerItem(Manifest.permission.POST_NOTIFICATIONS, R.string.permission_POST_NOTIFICATIONS, R.drawable.notifications_fill0)) - } - return list -} diff --git a/app/src/main/java/com/bintianqi/owndroid/PkgSelector.kt b/app/src/main/java/com/bintianqi/owndroid/PkgSelector.kt index d99c909..2f8e542 100644 --- a/app/src/main/java/com/bintianqi/owndroid/PkgSelector.kt +++ b/app/src/main/java/com/bintianqi/owndroid/PkgSelector.kt @@ -1,34 +1,60 @@ package com.bintianqi.owndroid +import android.content.pm.ApplicationInfo import android.graphics.drawable.Drawable import android.widget.Toast import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.runtime.* +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import com.bintianqi.owndroid.ui.NavIcon import com.google.accompanist.drawablepainter.rememberDrawablePainter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -36,22 +62,27 @@ private data class PkgInfo( val pkgName: String, val label: String, val icon: Drawable, - val type:String + val system: Boolean ) private val pkgs = mutableListOf() -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +val selectedPackage = MutableStateFlow("") + +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun PackageSelector(navCtrl: NavHostController, pkgName: MutableState) { +fun PackageSelector(navCtrl: NavHostController) { val context = LocalContext.current val pm = context.packageManager val apps = pm.getInstalledApplications(0) var progress by remember { mutableIntStateOf(0) } var show by remember { mutableStateOf(true) } var hideProgress by remember { mutableStateOf(true) } - var filter by remember { mutableStateOf("data") } + var system by remember { mutableStateOf(false) } + var search by remember { mutableStateOf("") } + var searchMode by remember { mutableStateOf(false) } val scrollState = rememberLazyListState() + val focusMgr = LocalFocusManager.current val co = rememberCoroutineScope() val getPkgList: suspend ()->Unit = { show = false @@ -59,16 +90,9 @@ fun PackageSelector(navCtrl: NavHostController, pkgName: MutableState) { hideProgress = false pkgs.clear() for(pkg in apps) { - val srcDir = pkg.sourceDir pkgs += PkgInfo( pkg.packageName, pkg.loadLabel(pm).toString(), pkg.loadIcon(pm), - if(srcDir.contains("/data/")) { "data" } - else if( - srcDir.contains("system/priv-app")||srcDir.contains("product/priv-app")|| - srcDir.contains("ext/priv-app")||srcDir.contains("vendor/priv-app") - ) {"priv"} - else if(srcDir.contains("apex")) {"apex"} - else{"system"} + (pkg.flags and ApplicationInfo.FLAG_SYSTEM) != 0 ) withContext(Dispatchers.Main) { progress += 1 } } @@ -80,54 +104,68 @@ fun PackageSelector(navCtrl: NavHostController, pkgName: MutableState) { topBar = { TopAppBar( actions = { - Icon( - painter = painterResource(R.drawable.filter_alt_fill0), - contentDescription = "filter", - modifier = Modifier - .padding(horizontal = 6.dp) - .clip(RoundedCornerShape(50)) - .combinedClickable( - onClick = { - when(filter) { - "data"-> { - filter = "system"; co.launch {scrollState.scrollToItem(0) } - Toast.makeText(context, R.string.show_system_app, Toast.LENGTH_SHORT).show() - } - "system"-> { - filter = "priv"; co.launch {scrollState.scrollToItem(0) } - Toast.makeText(context, R.string.show_priv_app, Toast.LENGTH_SHORT).show() - } - else-> { - filter = "data"; co.launch {scrollState.scrollToItem(0) } - Toast.makeText(context, R.string.show_user_app, Toast.LENGTH_SHORT).show() - } - } - }, - onLongClick = { - filter = "apex" - Toast.makeText(context, R.string.show_apex_app, Toast.LENGTH_SHORT).show() - } - ) - .padding(5.dp) - ) - Icon( - painter = painterResource(R.drawable.refresh_fill0), - contentDescription = "refresh", - modifier = Modifier - .padding(horizontal = 6.dp) - .clip(RoundedCornerShape(50)) - .clickable{ - co.launch{ - getPkgList() + if(!searchMode) { + Icon( + painter = painterResource(R.drawable.search_fill0), + contentDescription = "search", + modifier = Modifier + .padding(horizontal = 4.dp) + .clip(RoundedCornerShape(50)) + .clickable { searchMode = true } + .padding(5.dp) + ) + Icon( + painter = painterResource(R.drawable.filter_alt_fill0), + contentDescription = "filter", + modifier = Modifier + .padding(horizontal = 4.dp) + .clip(RoundedCornerShape(50)) + .clickable { + system = !system + Toast.makeText(context, if(system) R.string.show_system_app else R.string.show_user_app, Toast.LENGTH_SHORT).show() } - } - .padding(5.dp) - ) + .padding(5.dp) + ) + Icon( + painter = painterResource(R.drawable.refresh_fill0), + contentDescription = "refresh", + modifier = Modifier + .padding(horizontal = 4.dp) + .clip(RoundedCornerShape(50)) + .clickable { co.launch { getPkgList() } } + .padding(5.dp) + ) + } }, title = { - Text(text = stringResource(R.string.pkg_selector)) + if(searchMode) { + val fr = FocusRequester() + LaunchedEffect(Unit) { fr.requestFocus() } + OutlinedTextField( + value = search, + onValueChange = { search = it }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions { focusMgr.clearFocus() }, + placeholder = { Text(stringResource(R.string.search)) }, + trailingIcon = { + Icon( + painter = painterResource(R.drawable.close_fill0), + contentDescription = "clear search", + modifier = Modifier.clickable { + focusMgr.clearFocus() + search = "" + searchMode = false + } + ) + }, + textStyle = typography.bodyLarge, + modifier = Modifier.fillMaxWidth().focusRequester(fr) + ) + } else { + Text(stringResource(R.string.pkg_selector)) + } }, - navigationIcon = { NavIcon{navCtrl.navigateUp() } }, + navigationIcon = { NavIcon{ navCtrl.navigateUp() } }, colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.background) ) } @@ -144,12 +182,18 @@ fun PackageSelector(navCtrl: NavHostController, pkgName: MutableState) { } if(show) { items(pkgs) { - if(filter == it.type) { - PackageItem(it, navCtrl, pkgName) + if(system == it.system) { + if(search != "") { + if(it.pkgName.contains(search, ignoreCase = true) || it.label.contains(search, ignoreCase = true)) { + PackageItem(it, navCtrl) + } + } else { + PackageItem(it, navCtrl) + } } } items(1) { Spacer(Modifier.padding(vertical = 30.dp)) } - }else{ + } else { items(1) { Spacer(Modifier.padding(top = 5.dp)) Text(text = stringResource(R.string.loading), modifier = Modifier.alpha(0.8F)) @@ -157,30 +201,32 @@ fun PackageSelector(navCtrl: NavHostController, pkgName: MutableState) { } } LaunchedEffect(Unit) { - if(pkgs.size==0) { getPkgList() } + if(pkgs.size == 0) { getPkgList() } } } } @Composable -private fun PackageItem(pkg: PkgInfo, navCtrl: NavHostController, pkgName: MutableState) { +private fun PackageItem(pkg: PkgInfo, navCtrl: NavHostController) { + val focusMgr = LocalFocusManager.current Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() - .clickable{ pkgName.value = pkg.pkgName; navCtrl.navigateUp() } - .padding(vertical = 3.dp) + .clickable{ + focusMgr.clearFocus() + selectedPackage.value = pkg.pkgName + navCtrl.navigateUp() + } + .padding(horizontal = 8.dp, vertical = 10.dp) ) { - Spacer(Modifier.padding(start = 15.dp)) Image( painter = rememberDrawablePainter(pkg.icon), contentDescription = "App icon", - modifier = Modifier.size(50.dp) + modifier = Modifier.padding(start = 12.dp, end = 18.dp).size(40.dp) ) - Spacer(Modifier.padding(start = 15.dp)) Column { Text(text = pkg.label, style = typography.titleLarge) Text(text = pkg.pkgName, modifier = Modifier.alpha(0.8F)) - Spacer(Modifier.padding(top = 3.dp)) } } } diff --git a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt index 5bb2b60..39b8970 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt @@ -3,32 +3,43 @@ package com.bintianqi.owndroid import android.annotation.SuppressLint import android.app.NotificationManager import android.app.admin.DeviceAdminReceiver -import android.app.admin.DevicePolicyManager import android.content.BroadcastReceiver -import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.pm.PackageInstaller.* +import android.content.pm.PackageInstaller.EXTRA_STATUS +import android.content.pm.PackageInstaller.STATUS_FAILURE +import android.content.pm.PackageInstaller.STATUS_FAILURE_ABORTED +import android.content.pm.PackageInstaller.STATUS_FAILURE_BLOCKED +import android.content.pm.PackageInstaller.STATUS_FAILURE_CONFLICT +import android.content.pm.PackageInstaller.STATUS_FAILURE_INCOMPATIBLE +import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID +import android.content.pm.PackageInstaller.STATUS_FAILURE_STORAGE +import android.content.pm.PackageInstaller.STATUS_FAILURE_TIMEOUT +import android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION +import android.content.pm.PackageInstaller.STATUS_SUCCESS import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity -import androidx.compose.ui.platform.LocalContext +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.toggleInstallAppActivity import kotlinx.coroutines.flow.MutableStateFlow class Receiver : DeviceAdminReceiver() { override fun onEnabled(context: Context, intent: Intent) { super.onEnabled(context, intent) - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, this::class.java) - if(dpm.isAdminActive(receiver) || isProfileOwner(dpm) || isDeviceOwner(dpm)){ + context.toggleInstallAppActivity() + if(context.isDeviceAdmin || context.isProfileOwner || context.isDeviceOwner){ Toast.makeText(context, context.getString(R.string.onEnabled), Toast.LENGTH_SHORT).show() } } override fun onDisabled(context: Context, intent: Intent) { super.onDisabled(context, intent) + context.toggleInstallAppActivity() Toast.makeText(context, R.string.onDisabled, Toast.LENGTH_SHORT).show() } @@ -68,12 +79,12 @@ class PackageInstallerReceiver:BroadcastReceiver(){ class StopLockTaskModeReceiver: BroadcastReceiver() { @SuppressLint("NewApi") override fun onReceive(context: Context, intent: Intent) { - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val packages = dpm.getLockTaskPackages(receiver) dpm.setLockTaskPackages(receiver, arrayOf()) dpm.setLockTaskPackages(receiver, packages) - val nm = context.getSystemService(ComponentActivity.NOTIFICATION_SERVICE) as NotificationManager + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager nm.cancel(1) } } diff --git a/app/src/main/java/com/bintianqi/owndroid/Setting.kt b/app/src/main/java/com/bintianqi/owndroid/Setting.kt index c9da38a..b016648 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Setting.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Setting.kt @@ -67,11 +67,11 @@ private fun Home(navCtrl: NavHostController) { @Composable private fun Options() { val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(start = 20.dp, end = 16.dp)) { SwitchItem( R.string.show_dangerous_features, "", R.drawable.warning_fill0, { sharedPref.getBoolean("dangerous_features", false) }, - { sharedPref.edit().putBoolean("dangerous_features", it).apply() } + { sharedPref.edit().putBoolean("dangerous_features", it).apply() }, padding = false ) } } @@ -79,15 +79,15 @@ private fun Options() { @Composable private fun ThemeSettings(materialYou:MutableState, blackTheme:MutableState) { val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { - if(VERSION.SDK_INT>=31) { + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(start = 20.dp, end = 16.dp)) { + if(VERSION.SDK_INT >= 31) { SwitchItem( R.string.material_you_color, stringResource(R.string.dynamic_color_desc), null, { sharedPref.getBoolean("material_you",true) }, { sharedPref.edit().putBoolean("material_you", it).apply() materialYou.value = it - } + }, padding = false ) } if(isSystemInDarkTheme()) { @@ -97,7 +97,7 @@ private fun ThemeSettings(materialYou:MutableState, blackTheme:MutableS { sharedPref.edit().putBoolean("black_theme", it).apply() blackTheme.value = it - } + }, padding = false ) } } @@ -107,37 +107,31 @@ private fun ThemeSettings(materialYou:MutableState, blackTheme:MutableS private fun AuthSettings() { val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) var auth by remember{ mutableStateOf(sharedPref.getBoolean("auth",false)) } - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(start = 20.dp, end = 16.dp)) { SwitchItem( - R.string.lock_owndroid, "", null, - { auth }, + R.string.lock_owndroid, "", null, auth, { sharedPref.edit().putBoolean("auth", it).apply() auth = sharedPref.getBoolean("auth", false) - } + }, padding = false ) if(auth) { SwitchItem( R.string.enable_bio_auth, "", null, { sharedPref.getBoolean("bio_auth", false) }, - { sharedPref.edit().putBoolean("bio_auth", it).apply() } + { sharedPref.edit().putBoolean("bio_auth", it).apply() }, padding = false ) SwitchItem( R.string.lock_in_background, stringResource(R.string.developing), null, { sharedPref.getBoolean("lock_in_background", false) }, - { sharedPref.edit().putBoolean("lock_in_background", it).apply() } + { sharedPref.edit().putBoolean("lock_in_background", it).apply() }, padding = false ) } SwitchItem( R.string.protect_storage, "", null, { sharedPref.getBoolean("protect_storage", false) }, - { sharedPref.edit().putBoolean("protect_storage", it).apply() } + { sharedPref.edit().putBoolean("protect_storage", it).apply() }, padding = false ) - Box(modifier = Modifier.padding(horizontal = 8.dp)) { - Information { - Text(text = stringResource(R.string.auth_on_start)) - } - } } } @@ -166,7 +160,8 @@ private fun Automation() { SwitchItem( R.string.automation_debug, "", null, { sharedPref.getBoolean("automation_debug", false) }, - { sharedPref.edit().putBoolean("automation_debug", it).apply() } + { sharedPref.edit().putBoolean("automation_debug", it).apply() }, + padding = false ) } } @@ -179,13 +174,10 @@ private fun About() { val verName = pkgInfo.versionName Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) - Column(modifier = Modifier.padding(horizontal = 8.dp)) { - Text(text = stringResource(R.string.about), style = typography.headlineLarge) - Spacer(Modifier.padding(vertical = 5.dp)) - Text(text = stringResource(R.string.app_name)+" v$verName ($verCode)") - Text(text = stringResource(R.string.about_desc)) - Spacer(Modifier.padding(vertical = 5.dp)) - } + Text(text = stringResource(R.string.about), style = typography.headlineLarge, modifier = Modifier.padding(start = 26.dp)) + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = stringResource(R.string.app_name)+" v$verName ($verCode)", modifier = Modifier.padding(start = 26.dp)) + Spacer(Modifier.padding(vertical = 5.dp)) SubPageItem(R.string.user_guide, "", R.drawable.open_in_new) { shareLink(context, "https://owndroid.pages.dev") } SubPageItem(R.string.source_code, "", R.drawable.open_in_new) { shareLink(context, "https://github.com/BinTianqi/OwnDroid") } } diff --git a/app/src/main/java/com/bintianqi/owndroid/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/Utils.kt index d966696..67a7713 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Utils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Utils.kt @@ -86,7 +86,7 @@ fun registerActivityResult(context: ComponentActivity){ } createManagedProfile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {} addDeviceAdmin = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - val dpm = context.applicationContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager if(dpm.isAdminActive(ComponentName(context.applicationContext, Receiver::class.java))){ backToHomeStateFlow.value = true } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/ApplicationManage.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/ApplicationManage.kt index 93ee438..32c38d9 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/ApplicationManage.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/ApplicationManage.kt @@ -10,7 +10,7 @@ import android.app.admin.PackagePolicy import android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST import android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM import android.app.admin.PackagePolicy.PACKAGE_POLICY_BLOCKLIST -import android.content.ComponentName +import android.content.Context import android.content.Intent import android.content.pm.PackageManager.NameNotFoundException import android.net.Uri @@ -18,9 +18,9 @@ import android.os.Build.VERSION import android.os.Looper import android.provider.Settings import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement @@ -39,30 +39,33 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextField +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableIntState -import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource @@ -71,119 +74,99 @@ 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.compose.ui.window.Dialog import androidx.core.content.ContextCompat.startActivity import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable -import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.bintianqi.owndroid.InstallAppActivity import com.bintianqi.owndroid.PackageInstallerReceiver import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.Receiver import com.bintianqi.owndroid.fileUriFlow import com.bintianqi.owndroid.getFile +import com.bintianqi.owndroid.selectedPackage import com.bintianqi.owndroid.toText import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.Information +import com.bintianqi.owndroid.ui.NavIcon import com.bintianqi.owndroid.ui.RadioButtonItem import com.bintianqi.owndroid.ui.SubPageItem import com.bintianqi.owndroid.ui.SwitchItem -import com.bintianqi.owndroid.ui.TopBar import java.util.concurrent.Executors -private var dialogConfirmButtonAction = {} -private var dialogDismissButtonAction = {} -private var dialogGetStatus = { false } - +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun ApplicationManage(navCtrl:NavHostController, pkgName: MutableState, dialogStatus: MutableIntState) { +fun ApplicationManage(navCtrl:NavHostController, dialogStatus: MutableIntState) { val focusMgr = LocalFocusManager.current val localNavCtrl = rememberNavController() - val backStackEntry by localNavCtrl.currentBackStackEntryAsState() - val titleMap = mapOf( - "BlockUninstall" to R.string.block_uninstall, - "UserControlDisabled" to R.string.ucd, - "PermissionManage" to R.string.permission_manage, - "CrossProfilePackage" to R.string.cross_profile_package, - "CrossProfileWidget" to R.string.cross_profile_widget, - "CredentialManagePolicy" to R.string.credential_manage_policy, - "Accessibility" to R.string.permitted_accessibility_services, - "IME" to R.string.permitted_ime, - "KeepUninstalled" to R.string.keep_uninstalled_packages, - "InstallApp" to R.string.install_app, - "UninstallApp" to R.string.uninstall_app, - "ClearAppData" to R.string.clear_app_storage, - "DefaultDialer" to R.string.set_default_dialer, - ) - val clearAppDataDialog = remember { mutableStateOf(false) } - val defaultDialerAppDialog = remember { mutableStateOf(false) } - val enableSystemAppDialog = remember { mutableStateOf(false) } + var pkgName by rememberSaveable { mutableStateOf("") } + val updatePackage by selectedPackage.collectAsState() + LaunchedEffect(updatePackage) { + if(updatePackage != "") { + pkgName = updatePackage + selectedPackage.value = "" + } + } Scaffold( topBar = { - TopBar(backStackEntry, navCtrl, localNavCtrl) { - Text(text = stringResource(titleMap[backStackEntry?.destination?.route] ?: R.string.app_manager)) - } + TopAppBar( + title = { + TextField( + value = pkgName, + onValueChange = { pkgName = it }, + label = { Text(stringResource(R.string.package_name)) }, + modifier = Modifier.fillMaxWidth(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + trailingIcon = { + Icon(painter = painterResource(R.drawable.checklist_fill0), contentDescription = null, + modifier = Modifier + .clip(RoundedCornerShape(50)) + .clickable(onClick = { + focusMgr.clearFocus() + navCtrl.navigate("PackageSelector") + }) + .padding(3.dp)) + }, + textStyle = typography.bodyLarge, + singleLine = true + ) + }, + navigationIcon = { NavIcon { navCtrl.navigateUp() } } + ) } ) { paddingValues-> - Column( - modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding()) + NavHost( + modifier = Modifier.padding(top = paddingValues.calculateTopPadding()), + navController = localNavCtrl, startDestination = "Home", + enterTransition = Animations.navHostEnterTransition, + exitTransition = Animations.navHostExitTransition, + popEnterTransition = Animations.navHostPopEnterTransition, + popExitTransition = Animations.navHostPopExitTransition ) { - if(backStackEntry?.destination?.route!="InstallApp") { - TextField( - value = pkgName.value, - onValueChange = { pkgName.value = it }, - label = { Text(stringResource(R.string.package_name)) }, - modifier = Modifier.fillMaxWidth(), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - trailingIcon = { - Icon(painter = painterResource(R.drawable.checklist_fill0), contentDescription = null, - modifier = Modifier - .clip(RoundedCornerShape(50)) - .clickable(onClick = {navCtrl.navigate("PackageSelector")}) - .padding(3.dp)) - }, - singleLine = true - ) - } - NavHost( - navController = localNavCtrl, startDestination = "Home", - enterTransition = Animations.navHostEnterTransition, - exitTransition = Animations.navHostExitTransition, - popEnterTransition = Animations.navHostPopEnterTransition, - popExitTransition = Animations.navHostPopExitTransition - ) { - composable(route = "Home") { - Home(localNavCtrl, pkgName.value, dialogStatus, clearAppDataDialog, defaultDialerAppDialog, enableSystemAppDialog) - } - composable(route = "AlwaysOnVpn") { AlwaysOnVPNPackage(pkgName.value) } - composable(route = "UserControlDisabled") { UserCtrlDisabledPkg(pkgName.value) } - composable(route = "PermissionManage") { PermissionManage(pkgName.value, navCtrl) } - composable(route = "CrossProfilePackage") { CrossProfilePkg(pkgName.value) } - composable(route = "CrossProfileWidget") { CrossProfileWidget(pkgName.value) } - composable(route = "CredentialManagePolicy") { CredentialManagePolicy(pkgName.value) } - composable(route = "Accessibility") { PermittedAccessibility(pkgName.value) } - composable(route = "IME") { PermittedIME(pkgName.value) } - composable(route = "KeepUninstalled") { KeepUninstalledApp(pkgName.value) } - composable(route = "InstallApp") { InstallApp() } - composable(route = "UninstallApp") { UninstallApp(pkgName.value) } + composable(route = "Home") { + Home(localNavCtrl, pkgName, dialogStatus) } + composable(route = "UserControlDisabled") { UserCtrlDisabledPkg(pkgName) } + composable(route = "PermissionManage") { PermissionManage(pkgName) } + composable(route = "CrossProfilePackage") { CrossProfilePkg(pkgName) } + composable(route = "CrossProfileWidget") { CrossProfileWidget(pkgName) } + composable(route = "CredentialManagePolicy") { CredentialManagePolicy(pkgName) } + composable(route = "Accessibility") { PermittedAccessibility(pkgName) } + composable(route = "IME") { PermittedIME(pkgName) } + composable(route = "KeepUninstalled") { KeepUninstalledApp(pkgName) } + composable(route = "InstallApp") { InstallApp() } + composable(route = "UninstallApp") { UninstallApp(pkgName) } } } - if(dialogStatus.intValue!=0) { - LocalFocusManager.current.clearFocus() - AppControlDialog(dialogStatus) - } - if(clearAppDataDialog.value) { - ClearAppDataDialog(clearAppDataDialog, pkgName.value) + when(dialogStatus.intValue) { + 0 -> {} + 1 -> EnableSystemAppDialog(dialogStatus, pkgName) + 2 -> ClearAppDataDialog(dialogStatus, pkgName) + 3 -> DefaultDialerAppDialog(dialogStatus, pkgName) } - if(defaultDialerAppDialog.value) { - DefaultDialerAppDialog(defaultDialerAppDialog, pkgName.value) - } - if(enableSystemAppDialog.value) { - EnableSystemAppDialog(enableSystemAppDialog, pkgName.value) + LaunchedEffect(dialogStatus.intValue) { + focusMgr.clearFocus() } } @@ -191,19 +174,45 @@ fun ApplicationManage(navCtrl:NavHostController, pkgName: MutableState, private fun Home( navCtrl:NavHostController, pkgName: String, - dialogStatus: MutableIntState, - clearAppDataDialog: MutableState, - defaultDialerAppDialog: MutableState, - enableSystemAppDialog: MutableState + dialogStatus: MutableIntState ) { + val context = LocalContext.current + val dpm = context.getDPM() + val receiver = context.getReceiver() + val deviceOwner = context.isDeviceOwner + val profileOwner = context.isProfileOwner + var suspend by remember { mutableStateOf(false) } + suspend = try{ if(VERSION.SDK_INT >= 24) dpm.isPackageSuspended(receiver, pkgName) else false } + catch(e:NameNotFoundException) { false } + catch(e:IllegalArgumentException) { false } + var hide by remember { mutableStateOf(false) } + hide = dpm.isApplicationHidden(receiver, pkgName) + var blockUninstall by remember { mutableStateOf(false) } + blockUninstall = dpm.isUninstallBlocked(receiver,pkgName) + var appControlDialog by remember { mutableStateOf(false) } + var appControlAction by remember { mutableIntStateOf(0) } + val focusMgr = LocalFocusManager.current + val appControl: (Boolean) -> Unit = { + when(appControlAction) { + 1 -> if(VERSION.SDK_INT >= 24) dpm.setPackagesSuspended(receiver, arrayOf(pkgName), it) + 2 -> dpm.setApplicationHidden(receiver, pkgName, it) + 3 -> dpm.setUninstallBlocked(receiver, pkgName, it) + } + when(appControlAction) { + 1 -> { + suspend = try{ if(VERSION.SDK_INT >= 24) dpm.isPackageSuspended(receiver, pkgName) else false } + catch(e:NameNotFoundException) { false } + catch(e:IllegalArgumentException) { false } + } + 2 -> hide = dpm.isApplicationHidden(receiver,pkgName) + 3 -> blockUninstall = dpm.isUninstallBlocked(receiver,pkgName) + } + } Column( modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()) ) { - val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) Spacer(Modifier.padding(vertical = 5.dp)) - if(VERSION.SDK_INT >= 24&&isProfileOwner(dpm)&&dpm.isManagedProfile(receiver)) { + if(VERSION.SDK_INT >= 24 && profileOwner && dpm.isManagedProfile(receiver)) { Text(text = stringResource(R.string.scope_is_work_profile), textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth()) } SubPageItem(R.string.app_info,"", R.drawable.open_in_new) { @@ -211,145 +220,138 @@ private fun Home( intent.setData(Uri.parse("package:$pkgName")) startActivity(context, intent, null) } - if(VERSION.SDK_INT>=24 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { - val getSuspendStatus = { - try{ dpm.isPackageSuspended(receiver, pkgName) } - catch(e:NameNotFoundException) { false } - catch(e:IllegalArgumentException) { false } - } + if(VERSION.SDK_INT >= 24 && (deviceOwner || profileOwner)) { SwitchItem( title = R.string.suspend, desc = "", icon = R.drawable.block_fill0, - getState = getSuspendStatus, - onCheckedChange = { dpm.setPackagesSuspended(receiver, arrayOf(pkgName), it) }, - onClickBlank = { - dialogGetStatus = getSuspendStatus - dialogConfirmButtonAction = { dpm.setPackagesSuspended(receiver, arrayOf(pkgName), true) } - dialogDismissButtonAction = { dpm.setPackagesSuspended(receiver, arrayOf(pkgName), false) } - dialogStatus.intValue = 1 - } + state = suspend, + onCheckedChange = { appControlAction = 1; appControl(it) }, + onClickBlank = { appControlAction = 1; appControlDialog = true } ) } - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { SwitchItem( title = R.string.hide, desc = stringResource(R.string.isapphidden_desc), icon = R.drawable.visibility_off_fill0, - getState = { dpm.isApplicationHidden(receiver,pkgName) }, - onCheckedChange = { dpm.setApplicationHidden(receiver, pkgName, it) }, - onClickBlank = { - dialogGetStatus = { dpm.isApplicationHidden(receiver,pkgName) } - dialogConfirmButtonAction = { dpm.setApplicationHidden(receiver, pkgName, true) } - dialogDismissButtonAction = { dpm.setApplicationHidden(receiver, pkgName, false) } - dialogStatus.intValue = 2 - } + state = hide, + onCheckedChange = { appControlAction = 2; appControl(it) }, + onClickBlank = { appControlAction = 2; appControlDialog = true } ) } - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { SwitchItem( title = R.string.block_uninstall, desc = "", icon = R.drawable.delete_forever_fill0, - getState = { dpm.isUninstallBlocked(receiver,pkgName) }, - onCheckedChange = { dpm.setUninstallBlocked(receiver,pkgName,it) }, - onClickBlank = { - dialogGetStatus = { dpm.isUninstallBlocked(receiver,pkgName) } - dialogConfirmButtonAction = { dpm.setUninstallBlocked(receiver,pkgName,true) } - dialogDismissButtonAction = { dpm.setUninstallBlocked(receiver,pkgName,false) } - dialogStatus.intValue = 3 - } + state = blockUninstall, + onCheckedChange = { appControlAction = 3; appControl(it) }, + onClickBlank = { appControlAction = 3; appControlDialog = true } ) } - if(VERSION.SDK_INT>=24 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { - SubPageItem(R.string.always_on_vpn, "", R.drawable.vpn_key_fill0) { navCtrl.navigate("AlwaysOnVpn") } - } - if((VERSION.SDK_INT>=33&&isProfileOwner(dpm))||(VERSION.SDK_INT>=30&&isDeviceOwner(dpm))) { + if((VERSION.SDK_INT >= 33 && profileOwner) || (VERSION.SDK_INT >= 30 && deviceOwner)) { SubPageItem(R.string.ucd, "", R.drawable.do_not_touch_fill0) { navCtrl.navigate("UserControlDisabled") } } - if(VERSION.SDK_INT>=23&&(isDeviceOwner(dpm)||isProfileOwner(dpm))) { + if(VERSION.SDK_INT>=23&&(deviceOwner||profileOwner)) { SubPageItem(R.string.permission_manage, "", R.drawable.key_fill0) { navCtrl.navigate("PermissionManage") } } - if(VERSION.SDK_INT>=30&&isProfileOwner(dpm)&&dpm.isManagedProfile(receiver)) { + if(VERSION.SDK_INT >= 30 && profileOwner && dpm.isManagedProfile(receiver)) { SubPageItem(R.string.cross_profile_package, "", R.drawable.work_fill0) { navCtrl.navigate("CrossProfilePackage") } } - if(isProfileOwner(dpm)) { + if(profileOwner) { SubPageItem(R.string.cross_profile_widget, "", R.drawable.widgets_fill0) { navCtrl.navigate("CrossProfileWidget") } } - if(VERSION.SDK_INT>=34&&isDeviceOwner(dpm)) { + if(VERSION.SDK_INT >= 34 && deviceOwner) { SubPageItem(R.string.credential_manage_policy, "", R.drawable.license_fill0) { navCtrl.navigate("CredentialManagePolicy") } } - if(isProfileOwner(dpm)||isDeviceOwner(dpm)) { + if(profileOwner || deviceOwner) { SubPageItem(R.string.permitted_accessibility_services, "", R.drawable.settings_accessibility_fill0) { navCtrl.navigate("Accessibility") } } - if(isDeviceOwner(dpm)||isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { SubPageItem(R.string.permitted_ime, "", R.drawable.keyboard_fill0) { navCtrl.navigate("IME") } } - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { - SubPageItem(R.string.enable_system_app, "", R.drawable.enable_fill0) { enableSystemAppDialog.value = true } + if(deviceOwner || profileOwner) { + SubPageItem(R.string.enable_system_app, "", R.drawable.enable_fill0) { + if(pkgName != "") dialogStatus.intValue = 1 + } } - if(VERSION.SDK_INT>=28&&isDeviceOwner(dpm)) { + if(VERSION.SDK_INT >= 28 && deviceOwner) { SubPageItem(R.string.keep_uninstalled_packages, "", R.drawable.delete_fill0) { navCtrl.navigate("KeepUninstalled") } } - if(VERSION.SDK_INT>=28 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 28 && (deviceOwner || profileOwner)) { SubPageItem(R.string.clear_app_storage, "", R.drawable.mop_fill0) { - if(pkgName != "") { clearAppDataDialog.value = true } + if(pkgName != "") dialogStatus.intValue = 2 } } SubPageItem(R.string.install_app, "", R.drawable.install_mobile_fill0) { navCtrl.navigate("InstallApp") } SubPageItem(R.string.uninstall_app, "", R.drawable.delete_fill0) { navCtrl.navigate("UninstallApp") } - if(VERSION.SDK_INT >= 34 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { - SubPageItem(R.string.set_default_dialer, "", R.drawable.call_fill0) { defaultDialerAppDialog.value = true } + if(VERSION.SDK_INT >= 34 && (deviceOwner || dpm.isOrgProfile(receiver))) { + SubPageItem(R.string.set_default_dialer, "", R.drawable.call_fill0) { + if(pkgName != "") dialogStatus.intValue = 3 + } } Spacer(Modifier.padding(vertical = 30.dp)) LaunchedEffect(Unit) { fileUriFlow.value = Uri.parse("") } } -} - -@SuppressLint("NewApi") -@Composable -fun AlwaysOnVPNPackage(pkgName: String) { - val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) - var lockdown by remember { mutableStateOf(false) } - var pkg by remember { mutableStateOf("") } - val refresh = { pkg = dpm.getAlwaysOnVpnPackage(receiver) } - LaunchedEffect(Unit) { refresh() } - val setAlwaysOnVpn: (String?, Boolean)->Unit = { vpnPkg: String?, lockdownEnabled: Boolean -> - try { - dpm.setAlwaysOnVpnPackage(receiver, vpnPkg, lockdownEnabled) - Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - } catch(e: UnsupportedOperationException) { - Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show() - } catch(e: NameNotFoundException) { - Toast.makeText(context, R.string.not_installed, Toast.LENGTH_SHORT).show() + if(appControlDialog) { + LaunchedEffect(Unit) { + focusMgr.clearFocus() } - } - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { - Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.always_on_vpn), style = typography.headlineLarge, modifier = Modifier.padding(8.dp)) - Spacer(Modifier.padding(vertical = 5.dp)) - Text(text = stringResource(R.string.current_app_is) + pkg, modifier = Modifier.padding(8.dp)) - SwitchItem(R.string.enable_lockdown, "", null, { lockdown }, { lockdown = it }) - Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { setAlwaysOnVpn(pkgName, lockdown); refresh() }, - modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp) - ) { - Text(stringResource(R.string.apply)) - } - Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { setAlwaysOnVpn(null, false); refresh() }, - modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp) - ) { - Text(stringResource(R.string.clear_current_config)) - } - Spacer(Modifier.padding(vertical = 30.dp)) + AlertDialog( + onDismissRequest = { appControlDialog = false }, + title = { + Text( + text = stringResource( + when(appControlAction) { + 1 -> R.string.suspend + 2 -> R.string.hide + 3 -> R.string.block_uninstall + 4 -> R.string.always_on_vpn + else -> R.string.unknown + } + ), + style = typography.headlineMedium, + modifier = Modifier.padding(start = 5.dp) + ) + }, + text = { + val enabled = when(appControlAction){ + 1 -> suspend + 2 -> hide + 3 -> blockUninstall + else -> false + } + Text( + text = stringResource(R.string.current_state, stringResource(if(enabled) R.string.enabled else R.string.disabled)), + modifier = Modifier.padding(start = 5.dp, top = 5.dp, bottom = 5.dp) + ) + }, + confirmButton = { + TextButton( + onClick = { + appControl(true) + appControlDialog = false + } + ) { + Text(text = stringResource(R.string.enable)) + } + }, + dismissButton = { + TextButton( + onClick = { + appControl(false) + appControlDialog = false + } + ) { + Text(text = stringResource(R.string.disable)) + } + } + ) } } + @SuppressLint("NewApi") @Composable private fun UserCtrlDisabledPkg(pkgName:String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val pkgList = remember { mutableStateListOf() } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { val refresh = { @@ -403,83 +405,101 @@ private fun UserCtrlDisabledPkg(pkgName:String) { @SuppressLint("NewApi") @Composable -private fun PermissionManage(pkgName: String, navCtrl: NavHostController) { +private fun PermissionManage(pkgName: String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) - val focusMgr = LocalFocusManager.current - var inputPermission by remember { mutableStateOf("") } - var currentState by remember { mutableStateOf(context.getString(R.string.unknown)) } + val dpm = context.getDPM() + val receiver = context.getReceiver() + var showDialog by remember { mutableStateOf(false) } + var selectedPermission by remember { mutableStateOf(PermissionItem("", R.string.unknown, R.drawable.block_fill0)) } + val statusMap = remember { mutableStateMapOf() } val grantState = mapOf( PERMISSION_GRANT_STATE_DEFAULT to stringResource(R.string.default_stringres), PERMISSION_GRANT_STATE_GRANTED to stringResource(R.string.granted), PERMISSION_GRANT_STATE_DENIED to stringResource(R.string.denied) ) - val applyPermission by selectedPermission.collectAsState() - LaunchedEffect(applyPermission) { - if(applyPermission != "") { - inputPermission = applyPermission - selectedPermission.value = "" - } - } LaunchedEffect(pkgName) { - if(pkgName!="") { currentState = grantState[dpm.getPermissionGrantState(receiver,pkgName,inputPermission)]!! } + if(pkgName != "") { + permissionList().forEach { statusMap[it.permission] = dpm.getPermissionGrantState(receiver, pkgName, it.permission) } + } else { + statusMap.clear() + } } - Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { - Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.permission_manage), style = typography.headlineLarge) - Spacer(Modifier.padding(vertical = 5.dp)) - OutlinedTextField( - value = inputPermission, - label = { Text(stringResource(R.string.permission)) }, - onValueChange = { - inputPermission = it - currentState = grantState[dpm.getPermissionGrantState(receiver,pkgName,inputPermission)]!! - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - modifier = Modifier.fillMaxWidth(), - trailingIcon = { - Icon(painter = painterResource(R.drawable.checklist_fill0), contentDescription = null, - modifier = Modifier - .clip(RoundedCornerShape(50)) - .clickable(onClick = { navCtrl.navigate("PermissionPicker") }) - .padding(3.dp)) - } - ) - Spacer(Modifier.padding(vertical = 5.dp)) - Text(stringResource(R.string.current_state, currentState)) - Spacer(Modifier.padding(vertical = 5.dp)) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - dpm.setPermissionGrantState(receiver,pkgName,inputPermission, PERMISSION_GRANT_STATE_GRANTED) - currentState = grantState[dpm.getPermissionGrantState(receiver,pkgName,inputPermission)]!! - }, - modifier = Modifier.fillMaxWidth(0.49F) + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + Spacer(Modifier.padding(vertical = 4.dp)) + for(permission in permissionList()) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { + if(pkgName != "") { + selectedPermission = permission + showDialog = true + } + } + .padding(8.dp) ) { - Text(stringResource(R.string.grant)) + Icon( + painter = painterResource(permission.icon), + contentDescription = stringResource(permission.label), + modifier = Modifier.padding(horizontal = 12.dp) + ) + Column { + Text(text = stringResource(permission.label)) + Text( + text = grantState[statusMap[permission.permission]]?: stringResource(R.string.unknown), + modifier = Modifier.alpha(0.7F), style = typography.bodyMedium + ) + } } - Button( - onClick = { - dpm.setPermissionGrantState(receiver,pkgName,inputPermission, PERMISSION_GRANT_STATE_DENIED) - currentState = grantState[dpm.getPermissionGrantState(receiver,pkgName,inputPermission)]!! - }, - Modifier.fillMaxWidth(0.96F) + } + Spacer(Modifier.padding(vertical = 30.dp)) + } + if(showDialog) { + val grantPermission: (Int)->Unit = { + dpm.setPermissionGrantState(receiver, pkgName, selectedPermission.permission, it) + statusMap[selectedPermission.permission] = dpm.getPermissionGrantState(receiver, pkgName, selectedPermission.permission) + showDialog = false + } + @Composable + fun GrantPermissionItem(label: Int, status: Int) { + val selected = statusMap[selectedPermission.permission] == status + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(if(selected) colorScheme.primaryContainer else Color.Transparent) + .clickable { grantPermission(status) } + .padding(vertical = 16.dp, horizontal = 12.dp) ) { - Text(stringResource(R.string.deny)) + Text(text = stringResource(label), color = if(selected) colorScheme.primary else Color.Unspecified) + if(selected) { + Icon( + painter = painterResource(R.drawable.check_circle_fill0), + contentDescription = stringResource(label), + tint = colorScheme.primary + ) + } } } - Button( - onClick = { - dpm.setPermissionGrantState(receiver,pkgName,inputPermission, PERMISSION_GRANT_STATE_DEFAULT) - currentState = grantState[dpm.getPermissionGrantState(receiver,pkgName,inputPermission)]!! - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.default_stringres)) - } - Spacer(Modifier.padding(vertical = 30.dp)) + AlertDialog( + onDismissRequest = { showDialog = false }, + confirmButton = { TextButton(onClick = { showDialog = false }) { Text(stringResource(R.string.cancel)) } }, + title = { Text(stringResource(selectedPermission.label)) }, + text = { + Column { + Text(selectedPermission.permission) + Spacer(Modifier.padding(vertical = 4.dp)) + if(!(VERSION.SDK_INT >=31 && context.isProfileOwner && selectedPermission.profileOwnerRestricted)) { + GrantPermissionItem(R.string.grant, PERMISSION_GRANT_STATE_GRANTED) + } + GrantPermissionItem(R.string.deny, PERMISSION_GRANT_STATE_DENIED) + GrantPermissionItem(R.string.default_stringres, PERMISSION_GRANT_STATE_DEFAULT) + } + } + ) } } @@ -487,8 +507,8 @@ private fun PermissionManage(pkgName: String, navCtrl: NavHostController) { @Composable private fun CrossProfilePkg(pkgName: String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val crossProfilePkg = remember { mutableStateListOf() } val refresh = { crossProfilePkg.clear() @@ -541,8 +561,8 @@ private fun CrossProfilePkg(pkgName: String) { @Composable private fun CrossProfileWidget(pkgName: String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val pkgList = remember { mutableStateListOf() } val refresh = { pkgList.clear() @@ -598,7 +618,7 @@ private fun CrossProfileWidget(pkgName: String) { @Composable private fun CredentialManagePolicy(pkgName: String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() var policy: PackagePolicy? var policyType by remember{ mutableIntStateOf(-1) } val credentialList = remember { mutableStateListOf() } @@ -627,21 +647,21 @@ private fun CredentialManagePolicy(pkgName: String) { Text(text = stringResource(R.string.credential_manage_policy), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) RadioButtonItem( - stringResource(R.string.none), + R.string.none, policyType == -1, { policyType = -1 } ) RadioButtonItem( - stringResource(R.string.blacklist), + R.string.blacklist, policyType == PACKAGE_POLICY_BLOCKLIST, { policyType = PACKAGE_POLICY_BLOCKLIST } ) RadioButtonItem( - stringResource(R.string.whitelist), + R.string.whitelist, policyType == PACKAGE_POLICY_ALLOWLIST, { policyType = PACKAGE_POLICY_ALLOWLIST } ) RadioButtonItem( - stringResource(R.string.whitelist_and_system_app), + R.string.whitelist_and_system_app, policyType == PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM, { policyType = PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM } ) @@ -691,8 +711,8 @@ private fun CredentialManagePolicy(pkgName: String) { @Composable private fun PermittedAccessibility(pkgName: String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val pkgList = remember { mutableStateListOf() } var allowAll by remember { mutableStateOf(false) } val refresh = { @@ -777,8 +797,8 @@ private fun PermittedAccessibility(pkgName: String) { @Composable private fun PermittedIME(pkgName: String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val permittedIme = remember { mutableStateListOf() } var allowAll by remember { mutableStateOf(false) } val refresh = { @@ -796,19 +816,13 @@ private fun PermittedIME(pkgName: String) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.permitted_ime), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) - Row( - horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(horizontal = 6.dp, vertical = 8.dp) - ) { - Text(stringResource(R.string.allow_all), style = typography.titleLarge) - Switch( - checked = allowAll, - onCheckedChange = { - dpm.setPermittedInputMethods(receiver, if(it) null else listOf()) - refresh() - } - ) - } + SwitchItem( + R.string.allow_all, "", null, allowAll, + { + dpm.setPermittedInputMethods(receiver, if(it) null else listOf()) + refresh() + }, padding = false + ) AnimatedVisibility(!allowAll) { Column { SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize()) { @@ -864,8 +878,8 @@ private fun PermittedIME(pkgName: String) { @Composable private fun KeepUninstalledApp(pkgName: String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val pkgList = remember { mutableStateListOf() } val refresh = { pkgList.clear() @@ -929,9 +943,10 @@ private fun UninstallApp(pkgName: String) { onClick = { val intent = Intent(context, PackageInstallerReceiver::class.java) val intentSender = PendingIntent.getBroadcast(context, 8, intent, PendingIntent.FLAG_IMMUTABLE).intentSender - val pkgInstaller = context.packageManager.packageInstaller + val pkgInstaller = context.getPI() pkgInstaller.uninstall(pkgName, intentSender) }, + enabled = pkgName != "", modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.silent_uninstall)) @@ -942,6 +957,7 @@ private fun UninstallApp(pkgName: String) { intent.setData(Uri.parse("package:$pkgName")) context.startActivity(intent) }, + enabled = pkgName != "", modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.request_uninstall)) @@ -955,6 +971,7 @@ private fun InstallApp() { val context = LocalContext.current val focusMgr = LocalFocusManager.current val selected = fileUriFlow.collectAsState().value != Uri.parse("") + val sharedPrefs = context.getSharedPreferences("data", Context.MODE_PRIVATE) Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.install_app), style = typography.headlineLarge) @@ -980,6 +997,7 @@ private fun InstallApp() { intent.data = fileUriFlow.value context.startActivity(intent) }, + enabled = !sharedPrefs.getBoolean("dhizuku", false) && context.isDeviceOwner, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.silent_install)) @@ -1002,10 +1020,10 @@ private fun InstallApp() { @SuppressLint("NewApi") @Composable -private fun ClearAppDataDialog(status: MutableState, pkgName: String) { +private fun ClearAppDataDialog(status: MutableIntState, pkgName: String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() AlertDialog( title = { Text(text = stringResource(R.string.clear_app_storage)) }, text = { @@ -1025,7 +1043,7 @@ private fun ClearAppDataDialog(status: MutableState, pkgName: String) { Looper.loop() } dpm.clearApplicationUserData(receiver, pkgName, executor, onClear) - status.value = false + status.intValue = 0 }, colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error) ) { @@ -1034,29 +1052,29 @@ private fun ClearAppDataDialog(status: MutableState, pkgName: String) { }, dismissButton = { TextButton( - onClick = { status.value = false } + onClick = { status.intValue = 0 } ) { Text(text = stringResource(R.string.cancel)) } }, - onDismissRequest = { status.value = false }, + onDismissRequest = { status.intValue = 0 }, modifier = Modifier.fillMaxWidth() ) } @SuppressLint("NewApi") @Composable -private fun DefaultDialerAppDialog(status: MutableState, pkgName: String) { +private fun DefaultDialerAppDialog(status: MutableIntState, pkgName: String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() AlertDialog( title = { Text(stringResource(R.string.set_default_dialer)) }, text = { Text(stringResource(R.string.app_will_be_default_dialer) + "\n" + pkgName) }, - onDismissRequest = { status.value = false }, + onDismissRequest = { status.intValue = 0 }, dismissButton = { - TextButton(onClick = { status.value = false }) { + TextButton(onClick = { status.intValue = 0 }) { Text(stringResource(R.string.cancel)) } }, @@ -1069,7 +1087,7 @@ private fun DefaultDialerAppDialog(status: MutableState, pkgName: Strin }catch(e:IllegalArgumentException) { Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() } - status.value = false + status.intValue = 0 } ) { Text(stringResource(R.string.confirm)) @@ -1080,18 +1098,18 @@ private fun DefaultDialerAppDialog(status: MutableState, pkgName: Strin } @Composable -private fun EnableSystemAppDialog(status: MutableState, pkgName: String) { +private fun EnableSystemAppDialog(status: MutableIntState, pkgName: String) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() AlertDialog( title = { Text(stringResource(R.string.enable_system_app)) }, text = { Text(stringResource(R.string.enable_system_app_desc) + "\n" + pkgName) }, - onDismissRequest = { status.value = false }, + onDismissRequest = { status.intValue = 0 }, dismissButton = { - TextButton(onClick = { status.value = false }) { + TextButton(onClick = { status.intValue = 0 }) { Text(stringResource(R.string.cancel)) } }, @@ -1104,7 +1122,7 @@ private fun EnableSystemAppDialog(status: MutableState, pkgName: String } catch(e: IllegalArgumentException) { Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() } - status.value = false + status.intValue = 0 } ) { Text(stringResource(R.string.confirm)) @@ -1113,59 +1131,3 @@ private fun EnableSystemAppDialog(status: MutableState, pkgName: String modifier = Modifier.fillMaxWidth() ) } - -@Composable -private fun AppControlDialog(status: MutableIntState) { - val enabled = dialogGetStatus() - Dialog( - onDismissRequest = { status.intValue = 0 } - ) { - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.fillMaxWidth().padding(15.dp) - ) { - Text( - text = stringResource( - when(status.intValue) { - 1 -> R.string.suspend - 2 -> R.string.hide - 3 -> R.string.block_uninstall - 4 -> R.string.always_on_vpn - else -> R.string.unknown - } - ), - style = typography.headlineMedium, - modifier = Modifier.padding(start = 5.dp) - ) - Text( - text = stringResource(R.string.current_status_is) + stringResource(if(enabled) R.string.enabled else R.string.disabled), - modifier = Modifier.padding(start = 5.dp, top = 5.dp, bottom = 5.dp) - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - TextButton( - onClick = { status.intValue = 0 } - ) { - Text(text = stringResource(R.string.cancel)) - } - Row{ - TextButton( - onClick = { dialogDismissButtonAction(); status.intValue = 0 } - ) { - Text(text = stringResource(R.string.disable)) - } - TextButton( - onClick = { dialogConfirmButtonAction(); status.intValue = 0 } - ) { - Text(text = stringResource(R.string.enable)) - } - } - } - } - } - } -} 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 652ddd6..af9b4c7 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt @@ -1,32 +1,73 @@ package com.bintianqi.owndroid.dpm +import android.Manifest +import android.annotation.SuppressLint import android.app.PendingIntent import android.app.admin.DevicePolicyManager +import android.app.admin.FactoryResetProtectionPolicy +import android.app.admin.IDevicePolicyManager +import android.app.admin.SystemUpdatePolicy import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.pm.IPackageInstaller import android.content.pm.PackageInstaller +import android.content.pm.PackageManager import android.os.Build.VERSION import androidx.activity.result.ActivityResultLauncher +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import com.bintianqi.owndroid.InstallAppActivity import com.bintianqi.owndroid.PackageInstallerReceiver +import com.bintianqi.owndroid.R +import com.bintianqi.owndroid.Receiver +import com.bintianqi.owndroid.backToHomeStateFlow +import com.rosan.dhizuku.api.Dhizuku +import com.rosan.dhizuku.api.Dhizuku.binderWrapper +import com.rosan.dhizuku.api.DhizukuBinderWrapper import kotlinx.coroutines.flow.MutableStateFlow import java.io.IOException import java.io.InputStream -var selectedPermission = MutableStateFlow("") lateinit var createManagedProfile: ActivityResultLauncher lateinit var addDeviceAdmin: ActivityResultLauncher -fun isDeviceOwner(dpm: DevicePolicyManager): Boolean { - return dpm.isDeviceOwnerApp("com.bintianqi.owndroid") -} +val Context.isDeviceOwner: Boolean + get() { + val sharedPref = getSharedPreferences("data", Context.MODE_PRIVATE) + val dpm = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager + return dpm.isDeviceOwnerApp( + if(sharedPref.getBoolean("dhizuku", false)) { + Dhizuku.getOwnerPackageName() + } else { + "com.bintianqi.owndroid" + } + ) + } -fun isProfileOwner(dpm: DevicePolicyManager): Boolean { - return dpm.isProfileOwnerApp("com.bintianqi.owndroid") -} +val Context.isProfileOwner: Boolean + get() { + val dpm = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager + return dpm.isProfileOwnerApp("com.bintianqi.owndroid") + } + +val Context.isDeviceAdmin: Boolean + get() { + return getDPM().isAdminActive(getReceiver()) + } -fun DevicePolicyManager.isOrgProfile(receiver: ComponentName):Boolean { - return VERSION.SDK_INT >= 30 && isProfileOwner(this) && isManagedProfile(receiver) && isOrganizationOwnedDeviceWithManagedProfile +val Context.dpcPackageName: String + get() { + val sharedPref = getSharedPreferences("data", Context.MODE_PRIVATE) + return if(sharedPref.getBoolean("dhizuku", false)) { + Dhizuku.getOwnerPackageName() + } else { + "com.bintianqi.owndroid" + } + } + +fun DevicePolicyManager.isOrgProfile(receiver: ComponentName): Boolean { + return VERSION.SDK_INT >= 30 && this.isProfileOwnerApp("com.bintianqi.owndroid") && isManagedProfile(receiver) && isOrganizationOwnedDeviceWithManagedProfile } @Throws(IOException::class) @@ -46,3 +87,221 @@ fun installPackage(context: Context, inputStream: InputStream) { val pendingIntent = PendingIntent.getBroadcast(context, sessionId, intent, PendingIntent.FLAG_IMMUTABLE).intentSender session.commit(pendingIntent) } + +@SuppressLint("PrivateApi") +private fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? { + try { + val context = appContext.createPackageContext(Dhizuku.getOwnerComponent().packageName, Context.CONTEXT_IGNORE_SECURITY) + val manager = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val field = manager.javaClass.getDeclaredField("mService") + field.isAccessible = true + val oldInterface = field[manager] as IDevicePolicyManager + if (oldInterface is DhizukuBinderWrapper) return manager + val oldBinder = oldInterface.asBinder() + val newBinder = binderWrapper(oldBinder) + val newInterface = IDevicePolicyManager.Stub.asInterface(newBinder) + field[manager] = newInterface + return manager + } catch (e: Exception) { + dhizukuErrorStatus.value = 1 + } + return null +} + +@SuppressLint("PrivateApi") +private fun binderWrapperPackageInstaller(appContext: Context): PackageInstaller? { + try { + val context = appContext.createPackageContext(Dhizuku.getOwnerComponent().packageName, Context.CONTEXT_IGNORE_SECURITY) + val installer = context.packageManager.packageInstaller + val field = installer.javaClass.getDeclaredField("mInstaller") + field.isAccessible = true + val oldInterface = field[installer] as IPackageInstaller + if (oldInterface is DhizukuBinderWrapper) return installer + val oldBinder = oldInterface.asBinder() + val newBinder = binderWrapper(oldBinder) + val newInterface = IPackageInstaller.Stub.asInterface(newBinder) + field[installer] = newInterface + return installer + } catch (e: Exception) { + dhizukuErrorStatus.value = 1 + } + return null +} + +fun Context.getPI(): PackageInstaller { + val sharedPref = this.getSharedPreferences("data", Context.MODE_PRIVATE) + if(sharedPref.getBoolean("dhizuku", false)) { + if (!Dhizuku.isPermissionGranted()) { + dhizukuErrorStatus.value = 2 + backToHomeStateFlow.value = true + return this.packageManager.packageInstaller + } + return binderWrapperPackageInstaller(this) ?: this.packageManager.packageInstaller + } else { + return this.packageManager.packageInstaller + } +} + +fun Context.getDPM(): DevicePolicyManager { + val sharedPref = this.getSharedPreferences("data", Context.MODE_PRIVATE) + if(sharedPref.getBoolean("dhizuku", false)) { + if (!Dhizuku.isPermissionGranted()) { + dhizukuErrorStatus.value = 2 + backToHomeStateFlow.value = true + return this.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager + } + return binderWrapperDevicePolicyManager(this) ?: this.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager + } else { + return this.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager + } +} + +fun Context.getReceiver(): ComponentName { + val sharedPref = this.getSharedPreferences("data", Context.MODE_PRIVATE) + return if(sharedPref.getBoolean("dhizuku", false)) { + Dhizuku.getOwnerComponent() + } else { + ComponentName(this, Receiver::class.java) + } +} + +val dhizukuErrorStatus = MutableStateFlow(0) + +fun Context.resetDevicePolicy() { + val dpm = getDPM() + val receiver = getReceiver() + RestrictionData.getAllRestrictions(this).forEach { + dpm.clearUserRestriction(receiver, it) + } + dpm.accountTypesWithManagementDisabled?.forEach { + dpm.setAccountManagementDisabled(receiver, it, false) + } + if (VERSION.SDK_INT >= 30) { + dpm.setConfiguredNetworksLockdownState(receiver, false) + dpm.setAutoTimeZoneEnabled(receiver, true) + dpm.setAutoTimeEnabled(receiver, true) + dpm.setCommonCriteriaModeEnabled(receiver, false) + try { + val frp = FactoryResetProtectionPolicy.Builder().setFactoryResetProtectionEnabled(false).setFactoryResetProtectionAccounts(listOf()) + dpm.setFactoryResetProtectionPolicy(receiver, frp.build()) + } catch(_: Exception) {} + dpm.setUserControlDisabledPackages(receiver, listOf()) + } + if (VERSION.SDK_INT >= 33) { + dpm.minimumRequiredWifiSecurityLevel = DevicePolicyManager.WIFI_SECURITY_OPEN + dpm.wifiSsidPolicy = null + } + if (VERSION.SDK_INT >= 28) { + dpm.getOverrideApns(receiver).forEach { dpm.removeOverrideApn(receiver, it.id) } + dpm.setKeepUninstalledPackages(receiver, listOf()) + } + dpm.setCameraDisabled(receiver, false) + dpm.setScreenCaptureDisabled(receiver, false) + dpm.setMasterVolumeMuted(receiver, false) + try { + if(VERSION.SDK_INT >= 31) dpm.isUsbDataSignalingEnabled = true + } catch (_: Exception) { } + if (VERSION.SDK_INT >= 23) { + dpm.setPermissionPolicy(receiver, DevicePolicyManager.PERMISSION_POLICY_PROMPT) + dpm.setSystemUpdatePolicy(receiver, SystemUpdatePolicy.createAutomaticInstallPolicy()) + } + if (VERSION.SDK_INT >= 24) { + dpm.setAlwaysOnVpnPackage(receiver, null, false) + dpm.setPackagesSuspended(receiver, arrayOf(), false) + } + dpm.setPermittedInputMethods(receiver, null) + dpm.setPermittedAccessibilityServices(receiver, null) + packageManager.getInstalledApplications(0).forEach { + if (dpm.isUninstallBlocked(receiver, it.packageName)) dpm.setUninstallBlocked(receiver, it.packageName, false) + } + if (VERSION.SDK_INT >= 26) { + dpm.setRequiredStrongAuthTimeout(receiver, 0) + dpm.clearResetPasswordToken(receiver) + } + if (VERSION.SDK_INT >= 31) { + dpm.requiredPasswordComplexity = DevicePolicyManager.PASSWORD_COMPLEXITY_NONE + } + dpm.setKeyguardDisabledFeatures(receiver, 0) + dpm.setMaximumTimeToLock(receiver, 0) + dpm.setPasswordExpirationTimeout(receiver, 0) + dpm.setMaximumFailedPasswordsForWipe(receiver, 0) + dpm.setPasswordHistoryLength(receiver, 0) + if (VERSION.SDK_INT < 31) { + dpm.setPasswordQuality(receiver, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) + } + dpm.setRecommendedGlobalProxy(receiver, null) +} + +fun Context.toggleInstallAppActivity() { + val sharedPrefs = getSharedPreferences("data", Context.MODE_PRIVATE) + val disable = sharedPrefs.getBoolean("dhizuku", false) || !isDeviceOwner + packageManager?.setComponentEnabledSetting( + ComponentName(this, InstallAppActivity::class.java), + if (disable) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP + ) +} + +data class PermissionItem( + val permission: String, + @StringRes val label: Int, + @DrawableRes val icon: Int, + val profileOwnerRestricted: Boolean = false +) + +fun permissionList(): List{ + val list = mutableListOf() + if(VERSION.SDK_INT >= 33) { + list.add(PermissionItem(Manifest.permission.POST_NOTIFICATIONS, R.string.permission_POST_NOTIFICATIONS, R.drawable.notifications_fill0)) + } + list.add(PermissionItem(Manifest.permission.READ_EXTERNAL_STORAGE, R.string.permission_READ_EXTERNAL_STORAGE, R.drawable.folder_fill0)) + list.add(PermissionItem(Manifest.permission.WRITE_EXTERNAL_STORAGE, R.string.permission_WRITE_EXTERNAL_STORAGE, R.drawable.folder_fill0)) + if(VERSION.SDK_INT >= 33) { + list.add(PermissionItem(Manifest.permission.READ_MEDIA_AUDIO, R.string.permission_READ_MEDIA_AUDIO, R.drawable.music_note_fill0)) + list.add(PermissionItem(Manifest.permission.READ_MEDIA_VIDEO, R.string.permission_READ_MEDIA_VIDEO, R.drawable.movie_fill0)) + list.add(PermissionItem(Manifest.permission.READ_MEDIA_IMAGES, R.string.permission_READ_MEDIA_IMAGES, R.drawable.image_fill0)) + } + list.add(PermissionItem(Manifest.permission.CAMERA, R.string.permission_CAMERA, R.drawable.photo_camera_fill0, true)) + list.add(PermissionItem(Manifest.permission.RECORD_AUDIO, R.string.permission_RECORD_AUDIO, R.drawable.mic_fill0, true)) + list.add(PermissionItem(Manifest.permission.ACCESS_COARSE_LOCATION, R.string.permission_ACCESS_COARSE_LOCATION, R.drawable.location_on_fill0, true)) + list.add(PermissionItem(Manifest.permission.ACCESS_FINE_LOCATION, R.string.permission_ACCESS_FINE_LOCATION, R.drawable.location_on_fill0, true)) + if(VERSION.SDK_INT >= 29) { + list.add(PermissionItem(Manifest.permission.ACCESS_BACKGROUND_LOCATION, R.string.permission_ACCESS_BACKGROUND_LOCATION, R.drawable.location_on_fill0, true)) + } + list.add(PermissionItem(Manifest.permission.READ_CONTACTS, R.string.permission_READ_CONTACTS, R.drawable.contacts_fill0)) + list.add(PermissionItem(Manifest.permission.WRITE_CONTACTS, R.string.permission_WRITE_CONTACTS, R.drawable.contacts_fill0)) + list.add(PermissionItem(Manifest.permission.READ_CALENDAR, R.string.permission_READ_CALENDAR, R.drawable.calendar_month_fill0)) + list.add(PermissionItem(Manifest.permission.WRITE_CALENDAR, R.string.permission_WRITE_CALENDAR, R.drawable.calendar_month_fill0)) + if(VERSION.SDK_INT >= 31) { + list.add(PermissionItem(Manifest.permission.BLUETOOTH_CONNECT, R.string.permission_BLUETOOTH_CONNECT, R.drawable.bluetooth_fill0)) + list.add(PermissionItem(Manifest.permission.BLUETOOTH_SCAN, R.string.permission_BLUETOOTH_SCAN, R.drawable.bluetooth_searching_fill0)) + list.add(PermissionItem(Manifest.permission.BLUETOOTH_ADVERTISE, R.string.permission_BLUETOOTH_ADVERTISE, R.drawable.bluetooth_fill0)) + } + if(VERSION.SDK_INT >= 33) { + list.add(PermissionItem(Manifest.permission.NEARBY_WIFI_DEVICES, R.string.permission_NEARBY_WIFI_DEVICES, R.drawable.wifi_fill0)) + } + list.add(PermissionItem(Manifest.permission.CALL_PHONE, R.string.permission_CALL_PHONE, R.drawable.call_fill0)) + if(VERSION.SDK_INT >= 26) { + list.add(PermissionItem(Manifest.permission.ANSWER_PHONE_CALLS, R.string.permission_ANSWER_PHONE_CALLS, R.drawable.call_fill0)) + list.add(PermissionItem(Manifest.permission.READ_PHONE_NUMBERS, R.string.permission_READ_PHONE_STATE, R.drawable.mobile_phone_fill0)) + } + list.add(PermissionItem(Manifest.permission.READ_PHONE_STATE, R.string.permission_READ_PHONE_STATE, R.drawable.mobile_phone_fill0)) + list.add(PermissionItem(Manifest.permission.USE_SIP, R.string.permission_USE_SIP, R.drawable.call_fill0)) + if(VERSION.SDK_INT >= 31) { + list.add(PermissionItem(Manifest.permission.UWB_RANGING, R.string.permission_UWB_RANGING, R.drawable.cell_tower_fill0)) + } + list.add(PermissionItem(Manifest.permission.READ_SMS, R.string.permission_READ_SMS, R.drawable.sms_fill0)) + list.add(PermissionItem(Manifest.permission.RECEIVE_SMS, R.string.permission_RECEIVE_SMS, R.drawable.sms_fill0)) + list.add(PermissionItem(Manifest.permission.SEND_SMS, R.string.permission_SEND_SMS, R.drawable.sms_fill0)) + list.add(PermissionItem(Manifest.permission.READ_CALL_LOG, R.string.permission_READ_CALL_LOG, R.drawable.call_log_fill0)) + list.add(PermissionItem(Manifest.permission.WRITE_CALL_LOG, R.string.permission_WRITE_CALL_LOG, R.drawable.call_log_fill0)) + list.add(PermissionItem(Manifest.permission.RECEIVE_WAP_PUSH, R.string.permission_RECEIVE_WAP_PUSH, R.drawable.wifi_fill0)) + list.add(PermissionItem(Manifest.permission.BODY_SENSORS, R.string.permission_BODY_SENSORS, R.drawable.sensors_fill0, true)) + if(VERSION.SDK_INT >= 33) { + list.add(PermissionItem(Manifest.permission.BODY_SENSORS_BACKGROUND, R.string.permission_BODY_SENSORS_BACKGROUND, R.drawable.sensors_fill0)) + } + if(VERSION.SDK_INT > 29) { + list.add(PermissionItem(Manifest.permission.ACTIVITY_RECOGNITION, R.string.permission_ACTIVITY_RECOGNITION, R.drawable.history_fill0, true)) + } + return list +} diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt index 82ac61e..e14c1fe 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/ManagedProfile.kt @@ -1,7 +1,6 @@ package com.bintianqi.owndroid.dpm import android.annotation.SuppressLint -import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ALLOW_OFFLINE import android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME @@ -18,7 +17,6 @@ import android.content.* import android.os.Binder import android.os.Build.VERSION import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -58,7 +56,6 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.Receiver import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.CopyTextButton @@ -96,17 +93,18 @@ fun ManagedProfile(navCtrl: NavHostController) { @Composable private fun Home(navCtrl: NavHostController) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() + val profileOwner = context.isProfileOwner Column( modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()) ) { Text( text = stringResource(R.string.work_profile), style = typography.headlineLarge, - modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp) + modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 16.dp) ) - if(VERSION.SDK_INT >= 30 && isProfileOwner(dpm) && dpm.isManagedProfile(receiver)) { + if(VERSION.SDK_INT >= 30 && profileOwner && dpm.isManagedProfile(receiver)) { SubPageItem(R.string.org_owned_work_profile, "", R.drawable.corporate_fare_fill0) { navCtrl.navigate("OrgOwnedWorkProfile") } } if(VERSION.SDK_INT<24 || (VERSION.SDK_INT>=24 && dpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE))) { @@ -115,10 +113,10 @@ private fun Home(navCtrl: NavHostController) { if(dpm.isOrgProfile(receiver)) { SubPageItem(R.string.suspend_personal_app, "", R.drawable.block_fill0) { navCtrl.navigate("SuspendPersonalApp") } } - if(isProfileOwner(dpm) && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) { + if(profileOwner && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) { SubPageItem(R.string.intent_filter, "", R.drawable.filter_alt_fill0) { navCtrl.navigate("IntentFilter") } } - if(isProfileOwner(dpm) && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) { + if(profileOwner && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) { SubPageItem(R.string.delete_work_profile, "", R.drawable.delete_forever_fill0) { navCtrl.navigate("DeleteWorkProfile") } } Spacer(Modifier.padding(vertical = 30.dp)) @@ -128,14 +126,14 @@ private fun Home(navCtrl: NavHostController) { @Composable private fun CreateWorkProfile() { val context = LocalContext.current - val receiver = ComponentName(context,Receiver::class.java) + val receiver = context.getReceiver() Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.create_work_profile), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) var skipEncrypt by remember { mutableStateOf(false) } if(VERSION.SDK_INT>=24) { - CheckBoxItem(stringResource(R.string.skip_encryption), skipEncrypt, { skipEncrypt = it }) + CheckBoxItem(R.string.skip_encryption, skipEncrypt, { skipEncrypt = it }) } Spacer(Modifier.padding(vertical = 5.dp)) Button( @@ -165,7 +163,7 @@ private fun CreateWorkProfile() { @Composable private fun OrgOwnedProfile() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.org_owned_work_profile), style = typography.headlineLarge) @@ -188,15 +186,19 @@ private fun OrgOwnedProfile() { @Composable private fun SuspendPersonalApp() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current + var suspend by remember { mutableStateOf(dpm.getPersonalAppsSuspendedReasons(receiver) != PERSONAL_APPS_NOT_SUSPENDED) } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) SwitchItem( R.string.suspend_personal_app, "", null, - { dpm.getPersonalAppsSuspendedReasons(receiver)!=PERSONAL_APPS_NOT_SUSPENDED }, - { dpm.setPersonalAppsSuspended(receiver,it) } + suspend, + { + dpm.setPersonalAppsSuspended(receiver,it) + suspend = dpm.getPersonalAppsSuspendedReasons(receiver) != PERSONAL_APPS_NOT_SUSPENDED + }, padding = false ) var time by remember { mutableStateOf("") } time = dpm.getManagedProfileMaximumTimeOff(receiver).toString() @@ -231,8 +233,8 @@ private fun SuspendPersonalApp() { @Composable private fun IntentFilter() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { var action by remember { mutableStateOf("") } @@ -281,7 +283,7 @@ private fun IntentFilter() { @Composable private fun DeleteWorkProfile() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() val focusMgr = LocalFocusManager.current var warning by remember { mutableStateOf(false) } var externalStorage by remember { mutableStateOf(false) } @@ -296,9 +298,9 @@ private fun DeleteWorkProfile() { modifier = Modifier.padding(6.dp),color = colorScheme.error ) Spacer(Modifier.padding(vertical = 5.dp)) - CheckBoxItem(stringResource(R.string.wipe_external_storage), externalStorage, { externalStorage = it }) - if(VERSION.SDK_INT >= 28) { CheckBoxItem(stringResource(R.string.wipe_euicc), euicc, { euicc = it }) } - if(VERSION.SDK_INT >= 29) { CheckBoxItem(stringResource(R.string.wipe_silently), silent, { silent = it }) } + CheckBoxItem(R.string.wipe_external_storage, externalStorage, { externalStorage = it }) + if(VERSION.SDK_INT >= 28) { CheckBoxItem(R.string.wipe_euicc, euicc, { euicc = it }) } + CheckBoxItem(R.string.wipe_silently, silent, { silent = it }) AnimatedVisibility(!silent && VERSION.SDK_INT >= 28) { OutlinedTextField( value = reason, onValueChange = { reason = it }, @@ -310,6 +312,7 @@ private fun DeleteWorkProfile() { Button( onClick = { focusMgr.clearFocus() + silent = reason == "" warning = true }, colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), @@ -335,7 +338,6 @@ private fun DeleteWorkProfile() { var flag = 0 if(externalStorage) { flag += WIPE_EXTERNAL_STORAGE } if(euicc && VERSION.SDK_INT >= 28) { flag += WIPE_EUICC } - if(silent && VERSION.SDK_INT >= 29) { flag += WIPE_SILENTLY } if(VERSION.SDK_INT >= 28 && !silent) { dpm.wipeData(flag, reason) } else { 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 e718cd4..347db2d 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt @@ -1,7 +1,6 @@ package com.bintianqi.owndroid.dpm import android.annotation.SuppressLint -import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF import android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC import android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME @@ -16,7 +15,10 @@ import android.app.admin.DevicePolicyManager.WIFI_SECURITY_PERSONAL import android.app.admin.WifiSsidPolicy import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST -import android.content.ComponentName +import android.content.Context +import android.content.pm.PackageManager.NameNotFoundException +import android.net.ProxyInfo +import android.net.Uri import android.net.wifi.WifiSsid import android.os.Build.VERSION import android.telephony.TelephonyManager @@ -38,12 +40,13 @@ import android.telephony.data.ApnSetting.PROTOCOL_PPP import android.telephony.data.ApnSetting.PROTOCOL_UNSTRUCTURED import android.util.Log import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -51,12 +54,14 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold @@ -67,17 +72,21 @@ import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType @@ -89,9 +98,10 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.Receiver +import com.bintianqi.owndroid.selectedPackage import com.bintianqi.owndroid.toText import com.bintianqi.owndroid.ui.Animations +import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.RadioButtonItem import com.bintianqi.owndroid.ui.SubPageItem import com.bintianqi.owndroid.ui.SwitchItem @@ -128,6 +138,8 @@ fun Network(navCtrl: NavHostController) { composable(route = "MinWifiSecurityLevel") { WifiSecLevel() } composable(route = "WifiSsidPolicy") { WifiSsidPolicy() } composable(route = "PrivateDNS") { PrivateDNS() } + composable(route = "AlwaysOnVpn") { AlwaysOnVPNPackage(navCtrl) } + composable(route = "RecommendedGlobalProxy") { RecommendedGlobalProxy() } composable(route = "NetworkLog") { NetworkLog() } composable(route = "WifiAuthKeypair") { WifiAuthKeypair() } composable(route = "APN") { APN() } @@ -135,8 +147,8 @@ fun Network(navCtrl: NavHostController) { } if(wifiMacDialog.value && VERSION.SDK_INT >= 24) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() AlertDialog( onDismissRequest = { wifiMacDialog.value = false }, confirmButton = { TextButton(onClick = { wifiMacDialog.value = false }) { Text(stringResource(R.string.confirm)) } }, @@ -150,36 +162,44 @@ fun Network(navCtrl: NavHostController) { @Composable private fun Home(navCtrl:NavHostController, scrollState: ScrollState, wifiMacDialog: MutableState) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() + val deviceOwner = context.isDeviceOwner + val profileOwner = context.isProfileOwner Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) { Text( text = stringResource(R.string.network), style = typography.headlineLarge, - modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp) + modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 16.dp) ) - if(VERSION.SDK_INT >= 24 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 24 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.wifi_mac_addr, "", R.drawable.wifi_fill0) { wifiMacDialog.value = true } } if(VERSION.SDK_INT >= 30) { SubPageItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("Switches") } } - if(VERSION.SDK_INT >= 33 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 33 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.min_wifi_security_level, "", R.drawable.wifi_password_fill0) { navCtrl.navigate("MinWifiSecurityLevel") } } - if(VERSION.SDK_INT >= 33 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 33 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.wifi_ssid_policy, "", R.drawable.wifi_fill0) { navCtrl.navigate("WifiSsidPolicy") } } - if(VERSION.SDK_INT >= 29 && isDeviceOwner(dpm)) { + if(VERSION.SDK_INT >= 29 && deviceOwner) { SubPageItem(R.string.private_dns, "", R.drawable.dns_fill0) { navCtrl.navigate("PrivateDNS") } } - if(VERSION.SDK_INT >= 26&&(isDeviceOwner(dpm) || (isProfileOwner(dpm) && dpm.isManagedProfile(receiver)))) { + if(VERSION.SDK_INT >= 24 && (deviceOwner || profileOwner)) { + SubPageItem(R.string.always_on_vpn, "", R.drawable.vpn_key_fill0) { navCtrl.navigate("AlwaysOnVpn") } + } + if(deviceOwner) { + SubPageItem(R.string.recommended_global_proxy, "", R.drawable.vpn_key_fill0) { navCtrl.navigate("RecommendedGlobalProxy") } + } + if(VERSION.SDK_INT >= 26&&(deviceOwner || (profileOwner && dpm.isManagedProfile(receiver)))) { SubPageItem(R.string.retrieve_net_logs, "", R.drawable.description_fill0) { navCtrl.navigate("NetworkLog") } } - if(VERSION.SDK_INT >= 31 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) { SubPageItem(R.string.wifi_auth_keypair, "", R.drawable.key_fill0) { navCtrl.navigate("WifiAuthKeypair") } } - if(VERSION.SDK_INT >= 28 && isDeviceOwner(dpm)) { + if(VERSION.SDK_INT >= 28 && deviceOwner) { SubPageItem(R.string.override_apn_settings, "", R.drawable.cell_tower_fill0) { navCtrl.navigate("APN") } } Spacer(Modifier.padding(vertical = 30.dp)) @@ -189,19 +209,20 @@ private fun Home(navCtrl:NavHostController, scrollState: ScrollState, wifiMacDia @Composable private fun Switches() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) - Column(modifier = Modifier.fillMaxSize()) { + val dpm = context.getDPM() + val receiver = context.getReceiver() + val deviceOwner = context.isDeviceOwner + Column(modifier = Modifier.fillMaxSize().padding(start = 20.dp, end = 16.dp)) { Spacer(Modifier.padding(vertical = 5.dp)) - if(VERSION.SDK_INT >= 33 && isDeviceOwner(dpm)) { + if(VERSION.SDK_INT >= 33 && deviceOwner) { SwitchItem( R.string.preferential_network_service, stringResource(R.string.developing), R.drawable.globe_fill0, - { dpm.isPreferentialNetworkServiceEnabled }, { dpm.isPreferentialNetworkServiceEnabled = it } + { dpm.isPreferentialNetworkServiceEnabled }, { dpm.isPreferentialNetworkServiceEnabled = it }, padding = false ) } - if(VERSION.SDK_INT>=30 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT>=30 && (deviceOwner || dpm.isOrgProfile(receiver))) { SwitchItem(R.string.lockdown_admin_configured_network, "", R.drawable.wifi_password_fill0, - { dpm.hasLockdownAdminConfiguredNetworks(receiver) }, { dpm.setConfiguredNetworksLockdownState(receiver,it) } + { dpm.hasLockdownAdminConfiguredNetworks(receiver) }, { dpm.setConfiguredNetworksLockdownState(receiver,it) }, padding = false ) } } @@ -211,7 +232,7 @@ private fun Switches() { @Composable private fun WifiSecLevel() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() var selectedWifiSecLevel by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { selectedWifiSecLevel = dpm.minimumRequiredWifiSecurityLevel } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { @@ -219,7 +240,7 @@ private fun WifiSecLevel() { Text(text = stringResource(R.string.min_wifi_security_level), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) RadioButtonItem( - stringResource(R.string.wifi_security_level_open), + R.string.wifi_security_open, selectedWifiSecLevel == WIFI_SECURITY_OPEN, { selectedWifiSecLevel = WIFI_SECURITY_OPEN } ) @@ -255,7 +276,7 @@ private fun WifiSecLevel() { @Composable private fun WifiSsidPolicy() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { var selectedPolicyType by remember { mutableIntStateOf(-1) } @@ -271,17 +292,17 @@ private fun WifiSsidPolicy() { Text(text = stringResource(R.string.wifi_ssid_policy), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) RadioButtonItem( - stringResource(R.string.none), + R.string.none, selectedPolicyType == -1, { selectedPolicyType = -1 } ) RadioButtonItem( - stringResource(R.string.whitelist), + R.string.whitelist, selectedPolicyType == WIFI_SSID_POLICY_TYPE_ALLOWLIST, { selectedPolicyType = WIFI_SSID_POLICY_TYPE_ALLOWLIST } ) RadioButtonItem( - stringResource(R.string.blacklist), + R.string.blacklist, selectedPolicyType == WIFI_SSID_POLICY_TYPE_DENYLIST, { selectedPolicyType = WIFI_SSID_POLICY_TYPE_DENYLIST } ) @@ -363,8 +384,8 @@ private fun WifiSsidPolicy() { @Composable private fun PrivateDNS() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) @@ -429,19 +450,187 @@ private fun PrivateDNS() { } } +@SuppressLint("NewApi") +@Composable +fun AlwaysOnVPNPackage(navCtrl: NavHostController) { + val context = LocalContext.current + val dpm = context.getDPM() + val receiver = context.getReceiver() + var lockdown by rememberSaveable { mutableStateOf(false) } + var pkgName by rememberSaveable { mutableStateOf("") } + val focusMgr = LocalFocusManager.current + val refresh = { pkgName = dpm.getAlwaysOnVpnPackage(receiver) ?: "" } + LaunchedEffect(Unit) { refresh() } + val updatePackage by selectedPackage.collectAsState() + LaunchedEffect(updatePackage) { + if(selectedPackage.value != "") { + pkgName = selectedPackage.value + selectedPackage.value = "" + } + } + val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean -> + try { + dpm.setAlwaysOnVpnPackage(receiver, vpnPkg, lockdownEnabled) + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + true + } catch(e: UnsupportedOperationException) { + Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show() + false + } catch(e: NameNotFoundException) { + Toast.makeText(context, R.string.not_installed, Toast.LENGTH_SHORT).show() + false + } + } + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.always_on_vpn), style = typography.headlineLarge, modifier = Modifier.padding(vertical = 8.dp)) + OutlinedTextField( + value = pkgName, + onValueChange = { pkgName = it }, + label = { Text(stringResource(R.string.package_name)) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + trailingIcon = { + Icon(painter = painterResource(R.drawable.checklist_fill0), contentDescription = null, + modifier = Modifier + .clip(RoundedCornerShape(50)) + .clickable(onClick = { + focusMgr.clearFocus() + navCtrl.navigate("PackageSelector") + }) + .padding(3.dp)) + }, + modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp) + ) + SwitchItem(R.string.enable_lockdown, "", null, lockdown, { lockdown = it }, padding = false) + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = { if(setAlwaysOnVpn(pkgName, lockdown)) refresh() }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = { if(setAlwaysOnVpn(null, false)) refresh() }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.clear_current_config)) + } + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +private fun RecommendedGlobalProxy() { + val context = LocalContext.current + val dpm = context.getDPM() + val receiver = context.getReceiver() + val focusMgr = LocalFocusManager.current + var proxyType by remember { mutableIntStateOf(0) } + var proxyUri by remember { mutableStateOf("") } + var specifyPort by remember { mutableStateOf(false) } + var proxyPort by remember { mutableStateOf("") } + var exclList by remember { mutableStateOf("") } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.recommended_global_proxy), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + RadioButtonItem(R.string.proxy_type_off, proxyType == 0, { proxyType = 0 }) + RadioButtonItem(R.string.proxy_type_pac, proxyType == 1, { proxyType = 1 }) + RadioButtonItem(R.string.proxy_type_direct, proxyType == 2, { proxyType = 2 }) + AnimatedVisibility(proxyType != 0) { + OutlinedTextField( + value = proxyUri, + onValueChange = { proxyUri = it }, + label = { Text(if(proxyType == 1) "URL" else "Host") }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp) + ) + } + AnimatedVisibility(proxyType == 1 && VERSION.SDK_INT >= 30) { + Box(modifier = Modifier.padding(top = 2.dp)) { + CheckBoxItem(R.string.specify_port, specifyPort, { specifyPort = it }) + } + } + AnimatedVisibility((proxyType == 1 && specifyPort && VERSION.SDK_INT >= 30) || proxyType == 2) { + OutlinedTextField( + value = proxyPort, + onValueChange = { proxyPort = it }, + label = { Text(stringResource(R.string.port)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp) + ) + } + AnimatedVisibility(proxyType == 2) { + OutlinedTextField( + value = exclList, + onValueChange = { exclList = it }, + label = { Text(stringResource(R.string.exclude_hosts)) }, + maxLines = 5, + minLines = 2, + modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp) + ) + } + Spacer(Modifier.padding(vertical = 4.dp)) + Button( + onClick = { + if(proxyType == 0) { + dpm.setRecommendedGlobalProxy(receiver, null) + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + return@Button + } + if(proxyUri == "") { + Toast.makeText(context, R.string.invalid_config, Toast.LENGTH_SHORT).show() + return@Button + } + val uri = Uri.parse(proxyUri) + val port: Int + try { + port = proxyPort.toInt() + } catch(e: NumberFormatException) { + Toast.makeText(context, R.string.invalid_config, Toast.LENGTH_SHORT).show() + return@Button + } + val proxyInfo = + if(proxyType == 1) { + if(specifyPort && VERSION.SDK_INT >= 30) { + ProxyInfo.buildPacProxy(uri, port) + } else { + ProxyInfo.buildPacProxy(uri) + } + } else { + ProxyInfo.buildDirectProxy(proxyUri, port, exclList.lines()) + } + if(VERSION.SDK_INT >= 30 && !proxyInfo.isValid) { + Toast.makeText(context, R.string.invalid_config, Toast.LENGTH_SHORT).show() + return@Button + } + dpm.setRecommendedGlobalProxy(receiver, proxyInfo) + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + } +} + @SuppressLint("NewApi") @Composable private fun NetworkLog() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.retrieve_net_logs), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.developing)) Spacer(Modifier.padding(vertical = 5.dp)) - SwitchItem(R.string.enable,"",null, {dpm.isNetworkLoggingEnabled(receiver) }, {dpm.setNetworkLoggingEnabled(receiver,it) }) + SwitchItem(R.string.enable,"",null, { dpm.isNetworkLoggingEnabled(receiver) }, {dpm.setNetworkLoggingEnabled(receiver,it) }, padding = false) Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { @@ -465,7 +654,7 @@ private fun NetworkLog() { @Composable private fun WifiAuthKeypair() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { var keyPair by remember { mutableStateOf("") } @@ -511,8 +700,8 @@ private fun WifiAuthKeypair() { @Composable private fun APN() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { val setting = dpm.getOverrideApns(receiver) @@ -524,7 +713,7 @@ private fun APN() { Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(id = R.string.developing)) Spacer(Modifier.padding(vertical = 5.dp)) - SwitchItem(R.string.enable, "", null, { dpm.isOverrideApnEnabled(receiver) }, { dpm.setOverrideApnsEnabled(receiver,it) }) + SwitchItem(R.string.enable, "", null, { dpm.isOverrideApnEnabled(receiver) }, { dpm.setOverrideApnsEnabled(receiver,it) }, padding = false) Text(text = stringResource(R.string.total_apn_amount, setting.size)) if(setting.size>0) { Text(text = stringResource(R.string.select_a_apn_or_create, setting.size)) @@ -641,13 +830,13 @@ private fun APN() { } Text(text = stringResource(R.string.auth_type), style = typography.titleLarge) - RadioButtonItem(stringResource(R.string.none), selectedAuthType==AUTH_TYPE_NONE , { selectedAuthType=AUTH_TYPE_NONE }) + RadioButtonItem(R.string.none, selectedAuthType==AUTH_TYPE_NONE , { selectedAuthType=AUTH_TYPE_NONE }) RadioButtonItem("CHAP", selectedAuthType == AUTH_TYPE_CHAP , { selectedAuthType = AUTH_TYPE_CHAP }) RadioButtonItem("PAP", selectedAuthType == AUTH_TYPE_PAP, { selectedAuthType = AUTH_TYPE_PAP }) RadioButtonItem("PAP/CHAP", selectedAuthType == AUTH_TYPE_PAP_OR_CHAP, { selectedAuthType = AUTH_TYPE_PAP_OR_CHAP }) if(VERSION.SDK_INT>=29) { - val ts = context.getSystemService(ComponentActivity.TELEPHONY_SERVICE) as TelephonyManager + val ts = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager carrierId = ts.simCarrierId.toString() Text(text = "CarrierID", style = typography.titleLarge) TextField( 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 629c5e7..2e0af07 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt @@ -2,23 +2,67 @@ package com.bintianqi.owndroid.dpm import android.annotation.SuppressLint import android.app.KeyguardManager -import android.app.admin.DevicePolicyManager -import android.app.admin.DevicePolicyManager.* -import android.content.ComponentName +import android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_IRIS +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL +import android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH +import android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW +import android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM +import android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED +import android.app.admin.DevicePolicyManager.RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT +import android.app.admin.DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY import android.content.Context import android.content.Intent import android.os.Build.VERSION import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.* +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.runtime.* +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalContext @@ -26,6 +70,7 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity import androidx.navigation.NavHostController @@ -34,8 +79,13 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.Receiver -import com.bintianqi.owndroid.ui.* +import com.bintianqi.owndroid.toggle +import com.bintianqi.owndroid.ui.Animations +import com.bintianqi.owndroid.ui.CheckBoxItem +import com.bintianqi.owndroid.ui.Information +import com.bintianqi.owndroid.ui.RadioButtonItem +import com.bintianqi.owndroid.ui.SubPageItem +import com.bintianqi.owndroid.ui.TopBar @Composable fun Password(navCtrl: NavHostController) { @@ -45,7 +95,7 @@ fun Password(navCtrl: NavHostController) { Scaffold( topBar = { TopBar(backStackEntry,navCtrl,localNavCtrl) { - if(backStackEntry?.destination?.route == "Home" && scrollState.maxValue > 80) { + if(backStackEntry?.destination?.route == "Home" && scrollState.maxValue > 100) { Text( text = stringResource(R.string.password_and_keyguard), modifier = Modifier.alpha((maxOf(scrollState.value-30,0)).toFloat()/80) @@ -79,41 +129,45 @@ fun Password(navCtrl: NavHostController) { } @Composable -private fun Home(navCtrl:NavHostController,scrollState: ScrollState) { +private fun Home(navCtrl:NavHostController, scrollState: ScrollState) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val sharedPrefs = context.getSharedPreferences("data", Context.MODE_PRIVATE) + val deviceAdmin = context.isDeviceAdmin + val deviceOwner = context.isDeviceOwner + val profileOwner = context.isProfileOwner Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) { Text( text = stringResource(R.string.password_and_keyguard), style = typography.headlineLarge, - modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp) + modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 16.dp) ) SubPageItem(R.string.password_info, "", R.drawable.info_fill0) { navCtrl.navigate("PasswordInfo") } - if(VERSION.SDK_INT >= 26 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { - SubPageItem(R.string.reset_password_token, "", R.drawable.key_vertical_fill0) { navCtrl.navigate("ResetPasswordToken") } - } - if(dpm.isAdminActive(receiver) || isDeviceOwner(dpm) || isProfileOwner(dpm)) { - SubPageItem(R.string.reset_password, "", R.drawable.lock_reset_fill0) { navCtrl.navigate("ResetPassword") } + if(sharedPrefs.getBoolean("dangerous_features", false)) { + if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) { + SubPageItem(R.string.reset_password_token, "", R.drawable.key_vertical_fill0) { navCtrl.navigate("ResetPasswordToken") } + } + if(deviceAdmin || deviceOwner || profileOwner) { + SubPageItem(R.string.reset_password, "", R.drawable.lock_reset_fill0) { navCtrl.navigate("ResetPassword") } + } } - if(VERSION.SDK_INT >= 31 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) { SubPageItem(R.string.required_password_complexity, "", R.drawable.password_fill0) { navCtrl.navigate("RequirePasswordComplexity") } } - if(dpm.isAdminActive(receiver)) { + if(deviceAdmin) { SubPageItem(R.string.disable_keyguard_features, "", R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("DisableKeyguardFeatures") } } - if(isDeviceOwner(dpm)) { + if(deviceOwner) { SubPageItem(R.string.max_time_to_lock, "", R.drawable.schedule_fill0) { navCtrl.navigate("MaxTimeToLock") } SubPageItem(R.string.pwd_expiration_timeout, "", R.drawable.lock_clock_fill0) { navCtrl.navigate("PasswordTimeout") } SubPageItem(R.string.max_pwd_fail, "", R.drawable.no_encryption_fill0) { navCtrl.navigate("MaxPasswordFail") } } - if(dpm.isAdminActive(receiver)){ + if(deviceAdmin){ SubPageItem(R.string.pwd_history, "", R.drawable.history_fill0) { navCtrl.navigate("PasswordHistoryLength") } } - if(VERSION.SDK_INT >= 26 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) { SubPageItem(R.string.required_strong_auth_timeout, "", R.drawable.fingerprint_off_fill0) { navCtrl.navigate("RequiredStrongAuthTimeout") } } - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { SubPageItem(R.string.required_password_quality, "", R.drawable.password_fill0) { navCtrl.navigate("RequirePasswordQuality") } } Spacer(Modifier.padding(vertical = 30.dp)) @@ -123,8 +177,10 @@ private fun Home(navCtrl:NavHostController,scrollState: ScrollState) { @Composable private fun PasswordInfo() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() + val deviceOwner = context.isDeviceOwner + val profileOwner = context.isProfileOwner Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.password_info), style = typography.headlineLarge) @@ -139,14 +195,14 @@ private fun PasswordInfo() { val pwdComplex = passwordComplexity[dpm.passwordComplexity] Text(text = stringResource(R.string.current_password_complexity_is, pwdComplex?:stringResource(R.string.unknown))) } - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { Text(stringResource(R.string.is_password_sufficient, dpm.isActivePasswordSufficient)) } - if(dpm.isAdminActive(receiver)) { + if(context.isDeviceAdmin) { val pwdFailedAttempts = dpm.currentFailedPasswordAttempts Text(text = stringResource(R.string.password_failed_attempts_is, pwdFailedAttempts)) } - if(VERSION.SDK_INT >= 28 && isProfileOwner(dpm) && dpm.isManagedProfile(receiver)) { + if(VERSION.SDK_INT >= 28 && profileOwner && dpm.isManagedProfile(receiver)) { val unifiedPwd = dpm.isUsingUnifiedPassword(receiver) Text(stringResource(R.string.is_using_unified_password, unifiedPwd)) } @@ -157,25 +213,27 @@ private fun PasswordInfo() { @Composable private fun ResetPasswordToken() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) - val tokenByteArray by remember { mutableStateOf(byteArrayOf(1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0)) } + val dpm = context.getDPM() + val receiver = context.getReceiver() + var token by remember { mutableStateOf("") } + val tokenByteArray = token.toByteArray() + val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.reset_password_token), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { - Toast.makeText( - context, - if(dpm.clearResetPasswordToken(receiver)) R.string.success else R.string.failed, - Toast.LENGTH_SHORT - ).show() + OutlinedTextField( + value = token, onValueChange = { token = it }, + label = { Text(stringResource(R.string.token)) }, + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + supportingText = { + AnimatedVisibility(tokenByteArray.size < 32) { + Text(stringResource(R.string.token_must_longer_than_32_byte)) + } }, modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.clear)) - } + ) Button( onClick = { try { @@ -188,20 +246,38 @@ private fun ResetPasswordToken() { Toast.makeText(context, R.string.security_exception, Toast.LENGTH_SHORT).show() } }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), + enabled = tokenByteArray.size >= 32 ) { Text(stringResource(R.string.set)) } - Button( - onClick = { - if(!dpm.isResetPasswordTokenActive(receiver)) { - try { activateToken(context) } - catch(e:NullPointerException) { Toast.makeText(context, R.string.please_set_a_token, Toast.LENGTH_SHORT).show() } - } else { Toast.makeText(context, R.string.token_already_activated, Toast.LENGTH_SHORT).show() } - }, + Row( + horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() ) { - Text(stringResource(R.string.activate)) + Button( + onClick = { + if(!dpm.isResetPasswordTokenActive(receiver)) { + try { activateToken(context) } + catch(e:NullPointerException) { Toast.makeText(context, R.string.please_set_a_token, Toast.LENGTH_SHORT).show() } + } else { Toast.makeText(context, R.string.token_already_activated, Toast.LENGTH_SHORT).show() } + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.activate)) + } + Button( + onClick = { + Toast.makeText( + context, + if(dpm.clearResetPasswordToken(receiver)) R.string.success else R.string.failed, + Toast.LENGTH_SHORT + ).show() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.clear)) + } } Spacer(Modifier.padding(vertical = 5.dp)) Information{ Text(stringResource(R.string.activate_token_not_required_when_no_password)) } @@ -211,99 +287,138 @@ private fun ResetPasswordToken() { @Composable private fun ResetPassword() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current - var newPwd by remember { mutableStateOf("") } - val tokenByteArray by remember { mutableStateOf(byteArrayOf(1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0)) } - var confirmed by remember { mutableStateOf(false) } - var resetPwdFlag by remember { mutableIntStateOf(0) } + var password by remember { mutableStateOf("") } + var useToken by remember { mutableStateOf(false) } + var token by remember { mutableStateOf("") } + val tokenByteArray = token.toByteArray() + val flags = remember { mutableStateListOf() } + var confirmDialog by remember { mutableStateOf(false) } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.reset_password),style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) + if(VERSION.SDK_INT >= 26) { + OutlinedTextField( + value = token, onValueChange = { token = it }, + label = { Text(stringResource(R.string.token)) }, + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + modifier = Modifier.fillMaxWidth() + ) + } OutlinedTextField( - value = newPwd, - onValueChange = { newPwd=it }, - enabled = !confirmed, + value = password, + onValueChange = { password = it }, label = { Text(stringResource(R.string.password)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + supportingText = { Text(stringResource(R.string.reset_pwd_desc)) }, + visualTransformation = PasswordVisualTransformation(), modifier = Modifier.fillMaxWidth() ) - Spacer(Modifier.padding(vertical = 3.dp)) - Text(text = stringResource(R.string.reset_pwd_desc)) Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT >= 23) { - RadioButtonItem( - stringResource(R.string.do_not_ask_credentials_on_boot), - resetPwdFlag == RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT, - { resetPwdFlag = RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT } + CheckBoxItem( + R.string.do_not_ask_credentials_on_boot, + RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT in flags, + { flags.toggle(it, RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT) } ) } - RadioButtonItem( - stringResource(R.string.reset_password_require_entry), - resetPwdFlag == RESET_PASSWORD_REQUIRE_ENTRY, - { resetPwdFlag = RESET_PASSWORD_REQUIRE_ENTRY } + CheckBoxItem( + R.string.reset_password_require_entry, + RESET_PASSWORD_REQUIRE_ENTRY in flags, + { flags.toggle(it, RESET_PASSWORD_REQUIRE_ENTRY) } ) - RadioButtonItem(stringResource(R.string.none), resetPwdFlag == 0, { resetPwdFlag = 0 }) Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { - if(newPwd.length>=4 || newPwd.isEmpty()) { confirmed=!confirmed } - else { Toast.makeText(context, R.string.require_4_digit_password, Toast.LENGTH_SHORT).show() } - }, - modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors( - containerColor = if(confirmed) colorScheme.primary else colorScheme.error, - contentColor = if(confirmed) colorScheme.onPrimary else colorScheme.onError - ) - ) { - Text(text = stringResource(if(confirmed) R.string.cancel else R.string.confirm)) - } - Spacer(Modifier.padding(vertical = 3.dp)) if(VERSION.SDK_INT >= 26) { Button( onClick = { - val resetSuccess = dpm.resetPasswordWithToken(receiver,newPwd,tokenByteArray,resetPwdFlag) - if(resetSuccess) { Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show(); newPwd=""} - else{ Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() } - confirmed=false + useToken = true + confirmDialog = true + focusMgr.clearFocus() }, colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - enabled = confirmed && (isDeviceOwner(dpm) || isProfileOwner(dpm)), + enabled = tokenByteArray.size >=32 && password.length !in 1..3 && (context.isDeviceOwner || context.isProfileOwner), modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.reset_password_with_token)) } } - Button( - onClick = { - val resetSuccess = dpm.resetPassword(newPwd,resetPwdFlag) - if(resetSuccess) { Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show(); newPwd="" } - else{ Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() } - confirmed=false - }, - enabled = confirmed, - colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.reset_password_deprecated)) + if(VERSION.SDK_INT <= 30) { + Button( + onClick = { + useToken = false + confirmDialog = true + focusMgr.clearFocus() + }, + colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), + enabled = password.length !in 1..3, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.reset_password)) + } } Spacer(Modifier.padding(vertical = 30.dp)) } + if(confirmDialog) { + var confirmPassword by remember { mutableStateOf("") } + AlertDialog( + onDismissRequest = { confirmDialog = false }, + title = { Text(stringResource(R.string.reset_password)) }, + text = { + val dialogFocusMgr = LocalFocusManager.current + OutlinedTextField( + value = confirmPassword, + onValueChange = { confirmPassword = it }, + label = { Text(stringResource(R.string.confirm_password)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { dialogFocusMgr.clearFocus() }), + visualTransformation = PasswordVisualTransformation(), + modifier = Modifier.fillMaxWidth() + ) + }, + confirmButton = { + TextButton( + onClick = { + var resetFlag = 0 + flags.forEach { resetFlag += it } + val success = if(VERSION.SDK_INT >= 26 && useToken) { + dpm.resetPasswordWithToken(receiver, password, tokenByteArray, resetFlag) + } else { + dpm.resetPassword(password, resetFlag) + } + Toast.makeText(context, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show() + password = "" + confirmDialog = false + }, + colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error), + enabled = confirmPassword == password + ) { + Text(stringResource(R.string.confirm)) + } + }, + dismissButton = { + TextButton(onClick = { confirmDialog = false }) { + Text(stringResource(R.string.cancel)) + } + } + ) + } } @SuppressLint("NewApi") @Composable private fun PasswordComplexity() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() val passwordComplexity = mapOf( - PASSWORD_COMPLEXITY_NONE to stringResource(R.string.password_complexity_none), - PASSWORD_COMPLEXITY_LOW to stringResource(R.string.password_complexity_low), - PASSWORD_COMPLEXITY_MEDIUM to stringResource(R.string.password_complexity_medium), - PASSWORD_COMPLEXITY_HIGH to stringResource(R.string.password_complexity_high) + PASSWORD_COMPLEXITY_NONE to R.string.password_complexity_none, + PASSWORD_COMPLEXITY_LOW to R.string.password_complexity_low, + PASSWORD_COMPLEXITY_MEDIUM to R.string.password_complexity_medium, + PASSWORD_COMPLEXITY_HIGH to R.string.password_complexity_high ).toList() var selectedItem by remember { mutableIntStateOf(passwordComplexity[0].first) } LaunchedEffect(Unit) { selectedItem = dpm.requiredPasswordComplexity } @@ -311,28 +426,14 @@ private fun PasswordComplexity() { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.required_password_complexity), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) - RadioButtonItem( - passwordComplexity[0].second, - selectedItem == passwordComplexity[0].first, - { selectedItem = passwordComplexity[0].first } - ) - RadioButtonItem( - passwordComplexity[1].second, - selectedItem == passwordComplexity[1].first, - { selectedItem = passwordComplexity[1].first } - ) - RadioButtonItem( - passwordComplexity[2].second, - selectedItem == passwordComplexity[2].first, - { selectedItem = passwordComplexity[2].first } - ) - RadioButtonItem( - passwordComplexity[3].second, - selectedItem == passwordComplexity[3].first, - { selectedItem = passwordComplexity[3].first } - ) + for(index in 0..3) { + RadioButtonItem( + passwordComplexity[index].second, + selectedItem == passwordComplexity[index].first, + { selectedItem = passwordComplexity[index].first } + ) + } Spacer(Modifier.padding(vertical = 5.dp)) - Button( onClick = { dpm.requiredPasswordComplexity = selectedItem @@ -356,8 +457,8 @@ private fun PasswordComplexity() { @Composable private fun ScreenTimeout() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var inputContent by remember { mutableStateOf("") } LaunchedEffect(Unit) { inputContent = dpm.getMaximumTimeToLock(receiver).toString() } @@ -390,8 +491,8 @@ private fun ScreenTimeout() { @Composable private fun RequiredStrongAuthTimeout() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var input by remember { mutableStateOf("") } LaunchedEffect(Unit) { input = dpm.getRequiredStrongAuthTimeout(receiver).toString() } @@ -424,8 +525,8 @@ private fun RequiredStrongAuthTimeout() { @Composable private fun MaxFailedPasswordForWipe() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var inputContent by remember { mutableStateOf("") } LaunchedEffect(Unit) { inputContent = dpm.getMaximumFailedPasswordsForWipe(receiver).toString() } @@ -457,8 +558,8 @@ private fun MaxFailedPasswordForWipe() { @Composable private fun PasswordExpiration() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var inputContent by remember { mutableStateOf("") } LaunchedEffect(Unit) { inputContent = dpm.getPasswordExpirationTimeout(receiver).toString() } @@ -488,8 +589,8 @@ private fun PasswordExpiration() { @Composable private fun PasswordHistoryLength() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var inputContent by remember { mutableStateOf("") } LaunchedEffect(Unit) { inputContent = dpm.getPasswordHistoryLength(receiver).toString() } @@ -521,8 +622,8 @@ private fun PasswordHistoryLength() { @Composable private fun DisableKeyguardFeatures() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() var state by remember { mutableIntStateOf(-1) } var shortcuts by remember { mutableStateOf(false) } var biometrics by remember { mutableStateOf(false) } @@ -564,24 +665,24 @@ private fun DisableKeyguardFeatures() { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.disable_keyguard_features), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) - RadioButtonItem(stringResource(R.string.enable_all), state == 0, { state = 0 }) - RadioButtonItem(stringResource(R.string.disable_all), state == 1, { state = 1 }) - RadioButtonItem(stringResource(R.string.custom), state == 2 , { state = 2 }) + RadioButtonItem(R.string.enable_all, state == 0, { state = 0 }) + RadioButtonItem(R.string.disable_all, state == 1, { state = 1 }) + RadioButtonItem(R.string.custom, state == 2 , { state = 2 }) AnimatedVisibility(state==2) { Column { - CheckBoxItem(stringResource(R.string.disable_keyguard_features_widgets), widgets, { widgets = it }) - CheckBoxItem(stringResource(R.string.disable_keyguard_features_camera), camera, { camera = it }) - CheckBoxItem(stringResource(R.string.disable_keyguard_features_notification), notification, { notification = it }) - CheckBoxItem(stringResource(R.string.disable_keyguard_features_unredacted_notification), unredacted, { unredacted = it }) - CheckBoxItem(stringResource(R.string.disable_keyguard_features_trust_agents), agents, { agents = it }) - CheckBoxItem(stringResource(R.string.disable_keyguard_features_fingerprint), fingerprint, { fingerprint = it }) - if(VERSION.SDK_INT >= 24) { CheckBoxItem(stringResource(R.string.disable_keyguard_features_remote_input), remote , { remote = it }) } + CheckBoxItem(R.string.disable_keyguard_features_widgets, widgets, { widgets = it }) + CheckBoxItem(R.string.disable_keyguard_features_camera, camera, { camera = it }) + CheckBoxItem(R.string.disable_keyguard_features_notification, notification, { notification = it }) + CheckBoxItem(R.string.disable_keyguard_features_unredacted_notification, unredacted, { unredacted = it }) + CheckBoxItem(R.string.disable_keyguard_features_trust_agents, agents, { agents = it }) + CheckBoxItem(R.string.disable_keyguard_features_fingerprint, fingerprint, { fingerprint = it }) + if(VERSION.SDK_INT >= 24) { CheckBoxItem(R.string.disable_keyguard_features_remote_input, remote , { remote = it }) } if(VERSION.SDK_INT >= 28) { - CheckBoxItem(stringResource(R.string.disable_keyguard_features_face), face, { face = it }) - CheckBoxItem(stringResource(R.string.disable_keyguard_features_iris), iris, { iris = it }) - CheckBoxItem(stringResource(R.string.disable_keyguard_features_biometrics), biometrics, { biometrics = it }) + CheckBoxItem(R.string.disable_keyguard_features_face, face, { face = it }) + CheckBoxItem(R.string.disable_keyguard_features_iris, iris, { iris = it }) + CheckBoxItem(R.string.disable_keyguard_features_biometrics, biometrics, { biometrics = it }) } - if(VERSION.SDK_INT >= 34) { CheckBoxItem(stringResource(R.string.disable_keyguard_features_shortcuts), shortcuts, { shortcuts = it }) } + if(VERSION.SDK_INT >= 34) { CheckBoxItem(R.string.disable_keyguard_features_shortcuts, shortcuts, { shortcuts = it }) } } } Spacer(Modifier.padding(vertical = 5.dp)) @@ -618,17 +719,16 @@ private fun DisableKeyguardFeatures() { @Composable private fun PasswordQuality() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val passwordQuality = mapOf( - PASSWORD_QUALITY_UNSPECIFIED to stringResource(R.string.password_quality_unspecified), - PASSWORD_QUALITY_SOMETHING to stringResource(R.string.password_quality_something), - PASSWORD_QUALITY_ALPHABETIC to stringResource(R.string.password_quality_alphabetic), - PASSWORD_QUALITY_NUMERIC to stringResource(R.string.password_quality_numeric), - PASSWORD_QUALITY_ALPHANUMERIC to stringResource(R.string.password_quality_alphanumeric), - PASSWORD_QUALITY_BIOMETRIC_WEAK to stringResource(R.string.password_quality_biometrics_weak), - PASSWORD_QUALITY_NUMERIC_COMPLEX to stringResource(R.string.password_quality_numeric_complex), - PASSWORD_QUALITY_COMPLEX to stringResource(R.string.custom) + "(${stringResource(R.string.unsupported) })", + PASSWORD_QUALITY_UNSPECIFIED to R.string.password_quality_unspecified, + PASSWORD_QUALITY_SOMETHING to R.string.password_quality_something, + PASSWORD_QUALITY_ALPHABETIC to R.string.password_quality_alphabetic, + PASSWORD_QUALITY_NUMERIC to R.string.password_quality_numeric, + PASSWORD_QUALITY_ALPHANUMERIC to R.string.password_quality_alphanumeric, + PASSWORD_QUALITY_BIOMETRIC_WEAK to R.string.password_quality_biometrics_weak, + PASSWORD_QUALITY_NUMERIC_COMPLEX to R.string.password_quality_numeric_complex ).toList() var selectedItem by remember { mutableIntStateOf(passwordQuality[0].first) } LaunchedEffect(Unit) { selectedItem=dpm.getPasswordQuality(receiver) } @@ -639,14 +739,9 @@ private fun PasswordQuality() { Text(text = stringResource(R.string.password_complexity_instead_password_quality)) if(VERSION.SDK_INT >= 31) { Text(text = stringResource(R.string.password_quality_deprecated_desc), color = colorScheme.error) } Spacer(Modifier.padding(vertical = 5.dp)) - RadioButtonItem(passwordQuality[0].second, selectedItem == passwordQuality[0].first, { selectedItem = passwordQuality[0].first }) - RadioButtonItem(passwordQuality[1].second, selectedItem == passwordQuality[1].first, { selectedItem = passwordQuality[1].first }) - RadioButtonItem(passwordQuality[2].second, selectedItem == passwordQuality[2].first, { selectedItem = passwordQuality[2].first }) - RadioButtonItem(passwordQuality[3].second, selectedItem == passwordQuality[3].first, { selectedItem = passwordQuality[3].first }) - RadioButtonItem(passwordQuality[4].second, selectedItem == passwordQuality[4].first, { selectedItem = passwordQuality[4].first }) - RadioButtonItem(passwordQuality[5].second, selectedItem == passwordQuality[5].first, { selectedItem = passwordQuality[5].first }) - RadioButtonItem(passwordQuality[6].second, selectedItem == passwordQuality[6].first, { selectedItem = passwordQuality[6].first }) - RadioButtonItem(passwordQuality[7].second, selectedItem == passwordQuality[7].first, { selectedItem = passwordQuality[7].first }) + for(index in 1..6) { + RadioButtonItem(passwordQuality[index].second, selectedItem == passwordQuality[index].first, { selectedItem = passwordQuality[index].first }) + } Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { 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 7b4c38f..4a5aa1a 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt @@ -6,9 +6,10 @@ import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.os.Build.VERSION +import android.os.RemoteException import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -35,10 +36,10 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.Receiver import com.bintianqi.owndroid.backToHomeStateFlow import com.bintianqi.owndroid.ui.* -import kotlinx.coroutines.delay +import com.rosan.dhizuku.api.Dhizuku +import com.rosan.dhizuku.api.DhizukuRequestPermissionListener import kotlinx.coroutines.launch @Composable @@ -49,7 +50,7 @@ fun DpmPermissions(navCtrl:NavHostController) { Scaffold( topBar = { TopBar(backStackEntry,navCtrl,localNavCtrl) { - if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue>80) { + if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue > 100) { Text( text = stringResource(R.string.permission), modifier = Modifier.alpha((maxOf(scrollState.value-30,0)).toFloat()/80) @@ -86,61 +87,104 @@ fun DpmPermissions(navCtrl:NavHostController) { @Composable private fun Home(localNavCtrl:NavHostController,listScrollState:ScrollState) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val deviceAdmin = context.isDeviceAdmin + val deviceOwner = context.isDeviceOwner + val profileOwner = context.isProfileOwner Column(modifier = Modifier.fillMaxSize().verticalScroll(listScrollState)) { Text( text = stringResource(R.string.permission), style = typography.headlineLarge, - modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp) + modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 16.dp) ) + if(!dpm.isDeviceOwnerApp(context.packageName)) { + SwitchItem( + R.string.dhizuku, "", null, + { sharedPref.getBoolean("dhizuku", false) }, + { + toggleDhizukuMode(it, context) + } + ) + } SubPageItem( - R.string.device_admin, stringResource(if(dpm.isAdminActive(receiver)) R.string.activated else R.string.deactivated), + R.string.device_admin, stringResource(if(deviceAdmin) R.string.activated else R.string.deactivated), operation = { localNavCtrl.navigate("DeviceAdmin") } ) - if(!isDeviceOwner(dpm)) { + if(!deviceOwner) { SubPageItem( - R.string.profile_owner, stringResource(if(isProfileOwner(dpm)) R.string.activated else R.string.deactivated), + R.string.profile_owner, stringResource(if(profileOwner) R.string.activated else R.string.deactivated), operation = { localNavCtrl.navigate("ProfileOwner") } ) } - if(!isProfileOwner(dpm)) { + if(!profileOwner) { SubPageItem( - R.string.device_owner, stringResource(if(isDeviceOwner(dpm)) R.string.activated else R.string.deactivated), + R.string.device_owner, stringResource(if(deviceOwner) R.string.activated else R.string.deactivated), operation = { localNavCtrl.navigate("DeviceOwner") } ) } SubPageItem(R.string.shizuku,"") { localNavCtrl.navigate("Shizuku") } SubPageItem(R.string.device_info, "", R.drawable.perm_device_information_fill0) { localNavCtrl.navigate("DeviceInfo") } - if((VERSION.SDK_INT >= 26 && isDeviceOwner(dpm)) || (VERSION.SDK_INT>=24 && isProfileOwner(dpm))) { + if((VERSION.SDK_INT >= 26 && deviceOwner) || (VERSION.SDK_INT>=24 && profileOwner)) { SubPageItem(R.string.org_name, "", R.drawable.corporate_fare_fill0) { localNavCtrl.navigate("OrgName") } } - if(VERSION.SDK_INT >= 31 && (isProfileOwner(dpm) || isDeviceOwner(dpm))) { + if(VERSION.SDK_INT >= 31 && (profileOwner || deviceOwner)) { SubPageItem(R.string.org_id, "", R.drawable.corporate_fare_fill0) { localNavCtrl.navigate("OrgID") } SubPageItem(R.string.enrollment_specific_id, "", R.drawable.id_card_fill0) { localNavCtrl.navigate("SpecificID") } } - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { SubPageItem(R.string.disable_account_management, "", R.drawable.account_circle_fill0) { localNavCtrl.navigate("DisableAccountManagement") } } - if(VERSION.SDK_INT >= 24 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 24 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.device_owner_lock_screen_info, "", R.drawable.screen_lock_portrait_fill0) { localNavCtrl.navigate("LockScreenInfo") } } - if(VERSION.SDK_INT >= 24 && dpm.isAdminActive(receiver)) { + if(VERSION.SDK_INT >= 24 && deviceAdmin) { SubPageItem(R.string.support_msg, "", R.drawable.chat_fill0) { localNavCtrl.navigate("SupportMsg") } } - if(VERSION.SDK_INT >= 28 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 28 && (deviceOwner || profileOwner)) { SubPageItem(R.string.transfer_ownership, "", R.drawable.admin_panel_settings_fill0) { localNavCtrl.navigate("TransformOwnership") } } Spacer(Modifier.padding(vertical = 30.dp)) } } +private fun toggleDhizukuMode(status: Boolean, context: Context) { + val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) + if(!status) { + sharedPref.edit().putBoolean("dhizuku", false).apply() + backToHomeStateFlow.value = true + return + } + if(!Dhizuku.init(context)) { + dhizukuErrorStatus.value = 1 + return + } + if(Dhizuku.isPermissionGranted()) { + sharedPref.edit().putBoolean("dhizuku", true).apply() + backToHomeStateFlow.value = true + } else { + Dhizuku.requestPermission(object: DhizukuRequestPermissionListener() { + @Throws(RemoteException::class) + override fun onRequestPermission(grantResult: Int) { + if(grantResult == PackageManager.PERMISSION_GRANTED) { + sharedPref.edit().putBoolean("dhizuku", true).apply() + context.toggleInstallAppActivity() + backToHomeStateFlow.value = true + } else { + dhizukuErrorStatus.value = 2 + } + } + }) + } +} + @SuppressLint("NewApi") @Composable private fun LockScreenInfo() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var infoText by remember { mutableStateOf(dpm.deviceOwnerLockScreenInfo?.toString() ?: "") } Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { @@ -177,27 +221,27 @@ private fun LockScreenInfo() { @Composable private fun DeviceAdmin() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) - var showDeactivateButton by remember { mutableStateOf(dpm.isAdminActive(receiver)) } + val dpm = context.getDPM() + val receiver = context.getReceiver() var deactivateDialog by remember { mutableStateOf(false) } + val deviceAdmin = context.isDeviceAdmin Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.device_admin), style = typography.headlineLarge) - Text(text = stringResource(if(dpm.isAdminActive(receiver)) R.string.activated else R.string.deactivated), style = typography.titleLarge) + Text(text = stringResource(if(context.isDeviceAdmin) R.string.activated else R.string.deactivated), style = typography.titleLarge) Spacer(Modifier.padding(vertical = 5.dp)) - AnimatedVisibility(showDeactivateButton) { + AnimatedVisibility(deviceAdmin) { Button( onClick = { deactivateDialog = true }, - enabled = !isProfileOwner(dpm) && !isDeviceOwner(dpm), + enabled = !context.isProfileOwner && !context.isDeviceOwner, colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError) ) { Text(stringResource(R.string.deactivate)) } } - AnimatedVisibility(!showDeactivateButton) { + AnimatedVisibility(!deviceAdmin) { Column { - Button(onClick = {activateDeviceAdmin(context, receiver) }, modifier = Modifier.fillMaxWidth()) { + Button(onClick = { activateDeviceAdmin(context, receiver) }, modifier = Modifier.fillMaxWidth()) { Text(stringResource(R.string.activate_jump)) } Spacer(Modifier.padding(vertical = 5.dp)) @@ -209,7 +253,6 @@ private fun DeviceAdmin() { } } if(deactivateDialog) { - val co = rememberCoroutineScope() AlertDialog( title = { Text(stringResource(R.string.deactivate)) }, onDismissRequest = { deactivateDialog = false }, @@ -224,12 +267,7 @@ private fun DeviceAdmin() { TextButton( onClick = { dpm.removeActiveAdmin(receiver) - co.launch{ - delay(300) - deactivateDialog = false - showDeactivateButton = dpm.isAdminActive(receiver) - backToHomeStateFlow.value = !dpm.isAdminActive(receiver) - } + deactivateDialog = false } ) { Text(stringResource(R.string.confirm)) @@ -242,37 +280,26 @@ private fun DeviceAdmin() { @Composable private fun ProfileOwner() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) - var showDeactivateButton by remember { mutableStateOf(isProfileOwner(dpm)) } + val dpm = context.getDPM() + val receiver = context.getReceiver() var deactivateDialog by remember { mutableStateOf(false) } + val profileOwner = context.isProfileOwner Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.profile_owner), style = typography.headlineLarge) - Text(stringResource(if(isProfileOwner(dpm)) R.string.activated else R.string.deactivated), style = typography.titleLarge) + Text(stringResource(if(profileOwner) R.string.activated else R.string.deactivated), style = typography.titleLarge) Spacer(Modifier.padding(vertical = 5.dp)) - if(VERSION.SDK_INT >= 24) { - AnimatedVisibility(showDeactivateButton) { - Button( - onClick = { deactivateDialog = true }, - enabled = !dpm.isManagedProfile(receiver), - colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError) - ) { - Text(stringResource(R.string.deactivate)) - } - } - } - AnimatedVisibility(!showDeactivateButton) { - Column { - SelectionContainer{ - Text(text = stringResource(R.string.activate_profile_owner_command)) - } - CopyTextButton(R.string.copy_command, stringResource(R.string.activate_profile_owner_command)) + if(VERSION.SDK_INT >= 24 && profileOwner) { + Button( + onClick = { deactivateDialog = true }, + enabled = !dpm.isManagedProfile(receiver), + colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError) + ) { + Text(stringResource(R.string.deactivate)) } } } if(deactivateDialog && VERSION.SDK_INT >= 24) { - val co = rememberCoroutineScope() AlertDialog( title = { Text(stringResource(R.string.deactivate)) }, onDismissRequest = { deactivateDialog = false }, @@ -287,12 +314,7 @@ private fun ProfileOwner() { TextButton( onClick = { dpm.clearProfileOwner(receiver) - co.launch{ - delay(300) - deactivateDialog = false - showDeactivateButton = isProfileOwner(dpm) - backToHomeStateFlow.value = !isProfileOwner(dpm) - } + deactivateDialog = false } ) { Text(stringResource(R.string.confirm)) @@ -305,15 +327,16 @@ private fun ProfileOwner() { @Composable private fun DeviceOwner() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - var showDeactivateButton by remember { mutableStateOf(isDeviceOwner(dpm)) } + val dpm = context.getDPM() var deactivateDialog by remember { mutableStateOf(false) } + var resetPolicy by remember { mutableStateOf(true) } + val deviceOwner = context.isDeviceOwner Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.device_owner), style = typography.headlineLarge) - Text(text = stringResource(if(isDeviceOwner(dpm)) R.string.activated else R.string.deactivated), style = typography.titleLarge) + Text(text = stringResource(if(deviceOwner) R.string.activated else R.string.deactivated), style = typography.titleLarge) Spacer(Modifier.padding(vertical = 5.dp)) - AnimatedVisibility(showDeactivateButton) { + AnimatedVisibility(deviceOwner) { Button( onClick = { deactivateDialog = true }, colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError) @@ -321,7 +344,7 @@ private fun DeviceOwner() { Text(text = stringResource(R.string.deactivate)) } } - AnimatedVisibility(!showDeactivateButton) { + AnimatedVisibility(!deviceOwner) { Column { SelectionContainer{ Text(text = stringResource(R.string.activate_device_owner_command)) @@ -331,9 +354,17 @@ private fun DeviceOwner() { } } if(deactivateDialog) { - val co = rememberCoroutineScope() + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + val coroutine = rememberCoroutineScope() AlertDialog( title = { Text(stringResource(R.string.deactivate)) }, + text = { + Column { + if(sharedPref.getBoolean("dhizuku", false)) Text(stringResource(R.string.dhizuku_will_be_deactivated)) + Spacer(Modifier.padding(vertical = 4.dp)) + CheckBoxItem(text = R.string.reset_device_policy, checked = resetPolicy, operation = { resetPolicy = it }) + } + }, onDismissRequest = { deactivateDialog = false }, dismissButton = { TextButton( @@ -345,12 +376,16 @@ private fun DeviceOwner() { confirmButton = { TextButton( onClick = { - dpm.clearDeviceOwnerApp(context.packageName) - co.launch{ - delay(300) + coroutine.launch { + if(resetPolicy) context.resetDevicePolicy() + dpm.clearDeviceOwnerApp(context.dpcPackageName) + if(sharedPref.getBoolean("dhizuku", false)) { + if (!Dhizuku.init(context)) { + sharedPref.edit().putBoolean("dhizuku", false).apply() + backToHomeStateFlow.value = true + } + } deactivateDialog = false - showDeactivateButton = isDeviceOwner(dpm) - backToHomeStateFlow.value = !isDeviceOwner(dpm) } } ) { @@ -364,13 +399,13 @@ private fun DeviceOwner() { @Composable fun DeviceInfo() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.device_info), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) - if(VERSION.SDK_INT>=34 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT>=34 && (context.isDeviceOwner || dpm.isOrgProfile(receiver))) { val financed = dpm.isDeviceFinanced Text(stringResource(R.string.is_device_financed, financed)) } @@ -423,7 +458,7 @@ fun DeviceInfo() { @Composable private fun OrgID() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { var orgId by remember { mutableStateOf("") } @@ -461,7 +496,7 @@ private fun OrgID() { @Composable private fun SpecificID() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { val specificId = dpm.enrollmentSpecificId Spacer(Modifier.padding(vertical = 10.dp)) @@ -480,8 +515,8 @@ private fun SpecificID() { @Composable private fun OrgName() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { var orgName by remember { mutableStateOf(try{dpm.getOrganizationName(receiver).toString() }catch(e:SecurityException) {""}) } @@ -512,8 +547,8 @@ private fun OrgName() { @Composable private fun SupportMsg() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var shortMsg by remember { mutableStateOf(dpm.getShortSupportMessage(receiver)?.toString() ?: "") } var longMsg by remember { mutableStateOf(dpm.getLongSupportMessage(receiver)?.toString() ?: "") } @@ -571,8 +606,8 @@ private fun SupportMsg() { @Composable private fun DisableAccountManagement() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { Spacer(Modifier.padding(vertical = 10.dp)) @@ -625,8 +660,8 @@ private fun DisableAccountManagement() { @Composable private fun TransformOwnership() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current val focusRequester = FocusRequester() Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt index 6c187e5..f07adaa 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt @@ -1,6 +1,5 @@ package com.bintianqi.owndroid.dpm -import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Context import android.content.ServiceConnection @@ -9,7 +8,6 @@ import android.os.Binder import android.os.Build.VERSION import android.os.IBinder import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Column @@ -36,7 +34,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.bintianqi.owndroid.IUserService import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.Receiver import kotlinx.coroutines.delay import kotlinx.coroutines.launch import rikka.shizuku.Shizuku @@ -46,16 +43,15 @@ private var waitGrantPermission = false @Composable fun ShizukuActivate() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val coScope = rememberCoroutineScope() val outputTextScrollState = rememberScrollState() var enabled by remember { mutableStateOf(false) } var bindShizuku by remember { mutableStateOf(false) } var outputText by remember { mutableStateOf("") } - var showDeviceAdminButton by remember { mutableStateOf(!dpm.isAdminActive(receiver)) } - var showProfileOwnerButton by remember { mutableStateOf(!isProfileOwner(dpm)) } - var showDeviceOwnerButton by remember { mutableStateOf(!isDeviceOwner(dpm)) } + var showDeviceAdminButton by remember { mutableStateOf(!context.isDeviceAdmin) } + var showDeviceOwnerButton by remember { mutableStateOf(!context.isDeviceOwner) } var showOrgProfileOwnerButton by remember { mutableStateOf(true) } val service by shizukuService.collectAsState() LaunchedEffect(service) { @@ -118,16 +114,38 @@ fun ShizukuActivate() { ) { Text(text = stringResource(R.string.list_owners)) } + Button( + onClick = { + coScope.launch{ + outputText = service!!.execute("pm list users") + outputTextScrollState.animateScrollTo(0) + } + }, + enabled = enabled + ) { + Text(text = stringResource(R.string.list_users)) + } + Button( + onClick = { + coScope.launch{ + outputText = service!!.execute("dumpsys account") + outputTextScrollState.animateScrollTo(0) + } + }, + enabled = enabled + ) { + Text(text = stringResource(R.string.list_accounts)) + } Spacer(Modifier.padding(vertical = 5.dp)) - AnimatedVisibility(showDeviceAdminButton && showProfileOwnerButton && showDeviceOwnerButton) { + AnimatedVisibility(showDeviceAdminButton && showDeviceOwnerButton) { Button( onClick = { coScope.launch{ outputText = service!!.execute(context.getString(R.string.dpm_activate_da_command)) outputTextScrollState.animateScrollTo(0) delay(500) - showDeviceAdminButton = !dpm.isAdminActive(receiver) + showDeviceAdminButton = !context.isDeviceAdmin } }, enabled = enabled @@ -136,30 +154,14 @@ fun ShizukuActivate() { } } - AnimatedVisibility(showProfileOwnerButton&&showDeviceOwnerButton) { - Button( - onClick = { - coScope.launch{ - outputText = service!!.execute(context.getString(R.string.dpm_activate_po_command)) - outputTextScrollState.animateScrollTo(0) - delay(500) - showProfileOwnerButton = !isProfileOwner(dpm) - } - }, - enabled = enabled - ) { - Text(text = stringResource(R.string.activate_profile_owner)) - } - } - - AnimatedVisibility(showDeviceOwnerButton && showProfileOwnerButton) { + AnimatedVisibility(showDeviceOwnerButton) { Button( onClick = { coScope.launch{ outputText = service!!.execute(context.getString(R.string.dpm_activate_do_command)) outputTextScrollState.animateScrollTo(0) delay(500) - showDeviceOwnerButton = !isDeviceOwner(dpm) + showDeviceOwnerButton = !context.isDeviceOwner } }, enabled = enabled @@ -168,7 +170,7 @@ fun ShizukuActivate() { } } - if(VERSION.SDK_INT >= 30 && isProfileOwner(dpm) && dpm.isManagedProfile(receiver) && !dpm.isOrganizationOwnedDeviceWithManagedProfile) { + if(VERSION.SDK_INT >= 30 && context.isProfileOwner && dpm.isManagedProfile(receiver) && !dpm.isOrganizationOwnedDeviceWithManagedProfile) { AnimatedVisibility(showOrgProfileOwnerButton) { Button( onClick = { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt index 0045898..66fe68d 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt @@ -6,7 +6,6 @@ import android.app.AlertDialog import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent -import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback import android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK @@ -44,10 +43,10 @@ import android.os.Build.VERSION import android.os.UserManager import android.util.Log import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -57,6 +56,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.SelectionContainer @@ -64,6 +64,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.OutlinedTextField @@ -81,12 +82,15 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType @@ -98,11 +102,11 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.Receiver import com.bintianqi.owndroid.StopLockTaskModeReceiver import com.bintianqi.owndroid.fileUriFlow import com.bintianqi.owndroid.getFile import com.bintianqi.owndroid.prepareForNotification +import com.bintianqi.owndroid.selectedPackage import com.bintianqi.owndroid.toText import com.bintianqi.owndroid.toggle import com.bintianqi.owndroid.ui.Animations @@ -120,7 +124,7 @@ import java.util.concurrent.Executors import kotlin.math.pow @Composable -fun SystemManage(navCtrl:NavHostController) { +fun SystemManage(navCtrl: NavHostController) { val localNavCtrl = rememberNavController() val backStackEntry by localNavCtrl.currentBackStackEntryAsState() val scrollState = rememberScrollState() @@ -129,7 +133,7 @@ fun SystemManage(navCtrl:NavHostController) { Scaffold( topBar = { TopBar(backStackEntry,navCtrl,localNavCtrl) { - if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue>80) { + if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue > 100) { Text( text = stringResource(R.string.system_manage), modifier = Modifier.alpha((maxOf(scrollState.value-30,0)).toFloat()/80) @@ -154,7 +158,7 @@ fun SystemManage(navCtrl:NavHostController) { composable(route = "PermissionPolicy") { PermissionPolicy() } composable(route = "MTEPolicy") { MTEPolicy() } composable(route = "NearbyStreamingPolicy") { NearbyStreamingPolicy() } - composable(route = "LockTaskMode") { LockTaskMode() } + composable(route = "LockTaskMode") { LockTaskMode(navCtrl) } composable(route = "CaCert") { CaCert() } composable(route = "SecurityLogs") { SecurityLogs() } composable(route = "SystemUpdatePolicy") { SysUpdatePolicy() } @@ -174,58 +178,60 @@ fun SystemManage(navCtrl:NavHostController) { @Composable private fun Home(navCtrl: NavHostController, scrollState: ScrollState, rebootDialog: MutableState, bugReportDialog: MutableState) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) val dangerousFeatures = sharedPref.getBoolean("dangerous_features", false) + val deviceOwner = context.isDeviceOwner + val profileOwner = context.isProfileOwner Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) { Text( text = stringResource(R.string.system_manage), style = typography.headlineLarge, - modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp) + modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 16.dp) ) - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { SubPageItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("Switches") } } SubPageItem(R.string.keyguard, "", R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("Keyguard") } - if(VERSION.SDK_INT >= 24 && isDeviceOwner(dpm)) { + if(VERSION.SDK_INT >= 24 && deviceOwner) { SubPageItem(R.string.reboot, "", R.drawable.restart_alt_fill0) { rebootDialog.value = true } } - if(isDeviceOwner(dpm) && ((VERSION.SDK_INT >= 28 && dpm.isAffiliatedUser) || VERSION.SDK_INT >= 24)) { + if(deviceOwner && ((VERSION.SDK_INT >= 28 && dpm.isAffiliatedUser) || VERSION.SDK_INT >= 24)) { SubPageItem(R.string.bug_report, "", R.drawable.bug_report_fill0) { bugReportDialog.value = true } } - if(VERSION.SDK_INT >= 28 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 28 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.edit_time, "", R.drawable.schedule_fill0) { navCtrl.navigate("EditTime") } SubPageItem(R.string.edit_timezone, "", R.drawable.schedule_fill0) { navCtrl.navigate("EditTimeZone") } } - if(VERSION.SDK_INT >= 23 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) { SubPageItem(R.string.permission_policy, "", R.drawable.key_fill0) { navCtrl.navigate("PermissionPolicy") } } - if(VERSION.SDK_INT >= 34 && isDeviceOwner(dpm)) { + if(VERSION.SDK_INT >= 34 && deviceOwner) { SubPageItem(R.string.mte_policy, "", R.drawable.memory_fill0) { navCtrl.navigate("MTEPolicy") } } - if(VERSION.SDK_INT >= 31 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) { SubPageItem(R.string.nearby_streaming_policy, "", R.drawable.share_fill0) { navCtrl.navigate("NearbyStreamingPolicy") } } - if(VERSION.SDK_INT >= 28 && isDeviceOwner(dpm)) { + if(VERSION.SDK_INT >= 28 && deviceOwner) { SubPageItem(R.string.lock_task_mode, "", R.drawable.lock_fill0) { navCtrl.navigate("LockTaskMode") } } - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { SubPageItem(R.string.ca_cert, "", R.drawable.license_fill0) { navCtrl.navigate("CaCert") } } - if(VERSION.SDK_INT >= 26 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 26 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.security_logs, "", R.drawable.description_fill0) { navCtrl.navigate("SecurityLogs") } } - if(VERSION.SDK_INT >= 23 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 23 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.system_update_policy, "", R.drawable.system_update_fill0) { navCtrl.navigate("SystemUpdatePolicy") } } - if(VERSION.SDK_INT >= 29 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 29 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.install_system_update, "", R.drawable.system_update_fill0) { navCtrl.navigate("InstallSystemUpdate") } } - if(VERSION.SDK_INT >= 30 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 30 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.frp_policy, "", R.drawable.device_reset_fill0) { navCtrl.navigate("FRP") } } - if(dangerousFeatures && dpm.isAdminActive(receiver) && !(VERSION.SDK_INT >= 24 && isProfileOwner(dpm) && dpm.isManagedProfile(receiver))) { + if(dangerousFeatures && context.isDeviceAdmin && !(VERSION.SDK_INT >= 24 && profileOwner && dpm.isManagedProfile(receiver))) { SubPageItem(R.string.wipe_data, "", R.drawable.device_reset_fill0) { navCtrl.navigate("WipeData") } } Spacer(Modifier.padding(vertical = 30.dp)) @@ -236,58 +242,61 @@ private fun Home(navCtrl: NavHostController, scrollState: ScrollState, rebootDia @Composable private fun Switches() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + 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 + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(start = 20.dp, end = 16.dp)) { Spacer(Modifier.padding(vertical = 10.dp)) - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { SwitchItem(R.string.disable_cam,"", R.drawable.photo_camera_fill0, - { dpm.getCameraDisabled(null) }, { dpm.setCameraDisabled(receiver,it) } + { dpm.getCameraDisabled(null) }, { dpm.setCameraDisabled(receiver,it) }, padding = false ) } - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { - SwitchItem(R.string.disable_screen_capture, stringResource(R.string.also_disable_aosp_screen_record), R.drawable.screenshot_fill0, - { dpm.getScreenCaptureDisabled(null) }, { dpm.setScreenCaptureDisabled(receiver,it) } + if(deviceOwner || profileOwner) { + SwitchItem(R.string.disable_screen_capture, "", R.drawable.screenshot_fill0, + { dpm.getScreenCaptureDisabled(null) }, { dpm.setScreenCaptureDisabled(receiver,it) }, padding = false ) } - if(VERSION.SDK_INT >= 34 && (isDeviceOwner(dpm) || (isProfileOwner(dpm) && dpm.isAffiliatedUser))) { + if(VERSION.SDK_INT >= 34 && (deviceOwner || (profileOwner && dpm.isAffiliatedUser))) { SwitchItem(R.string.disable_status_bar, "", R.drawable.notifications_fill0, - { dpm.isStatusBarDisabled}, { dpm.setStatusBarDisabled(receiver,it) } + { dpm.isStatusBarDisabled}, { dpm.setStatusBarDisabled(receiver,it) }, padding = false ) } - if(isDeviceOwner(dpm) || dpm.isOrgProfile(receiver)) { + if(deviceOwner || (VERSION.SDK_INT >= 23 && profileOwner && um.isSystemUser) || dpm.isOrgProfile(receiver)) { if(VERSION.SDK_INT >= 30) { SwitchItem(R.string.auto_time, "", R.drawable.schedule_fill0, - { dpm.getAutoTimeEnabled(receiver) }, { dpm.setAutoTimeEnabled(receiver,it) } + { dpm.getAutoTimeEnabled(receiver) }, { dpm.setAutoTimeEnabled(receiver,it) }, padding = false ) SwitchItem(R.string.auto_timezone, "", R.drawable.globe_fill0, - { dpm.getAutoTimeZoneEnabled(receiver) }, { dpm.setAutoTimeZoneEnabled(receiver,it) } + { dpm.getAutoTimeZoneEnabled(receiver) }, { dpm.setAutoTimeZoneEnabled(receiver,it) }, padding = false ) }else{ - SwitchItem(R.string.require_auto_time, "", R.drawable.schedule_fill0, { dpm.autoTimeRequired}, { dpm.setAutoTimeRequired(receiver,it) }) + SwitchItem(R.string.require_auto_time, "", R.drawable.schedule_fill0, { dpm.autoTimeRequired}, { dpm.setAutoTimeRequired(receiver,it) }, padding = false) } } - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { SwitchItem(R.string.master_mute, "", R.drawable.volume_up_fill0, - { dpm.isMasterVolumeMuted(receiver) }, { dpm.setMasterVolumeMuted(receiver,it) } + { dpm.isMasterVolumeMuted(receiver) }, { dpm.setMasterVolumeMuted(receiver,it) }, padding = false ) } - if(VERSION.SDK_INT >= 26 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) { SwitchItem(R.string.backup_service, "", R.drawable.backup_fill0, - { dpm.isBackupServiceEnabled(receiver) }, { dpm.setBackupServiceEnabled(receiver,it) } + { dpm.isBackupServiceEnabled(receiver) }, { dpm.setBackupServiceEnabled(receiver,it) }, padding = false ) } - if(VERSION.SDK_INT >= 23 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 24 && profileOwner && dpm.isManagedProfile(receiver)) { SwitchItem(R.string.disable_bt_contact_share, "", R.drawable.account_circle_fill0, - { dpm.getBluetoothContactSharingDisabled(receiver) }, { dpm.setBluetoothContactSharingDisabled(receiver,it) } + { dpm.getBluetoothContactSharingDisabled(receiver) }, { dpm.setBluetoothContactSharingDisabled(receiver,it) }, padding = false ) } - if(VERSION.SDK_INT >= 30 && isDeviceOwner(dpm)) { - SwitchItem(R.string.common_criteria_mode, stringResource(R.string.common_criteria_mode_desc),R.drawable.security_fill0, - { dpm.isCommonCriteriaModeEnabled(receiver) }, { dpm.setCommonCriteriaModeEnabled(receiver,it) } + if(VERSION.SDK_INT >= 30 && deviceOwner) { + SwitchItem(R.string.common_criteria_mode , "",R.drawable.security_fill0, + { dpm.isCommonCriteriaModeEnabled(receiver) }, { dpm.setCommonCriteriaModeEnabled(receiver,it) }, padding = false ) } - if(VERSION.SDK_INT >= 31 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if(VERSION.SDK_INT >= 31 && (deviceOwner || dpm.isOrgProfile(receiver))) { SwitchItem( R.string.usb_signal, "", R.drawable.usb_fill0, { dpm.isUsbDataSignalingEnabled }, { @@ -296,7 +305,7 @@ private fun Switches() { } else { Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show() } - } + }, padding = false ) } Spacer(Modifier.padding(vertical = 30.dp)) @@ -306,49 +315,59 @@ private fun Switches() { @Composable private fun Keyguard() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() + val deviceOwner = context.isDeviceOwner + val profileOwner = context.isProfileOwner Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.keyguard), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT >= 23) { - Button( - onClick = { - Toast.makeText(context, if(dpm.setKeyguardDisabled(receiver,true)) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(dpm) || (VERSION.SDK_INT >= 28 && isProfileOwner(dpm) && dpm.isAffiliatedUser), + Row( + horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() ) { - Text(stringResource(R.string.disable)) - } - Button( - onClick = { - Toast.makeText(context, if(dpm.setKeyguardDisabled(receiver,false)) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(dpm) || (VERSION.SDK_INT >= 28 && isProfileOwner(dpm) && dpm.isAffiliatedUser), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.enable)) + Button( + onClick = { + Toast.makeText(context, if(dpm.setKeyguardDisabled(receiver,true)) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show() + }, + enabled = deviceOwner || (VERSION.SDK_INT >= 28 && profileOwner && dpm.isAffiliatedUser), + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.disable)) + } + Button( + onClick = { + Toast.makeText(context, if(dpm.setKeyguardDisabled(receiver,false)) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show() + }, + enabled = deviceOwner || (VERSION.SDK_INT >= 28 && profileOwner && dpm.isAffiliatedUser), + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.enable)) + } } - Spacer(Modifier.padding(vertical = 3.dp)) - Information{ Text(text = stringResource(R.string.require_no_password_to_disable)) } - Spacer(Modifier.padding(vertical = 8.dp)) + Spacer(Modifier.padding(vertical = 15.dp)) } + Text(text = stringResource(R.string.lock_now), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 2.dp)) var flag by remember { mutableIntStateOf(0) } - Button( - onClick = { dpm.lockNow() }, - enabled = dpm.isAdminActive(receiver), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.lock_now)) - } - if(VERSION.SDK_INT >= 26) { + if(VERSION.SDK_INT >= 26 && profileOwner && dpm.isManagedProfile(receiver)) { CheckBoxItem( - stringResource(R.string.evict_credential_encryptoon_key), + R.string.evict_credential_encryptoon_key, flag == FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY, { flag = if(flag==0) {1}else{0} } ) + Spacer(Modifier.padding(vertical = 2.dp)) + } + Button( + onClick = { + if(VERSION.SDK_INT >= 26) dpm.lockNow(flag) else dpm.lockNow() + }, + enabled = context.isDeviceAdmin, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.lock_now)) } Spacer(Modifier.padding(vertical = 30.dp)) } @@ -358,8 +377,8 @@ private fun Keyguard() { @Composable private fun BugReportDialog(status: MutableState) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() AlertDialog( onDismissRequest = { status.value = false }, title = { Text(stringResource(R.string.bug_report)) }, @@ -388,8 +407,8 @@ private fun BugReportDialog(status: MutableState) { @Composable private fun RebootDialog(status: MutableState) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() AlertDialog( onDismissRequest = { status.value = false }, title = { Text(stringResource(R.string.reboot)) }, @@ -415,8 +434,8 @@ private fun RebootDialog(status: MutableState) { @Composable private fun EditTime() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) @@ -453,9 +472,9 @@ private fun EditTime() { @Composable private fun EditTimeZone() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() val focusMgr = LocalFocusManager.current - val receiver = ComponentName(context,Receiver::class.java) + val receiver = context.getReceiver() var expanded by remember { mutableStateOf(false) } var inputTimezone by remember { mutableStateOf("") } Column( @@ -506,16 +525,16 @@ private fun EditTimeZone() { @Composable private fun PermissionPolicy() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { var selectedPolicy by remember { mutableIntStateOf(dpm.getPermissionPolicy(receiver)) } Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.permission_policy), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) - RadioButtonItem(stringResource(R.string.default_stringres), selectedPolicy == PERMISSION_POLICY_PROMPT, { selectedPolicy = PERMISSION_POLICY_PROMPT }) - RadioButtonItem(stringResource(R.string.auto_grant), selectedPolicy == PERMISSION_POLICY_AUTO_GRANT, { selectedPolicy = PERMISSION_POLICY_AUTO_GRANT }) - RadioButtonItem(stringResource(R.string.auto_deny), selectedPolicy == PERMISSION_POLICY_AUTO_DENY, { selectedPolicy = PERMISSION_POLICY_AUTO_DENY }) + RadioButtonItem(R.string.default_stringres, selectedPolicy == PERMISSION_POLICY_PROMPT, { selectedPolicy = PERMISSION_POLICY_PROMPT }) + RadioButtonItem(R.string.auto_grant, selectedPolicy == PERMISSION_POLICY_AUTO_GRANT, { selectedPolicy = PERMISSION_POLICY_AUTO_GRANT }) + RadioButtonItem(R.string.auto_deny, selectedPolicy == PERMISSION_POLICY_AUTO_DENY, { selectedPolicy = PERMISSION_POLICY_AUTO_DENY }) Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { @@ -533,24 +552,24 @@ private fun PermissionPolicy() { @Composable private fun MTEPolicy() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.mte_policy), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) var selectedMtePolicy by remember { mutableIntStateOf(dpm.mtePolicy) } RadioButtonItem( - stringResource(R.string.decide_by_user), + R.string.decide_by_user, selectedMtePolicy == MTE_NOT_CONTROLLED_BY_POLICY, { selectedMtePolicy = MTE_NOT_CONTROLLED_BY_POLICY } ) RadioButtonItem( - stringResource(R.string.enabled), + R.string.enabled, selectedMtePolicy == MTE_ENABLED, { selectedMtePolicy = MTE_ENABLED } ) RadioButtonItem( - stringResource(R.string.disabled), + R.string.disabled, selectedMtePolicy == MTE_DISABLED, { selectedMtePolicy = MTE_DISABLED } ) @@ -559,7 +578,7 @@ private fun MTEPolicy() { try { dpm.mtePolicy = selectedMtePolicy Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - }catch(e:java.lang.UnsupportedOperationException) { + } catch(e:java.lang.UnsupportedOperationException) { Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show() } selectedMtePolicy = dpm.mtePolicy @@ -578,29 +597,29 @@ private fun MTEPolicy() { @Composable private fun NearbyStreamingPolicy() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { var appPolicy by remember { mutableIntStateOf(dpm.nearbyAppStreamingPolicy) } Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.nearby_app_streaming), style = typography.titleLarge) Spacer(Modifier.padding(vertical = 3.dp)) RadioButtonItem( - stringResource(R.string.decide_by_user), + R.string.decide_by_user, appPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY, { appPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY } ) RadioButtonItem( - stringResource(R.string.enabled), + R.string.enabled, appPolicy == NEARBY_STREAMING_ENABLED, { appPolicy = NEARBY_STREAMING_ENABLED } ) RadioButtonItem( - stringResource(R.string.disabled), + R.string.disabled, appPolicy == NEARBY_STREAMING_DISABLED, { appPolicy = NEARBY_STREAMING_DISABLED } ) RadioButtonItem( - stringResource(R.string.enable_if_secure_enough), + R.string.enable_if_secure_enough, appPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY, { appPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY } ) @@ -619,22 +638,22 @@ private fun NearbyStreamingPolicy() { Text(text = stringResource(R.string.nearby_notification_streaming), style = typography.titleLarge) Spacer(Modifier.padding(vertical = 3.dp)) RadioButtonItem( - stringResource(R.string.decide_by_user), + R.string.decide_by_user, notificationPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY, { notificationPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY } ) RadioButtonItem( - stringResource(R.string.enabled), + R.string.enabled, notificationPolicy == NEARBY_STREAMING_ENABLED, { notificationPolicy = NEARBY_STREAMING_ENABLED } ) RadioButtonItem( - stringResource(R.string.disabled), + R.string.disabled, notificationPolicy == NEARBY_STREAMING_DISABLED, { notificationPolicy = NEARBY_STREAMING_DISABLED } ) RadioButtonItem( - stringResource(R.string.enable_if_secure_enough), + R.string.enable_if_secure_enough, notificationPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY, { notificationPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY } ) @@ -654,15 +673,16 @@ private fun NearbyStreamingPolicy() { @SuppressLint("NewApi") @Composable -private fun LockTaskMode() { +private fun LockTaskMode(navCtrl: NavHostController) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current val coroutine = rememberCoroutineScope() + var appSelectorRequest by rememberSaveable { mutableIntStateOf(0) } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { val lockTaskFeatures = remember { mutableStateListOf() } - var custom by remember { mutableStateOf(false) } + var custom by rememberSaveable { mutableStateOf(false) } val refreshFeature = { var calculate = dpm.getLockTaskFeatures(receiver) lockTaskFeatures.clear() @@ -686,43 +706,43 @@ private fun LockTaskMode() { Text(text = stringResource(R.string.lock_task_feature), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) LaunchedEffect(Unit) { refreshFeature() } - RadioButtonItem(stringResource(R.string.disable_all), !custom, { custom = false }) - RadioButtonItem(stringResource(R.string.custom), custom, { custom = true }) + RadioButtonItem(R.string.disable_all, !custom, { custom = false }) + RadioButtonItem(R.string.custom, custom, { custom = true }) AnimatedVisibility(custom) { Column { CheckBoxItem( - stringResource(R.string.ltf_sys_info), + R.string.ltf_sys_info, LOCK_TASK_FEATURE_SYSTEM_INFO in lockTaskFeatures, { lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_SYSTEM_INFO) } ) CheckBoxItem( - stringResource(R.string.ltf_notifications), + R.string.ltf_notifications, LOCK_TASK_FEATURE_NOTIFICATIONS in lockTaskFeatures, { lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_NOTIFICATIONS) } ) CheckBoxItem( - stringResource(R.string.ltf_home), + R.string.ltf_home, LOCK_TASK_FEATURE_HOME in lockTaskFeatures, { lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_HOME) } ) CheckBoxItem( - stringResource(R.string.ltf_overview), + R.string.ltf_overview, LOCK_TASK_FEATURE_OVERVIEW in lockTaskFeatures, { lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_OVERVIEW) } ) CheckBoxItem( - stringResource(R.string.ltf_global_actions), + R.string.ltf_global_actions, LOCK_TASK_FEATURE_GLOBAL_ACTIONS in lockTaskFeatures, { lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_GLOBAL_ACTIONS) } ) CheckBoxItem( - stringResource(R.string.ltf_keyguard), + R.string.ltf_keyguard, LOCK_TASK_FEATURE_KEYGUARD in lockTaskFeatures, { lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_KEYGUARD) } ) if(VERSION.SDK_INT >= 30) { CheckBoxItem( - stringResource(R.string.ltf_block_activity_start_in_task), + R.string.ltf_block_activity_start_in_task, LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK in lockTaskFeatures, { lockTaskFeatures.toggle(it, LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK) } ) @@ -753,7 +773,7 @@ private fun LockTaskMode() { } val lockTaskPackages = remember { mutableStateListOf() } - var inputLockTaskPkg by remember { mutableStateOf("") } + var inputLockTaskPkg by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { lockTaskPackages.addAll(dpm.getLockTaskPackages(receiver)) } Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.lock_task_packages), style = typography.headlineLarge) @@ -769,6 +789,17 @@ private fun LockTaskMode() { label = { Text(stringResource(R.string.package_name)) }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + trailingIcon = { + Icon(painter = painterResource(R.drawable.checklist_fill0), contentDescription = null, + modifier = Modifier + .clip(RoundedCornerShape(50)) + .clickable(onClick = { + focusMgr.clearFocus() + appSelectorRequest = 1 + navCtrl.navigate("PackageSelector") + }) + .padding(3.dp)) + }, modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp) ) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { @@ -794,7 +825,16 @@ private fun LockTaskMode() { ) { Text(stringResource(R.string.apply)) } - var startLockTaskApp by remember { mutableStateOf("") } + var startLockTaskApp by rememberSaveable { mutableStateOf("") } + var startLockTaskActivity by rememberSaveable { mutableStateOf("") } + var specifyActivity by rememberSaveable { mutableStateOf(false) } + val updatePackage by selectedPackage.collectAsState() + LaunchedEffect(updatePackage) { + if(updatePackage != "") { + if(appSelectorRequest == 1) inputLockTaskPkg = updatePackage else startLockTaskApp = updatePackage + selectedPackage.value = "" + } + } Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.start_lock_task_mode), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) @@ -804,8 +844,30 @@ private fun LockTaskMode() { label = { Text(stringResource(R.string.package_name)) }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + trailingIcon = { + Icon(painter = painterResource(R.drawable.checklist_fill0), contentDescription = null, + modifier = Modifier + .clip(RoundedCornerShape(50)) + .clickable(onClick = { + focusMgr.clearFocus() + appSelectorRequest = 2 + navCtrl.navigate("PackageSelector") + }) + .padding(3.dp)) + }, modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp) ) + CheckBoxItem(R.string.specify_activity, specifyActivity, { specifyActivity = it }) + AnimatedVisibility(specifyActivity) { + OutlinedTextField( + value = startLockTaskActivity, + onValueChange = { startLockTaskActivity = it }, + label = { Text("Activity") }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + modifier = Modifier.fillMaxWidth().padding(bottom = 5.dp) + ) + } Button( modifier = Modifier.fillMaxWidth(), onClick = { @@ -815,7 +877,8 @@ private fun LockTaskMode() { } val options = ActivityOptions.makeBasic().setLockTaskEnabled(true) val packageManager = context.packageManager - val launchIntent = packageManager.getLaunchIntentForPackage(startLockTaskApp) + val launchIntent = if(specifyActivity) Intent().setComponent(ComponentName(startLockTaskApp, startLockTaskActivity)) + else packageManager.getLaunchIntentForPackage(startLockTaskApp) if (launchIntent != null) { coroutine.launch { prepareForNotification(context) { @@ -838,8 +901,8 @@ private fun LockTaskMode() { @Composable private fun CaCert() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val uri by fileUriFlow.collectAsState() var exist by remember { mutableStateOf(false) } val uriPath = uri.path ?: "" @@ -865,7 +928,7 @@ private fun CaCert() { Text(text = uriPath) } Text( - text = if(uriPath == "") { stringResource(R.string.please_select_ca_cert) } else { stringResource(R.string.cacert_installed, exist) }, + text = if(uriPath == "") { stringResource(R.string.please_select_ca_cert) } else { stringResource(R.string.cert_installed, exist) }, modifier = Modifier.animateContentSize() ) Spacer(Modifier.padding(vertical = 5.dp)) @@ -922,14 +985,14 @@ private fun CaCert() { @Composable private fun SecurityLogs() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.security_logs), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.developing)) - SwitchItem(R.string.enable, "", null, { dpm.isSecurityLoggingEnabled(receiver) }, { dpm.setSecurityLoggingEnabled(receiver,it) }) + SwitchItem(R.string.enable, "", null, { dpm.isSecurityLoggingEnabled(receiver) }, { dpm.setSecurityLoggingEnabled(receiver,it) }, padding = false) Button( onClick = { val log = dpm.retrieveSecurityLogs(receiver) @@ -967,9 +1030,9 @@ private fun SecurityLogs() { @Composable fun FactoryResetProtection() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val dpm = context.getDPM() val focusMgr = LocalFocusManager.current - val receiver = ComponentName(context,Receiver::class.java) + val receiver = context.getReceiver() var usePolicy by remember { mutableStateOf(false) } var enabled by remember { mutableStateOf(false) } var unsupported by remember { mutableStateOf(false) } @@ -1006,7 +1069,7 @@ fun FactoryResetProtection() { } AnimatedVisibility(usePolicy) { Column { - CheckBoxItem(stringResource(R.string.enable_frp), enabled, { enabled = it }) + CheckBoxItem(R.string.enable_frp, enabled, { enabled = it }) Text(stringResource(R.string.account_list_is)) Text( text = if(accountList.isEmpty()) stringResource(R.string.none) else accountList.toText(), @@ -1070,8 +1133,8 @@ fun FactoryResetProtection() { private fun WipeData() { val context = LocalContext.current val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var warning by remember { mutableStateOf(false) } var wipeDevice by remember { mutableStateOf(false) } @@ -1089,16 +1152,16 @@ private fun WipeData() { ) Spacer(Modifier.padding(vertical = 5.dp)) CheckBoxItem( - stringResource(R.string.wipe_external_storage), + R.string.wipe_external_storage, externalStorage, { externalStorage = it } ) - if(VERSION.SDK_INT >= 22 && isDeviceOwner(dpm)) { - CheckBoxItem(stringResource(R.string.wipe_reset_protection_data), + if(VERSION.SDK_INT >= 22 && context.isDeviceOwner) { + CheckBoxItem(R.string.wipe_reset_protection_data, protectionData, { protectionData = it } ) } - if(VERSION.SDK_INT >= 28) { CheckBoxItem(stringResource(R.string.wipe_euicc), euicc, { euicc = it }) } - if(VERSION.SDK_INT >= 29) { CheckBoxItem(stringResource(R.string.wipe_silently), silent, { silent = it }) } + if(VERSION.SDK_INT >= 28) { CheckBoxItem(R.string.wipe_euicc, euicc, { euicc = it }) } + if(VERSION.SDK_INT >= 29) { CheckBoxItem(R.string.wipe_silently, silent, { silent = it }) } AnimatedVisibility(!silent && VERSION.SDK_INT >= 28) { OutlinedTextField( value = reason, onValueChange = { reason = it }, @@ -1120,7 +1183,7 @@ private fun WipeData() { Text("WipeData") } } - if (VERSION.SDK_INT >= 34 && (isDeviceOwner(dpm) || dpm.isOrgProfile(receiver))) { + if (VERSION.SDK_INT >= 34 && (context.isDeviceOwner || dpm.isOrgProfile(receiver))) { Button( onClick = { focusMgr.clearFocus() @@ -1180,8 +1243,8 @@ private fun WipeData() { @Composable private fun SysUpdatePolicy() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) @@ -1191,18 +1254,18 @@ private fun SysUpdatePolicy() { Text(text = stringResource(R.string.system_update_policy), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) RadioButtonItem( - stringResource(R.string.system_update_policy_automatic), + R.string.system_update_policy_automatic, selectedPolicy == TYPE_INSTALL_AUTOMATIC, { selectedPolicy = TYPE_INSTALL_AUTOMATIC } ) RadioButtonItem( - stringResource(R.string.system_update_policy_install_windowed), + R.string.system_update_policy_install_windowed, selectedPolicy == TYPE_INSTALL_WINDOWED, { selectedPolicy = TYPE_INSTALL_WINDOWED } ) RadioButtonItem( - stringResource(R.string.system_update_policy_postpone), + R.string.system_update_policy_postpone, selectedPolicy == TYPE_POSTPONE, { selectedPolicy = TYPE_POSTPONE } ) - RadioButtonItem(stringResource(R.string.none), selectedPolicy == null, { selectedPolicy = null }) + RadioButtonItem(R.string.none, selectedPolicy == null, { selectedPolicy = null }) var windowedPolicyStart by remember { mutableStateOf("") } var windowedPolicyEnd by remember { mutableStateOf("") } if(selectedPolicy == 2) { @@ -1270,8 +1333,8 @@ private fun SysUpdatePolicy() { @Composable fun InstallSystemUpdate() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val callback = object: InstallSystemUpdateCallback() { override fun onInstallUpdateError(errorCode: Int, errorMessage: String) { super.onInstallUpdateError(errorCode, errorMessage) @@ -1332,7 +1395,7 @@ fun InstallSystemUpdate() { @SuppressLint("NewApi") private fun sendStopLockTaskNotification(context: Context) { - val nm = context.getSystemService(ComponentActivity.NOTIFICATION_SERVICE) as NotificationManager + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (VERSION.SDK_INT >= 26) { val channel = NotificationChannel("LockTaskMode", context.getString(R.string.lock_task_mode), NotificationManager.IMPORTANCE_HIGH).apply { description = "Notification channel for stop lock task mode" diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/UserManager.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/UserManager.kt index 0648413..20eb547 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/UserManager.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/UserManager.kt @@ -2,7 +2,6 @@ package com.bintianqi.owndroid.dpm import android.annotation.SuppressLint import android.app.admin.DevicePolicyManager -import android.content.ComponentName import android.content.Context import android.content.Intent import android.graphics.BitmapFactory @@ -14,7 +13,6 @@ import android.os.UserHandle import android.os.UserManager import android.provider.MediaStore import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Arrangement @@ -57,7 +55,6 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.Receiver import com.bintianqi.owndroid.fileUriFlow import com.bintianqi.owndroid.getFile import com.bintianqi.owndroid.toText @@ -77,7 +74,7 @@ fun UserManage(navCtrl: NavHostController) { Scaffold( topBar = { TopBar(backStackEntry, navCtrl, localNavCtrl) { - if(backStackEntry?.destination?.route == "Home" && scrollState.maxValue > 80) { + if(backStackEntry?.destination?.route == "Home" && scrollState.maxValue > 100) { Text( text = stringResource(R.string.user_manager), modifier = Modifier.alpha((maxOf(scrollState.value-30, 0)).toFloat() / 80) @@ -109,30 +106,31 @@ fun UserManage(navCtrl: NavHostController) { @Composable private fun Home(navCtrl: NavHostController,scrollState: ScrollState) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val deviceOwner = context.isDeviceOwner + val profileOwner = context.isProfileOwner Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) { Text( text = stringResource(R.string.user_manager), style = typography.headlineLarge, - modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp) + modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 16.dp) ) SubPageItem(R.string.user_info, "", R.drawable.person_fill0) { navCtrl.navigate("UserInfo") } - if(isDeviceOwner(dpm)) { + if(deviceOwner) { SubPageItem(R.string.user_operation, "", R.drawable.sync_alt_fill0) { navCtrl.navigate("UserOperation") } } - if(VERSION.SDK_INT >= 24 && isDeviceOwner(dpm)) { + if(VERSION.SDK_INT >= 24 && deviceOwner) { SubPageItem(R.string.create_user, "", R.drawable.person_add_fill0) { navCtrl.navigate("CreateUser") } } - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(deviceOwner || profileOwner) { SubPageItem(R.string.edit_username, "", R.drawable.edit_fill0) { navCtrl.navigate("EditUsername") } } - if(VERSION.SDK_INT >= 23 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) { SubPageItem(R.string.change_user_icon, "", R.drawable.account_circle_fill0) { navCtrl.navigate("ChangeUserIcon") } } - if(VERSION.SDK_INT >= 28 && isDeviceOwner(dpm)) { + if(VERSION.SDK_INT >= 28 && deviceOwner) { SubPageItem(R.string.user_session_msg, "", R.drawable.notifications_fill0) { navCtrl.navigate("UserSessionMessage") } } - if(VERSION.SDK_INT >= 26 && (isDeviceOwner(dpm) || isProfileOwner(dpm))) { + if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) { SubPageItem(R.string.affiliation_id, "", R.drawable.id_card_fill0) { navCtrl.navigate("AffiliationID") } } Spacer(Modifier.padding(vertical = 30.dp)) @@ -143,8 +141,8 @@ private fun Home(navCtrl: NavHostController,scrollState: ScrollState) { @Composable private fun CurrentUserInfo() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) @@ -159,7 +157,7 @@ private fun CurrentUserInfo() { if (VERSION.SDK_INT >= 28) { val logoutable = dpm.isLogoutEnabled Text(text = stringResource(R.string.user_can_logout, logoutable)) - if(isDeviceOwner(dpm) || isProfileOwner(dpm)) { + if(context.isDeviceOwner || context.isProfileOwner) { val ephemeralUser = dpm.isEphemeralUser(receiver) Text(text = stringResource(R.string.is_ephemeral_user, ephemeralUser)) } @@ -175,8 +173,8 @@ private fun CurrentUserInfo() { private fun UserOperation() { val context = LocalContext.current val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) @@ -205,11 +203,11 @@ private fun UserOperation() { ) Spacer(Modifier.padding(vertical = 3.dp)) if(VERSION.SDK_INT >= 24) { - CheckBoxItem(text = stringResource(R.string.use_uid), checked = useUid, operation = { idInput=""; useUid = it }) + CheckBoxItem(text = R.string.use_uid, checked = useUid, operation = { idInput=""; useUid = it }) } Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT > 28) { - if(isProfileOwner(dpm)&&dpm.isAffiliatedUser) { + if(context.isProfileOwner && dpm.isAffiliatedUser) { Button( onClick = { val result = dpm.logoutUser(receiver) @@ -281,8 +279,8 @@ private fun UserOperation() { private fun CreateUser() { val context = LocalContext.current val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var userName by remember { mutableStateOf("") } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { @@ -299,20 +297,20 @@ private fun CreateUser() { ) Spacer(Modifier.padding(vertical = 5.dp)) var selectedFlag by remember { mutableIntStateOf(0) } - RadioButtonItem(stringResource(R.string.none), selectedFlag == 0, { selectedFlag = 0 }) + RadioButtonItem(R.string.none, selectedFlag == 0, { selectedFlag = 0 }) RadioButtonItem( - stringResource(R.string.create_user_skip_wizard), + R.string.create_user_skip_wizard, selectedFlag == DevicePolicyManager.SKIP_SETUP_WIZARD, { selectedFlag = DevicePolicyManager.SKIP_SETUP_WIZARD } ) if(VERSION.SDK_INT >= 28) { RadioButtonItem( - stringResource(R.string.create_user_ephemeral_user), + R.string.create_user_ephemeral_user, selectedFlag == DevicePolicyManager.MAKE_USER_EPHEMERAL, { selectedFlag = DevicePolicyManager.MAKE_USER_EPHEMERAL } ) RadioButtonItem( - stringResource(R.string.create_user_enable_all_system_app), + R.string.create_user_enable_all_system_app, selectedFlag == DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED, { selectedFlag = DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED } ) @@ -339,8 +337,8 @@ private fun CreateUser() { @Composable private fun AffiliationID() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var input by remember { mutableStateOf("") } var list by remember { mutableStateOf("") } @@ -405,8 +403,8 @@ private fun AffiliationID() { @Composable private fun Username() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var inputUsername by remember { mutableStateOf("") } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { @@ -444,8 +442,8 @@ private fun Username() { @Composable private fun UserSessionMessage() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current val getStart = dpm.getStartUserSessionMessage(receiver)?:"" val getEnd = dpm.getEndUserSessionMessage(receiver)?:"" @@ -501,8 +499,8 @@ private fun UserSessionMessage() { @Composable private fun UserIcon() { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() var getContent by remember { mutableStateOf(false) } val canApply = fileUriFlow.collectAsState().value != Uri.parse("") Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { @@ -511,7 +509,7 @@ private fun UserIcon() { Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.pick_a_square_image)) Spacer(Modifier.padding(vertical = 5.dp)) - CheckBoxItem(stringResource(R.string.file_picker_instead_gallery), getContent, { getContent = it }) + CheckBoxItem(R.string.file_picker_instead_gallery, getContent, { getContent = it }) Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { 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 fd25984..f0ec4f2 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt @@ -1,19 +1,18 @@ package com.bintianqi.owndroid.dpm import android.annotation.SuppressLint -import android.app.admin.DevicePolicyManager -import android.content.ComponentName import android.content.Context import android.os.Build.VERSION import android.os.UserManager import android.widget.Toast -import androidx.activity.ComponentActivity import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -33,13 +32,12 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.Receiver import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.SubPageItem import com.bintianqi.owndroid.ui.SwitchItem import com.bintianqi.owndroid.ui.TopBar -private data class Restriction( +data class Restriction( val restriction:String, @StringRes val name:Int, val desc:String, @@ -98,18 +96,18 @@ fun UserRestriction(navCtrl: NavHostController) { @Composable private fun Home(navCtrl:NavHostController, scrollState: ScrollState) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) + val dpm = context.getDPM() + val receiver = context.getReceiver() Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) { Text( text = stringResource(R.string.user_restrict), style = typography.headlineLarge, - modifier = Modifier.padding(top = 8.dp, bottom = 7.dp, start = 15.dp) + modifier = Modifier.padding(top = 8.dp, bottom = 7.dp, start = 16.dp) ) - Text(text = stringResource(R.string.switch_to_disable_feature), modifier = Modifier.padding(start = 15.dp)) - if(isProfileOwner(dpm)) { Text(text = stringResource(R.string.profile_owner_is_restricted), modifier = Modifier.padding(start = 15.dp)) } - if(isProfileOwner(dpm) && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) { - Text(text = stringResource(R.string.some_features_invalid_in_work_profile), modifier = Modifier.padding(start = 15.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 && (VERSION.SDK_INT < 24 || (VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver)))) { + Text(text = stringResource(R.string.some_features_invalid_in_work_profile), modifier = Modifier.padding(start = 16.dp)) } Spacer(Modifier.padding(vertical = 2.dp)) SubPageItem(R.string.network_internet, "", R.drawable.wifi_fill0) { navCtrl.navigate("Internet") } @@ -193,29 +191,30 @@ private fun UserRestrictionItem( leadIcon:Int ) { val context = LocalContext.current - val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context, Receiver::class.java) - SwitchItem( - itemName,restrictionDescription,leadIcon, - { if(isDeviceOwner(dpm)||isProfileOwner(dpm)) { dpm.getUserRestrictions(receiver).getBoolean(restriction) }else{ false } }, - { - try{ - if(it) { - dpm.addUserRestriction(receiver,restriction) - }else{ - dpm.clearUserRestriction(receiver,restriction) + val dpm = context.getDPM() + val receiver = context.getReceiver() + Box(modifier = Modifier.padding(start = 22.dp, end = 16.dp)) { + SwitchItem( + itemName, restrictionDescription, leadIcon, + { dpm.getUserRestrictions(receiver).getBoolean(restriction) }, + { + try{ + if(it) { + dpm.addUserRestriction(receiver,restriction) + }else{ + dpm.clearUserRestriction(receiver,restriction) + } + } catch(e:SecurityException) { + if(context.isProfileOwner) { + Toast.makeText(context, R.string.require_device_owner, Toast.LENGTH_SHORT).show() + } } - }catch(e:SecurityException) { - if(isProfileOwner(dpm)) { - Toast.makeText(context, R.string.require_device_owner, Toast.LENGTH_SHORT).show() - } - } - }, - isDeviceOwner(dpm)||isProfileOwner(dpm) - ) + }, padding = false + ) + } } -private object RestrictionData{ +object RestrictionData { fun internet(): List{ val list:MutableList = mutableListOf() list += Restriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, R.string.config_mobile_network, "", R.drawable.signal_cellular_alt_fill0) @@ -262,7 +261,7 @@ private object RestrictionData{ if(VERSION.SDK_INT>=29) { list += Restriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, R.string.install_unknown_src_globally, "", R.drawable.android_fill0) } list += Restriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, R.string.inst_unknown_src, "", R.drawable.android_fill0) list += Restriction(UserManager.DISALLOW_UNINSTALL_APPS, R.string.uninstall_app, "", R.drawable.delete_fill0) - list += Restriction(UserManager.DISALLOW_APPS_CONTROL, R.string.apps_ctrl, context.getString(R.string.apps_control_desc), R.drawable.apps_fill0) + list += Restriction(UserManager.DISALLOW_APPS_CONTROL, R.string.apps_ctrl, "", R.drawable.apps_fill0) if(VERSION.SDK_INT>=34) { list += Restriction(UserManager.DISALLOW_CONFIG_DEFAULT_APPS, R.string.config_default_apps, "", R.drawable.apps_fill0) } return list } @@ -302,10 +301,10 @@ private object RestrictionData{ list += Restriction(UserManager.DISALLOW_CONTENT_CAPTURE, R.string.content_capture, "", R.drawable.screenshot_fill0) list += Restriction(UserManager.DISALLOW_CONTENT_SUGGESTIONS, R.string.content_suggestions, "", R.drawable.sms_fill0) } - list += Restriction(UserManager.DISALLOW_CREATE_WINDOWS, R.string.create_windows, context.getString(R.string.create_windows_desc), R.drawable.web_asset) + list += Restriction(UserManager.DISALLOW_CREATE_WINDOWS, R.string.create_windows, "", R.drawable.web_asset) if(VERSION.SDK_INT>=24) { list += Restriction(UserManager.DISALLOW_SET_WALLPAPER, R.string.set_wallpaper, "", R.drawable.wallpaper_fill0) } if(VERSION.SDK_INT>=34) { list += Restriction(UserManager.DISALLOW_GRANT_ADMIN, R.string.grant_admin, "", R.drawable.security_fill0) } - if(VERSION.SDK_INT>=23) { list += Restriction(UserManager.DISALLOW_FUN, R.string.`fun`, context.getString(R.string.fun_desc), R.drawable.stadia_controller_fill0) } + if(VERSION.SDK_INT>=23) { list += Restriction(UserManager.DISALLOW_FUN, R.string.`fun`, "", R.drawable.stadia_controller_fill0) } list += Restriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, R.string.modify_accounts, "", R.drawable.manage_accounts_fill0) if(VERSION.SDK_INT>=28) { list += Restriction(UserManager.DISALLOW_CONFIG_LOCALE, R.string.config_locale, "", R.drawable.language_fill0) @@ -317,4 +316,14 @@ private object RestrictionData{ list += Restriction(UserManager.DISALLOW_DEBUGGING_FEATURES, R.string.debug_features, "", R.drawable.adb_fill0) return list } + fun getAllRestrictions(context: Context): List { + val result = mutableListOf() + internet().forEach { result.add(it.restriction) } + connectivity().forEach { result.add(it.restriction) } + media().forEach { result.add(it.restriction) } + application(context).forEach { result.add(it.restriction) } + user().forEach { result.add(it.restriction) } + other(context).forEach { result.add(it.restriction) } + return result + } } diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt index 4aa34ec..c913cbb 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt @@ -35,11 +35,10 @@ fun SubPageItem( operation: () -> Unit ) { Row( - modifier = Modifier.fillMaxWidth().clickable(onClick = operation).padding(vertical = 15.dp), + modifier = Modifier.fillMaxWidth().clickable(onClick = operation).padding(top = 15.dp, bottom = 15.dp, start = 30.dp, end = 12.dp), verticalAlignment = Alignment.CenterVertically ) { - Spacer(Modifier.padding(start = 30.dp)) - if(icon!=null) { + if(icon != null) { Icon(painter = painterResource(icon), contentDescription = stringResource(title), modifier = Modifier.padding(top = 1.dp)) Spacer(Modifier.padding(start = 15.dp)) } @@ -78,6 +77,16 @@ fun Information(content: @Composable ()->Unit) { } } +@Composable +fun RadioButtonItem( + @StringRes text: Int, + selected: Boolean, + operation: () -> Unit, + textColor: Color = colorScheme.onBackground +) { + RadioButtonItem(stringResource(text), selected, operation, textColor) +} + @Composable fun RadioButtonItem( text: String, @@ -97,7 +106,7 @@ fun RadioButtonItem( @Composable fun CheckBoxItem( - text: String, + @StringRes text: Int, checked: Boolean, operation: (Boolean) -> Unit, textColor: Color = colorScheme.onBackground @@ -111,10 +120,11 @@ fun CheckBoxItem( checked = checked, onCheckedChange = operation ) - Text(text = text, color = textColor, modifier = Modifier.padding(bottom = if(zhCN) { 2 } else { 0 }.dp)) + Text(text = stringResource(text), color = textColor, modifier = Modifier.padding(bottom = if(zhCN) { 2 } else { 0 }.dp)) } } + @Composable fun SwitchItem( @StringRes title: Int, @@ -123,22 +133,35 @@ fun SwitchItem( getState: ()->Boolean, onCheckedChange: (Boolean)->Unit, enable: Boolean = true, - onClickBlank: (() -> Unit)? = null + onClickBlank: (() -> Unit)? = null, + padding: Boolean = true +) { + var state by remember { mutableStateOf(getState()) } + SwitchItem(title, desc, icon, state, { onCheckedChange(it); state = getState() }, enable, onClickBlank, padding) +} + +@Composable +fun SwitchItem( + @StringRes title: Int, + desc: String, + @DrawableRes icon: Int?, + state: Boolean, + onCheckedChange: (Boolean)->Unit, + enable: Boolean = true, + onClickBlank: (() -> Unit)? = null, + padding: Boolean = true ) { - var checked by remember { mutableStateOf(false) } - checked = getState() Box( modifier = Modifier .fillMaxWidth() .clickable(enabled = onClickBlank != null, onClick = onClickBlank?:{}) - .padding(vertical = 5.dp) + .padding(top = 5.dp, bottom = 5.dp, start = if(padding) 30.dp else 0.dp, end = if(padding) 12.dp else 0.dp) ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.align(Alignment.CenterStart) ) { - Spacer(Modifier.padding(start = 30.dp)) - if(icon!=null) { + if(icon != null) { Icon(painter = painterResource(icon),contentDescription = null) Spacer(Modifier.padding(start = 15.dp)) } @@ -151,8 +174,8 @@ fun SwitchItem( } } Switch( - checked = checked, onCheckedChange = {onCheckedChange(it);checked=getState() }, - modifier = Modifier.align(Alignment.CenterEnd).padding(end = 12.dp), enabled = enable + checked = state, onCheckedChange = { onCheckedChange(it) }, + modifier = Modifier.align(Alignment.CenterEnd), enabled = enable ) } } diff --git a/app/src/main/res/drawable/check_circle_fill0.xml b/app/src/main/res/drawable/check_circle_fill0.xml new file mode 100644 index 0000000..e63dbdf --- /dev/null +++ b/app/src/main/res/drawable/check_circle_fill0.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/close_fill0.xml b/app/src/main/res/drawable/close_fill0.xml new file mode 100644 index 0000000..0dffe63 --- /dev/null +++ b/app/src/main/res/drawable/close_fill0.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/search_fill0.xml b/app/src/main/res/drawable/search_fill0.xml new file mode 100644 index 0000000..0812e9f --- /dev/null +++ b/app/src/main/res/drawable/search_fill0.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f502e9d..575e670 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -48,7 +48,6 @@ Kopyala Dosya Mevcut Değil G/Ç Hatası - Mevcut Durum: Başlat Stop Tümünü İzin Ver @@ -63,6 +62,8 @@ Etkinleştir... Profil Sahibi Cihaz Sahibi + Dhizuku will be deactivated + Reset device policy Cihaz Yöneticisini Etkinleştir Cihaz Bilgisi Cihaz Kimliği Doğrulama Desteği: @@ -83,7 +84,7 @@ Hesap Yönetimini Devre Dışı Bırak Hesap Türleri: Sahipliği Devret - Cihaz sahibi veya profil sahibi ayrıcalığını başka bir uygulamaya devredin. Hedef uygulama cihaz yöneticisi olmalıdır. + Cihaz sahibi veya profil sahibi ayrıcalığını başka bir uygulamaya devredin. Hedef Paket Adı Hedef Sınıf Adı Ekran Kilidi Bilgisi @@ -99,13 +100,19 @@ OwnDroid: Devre Dışı OwnDroid: İş Profili Başarıyla Oluşturuldu + + Failed to initialize Dhizuku + Dhizuku permission not granted + Dhizuku mode disabled + İzni Kontrol Et Sahipleri Listele + List users + List accounts Shizuku Başlatılmadı. İzin Verildi (Kabuk) İzin Verildi (Root) - Profil Sahibini Etkinleştir Cihaz Sahibini Etkinleştir Kuruluş Profili Sahibini Etkinleştir Shizuku Hizmeti Bağlantısı Kesildi @@ -118,7 +125,6 @@ Kamerayı devre dışı bırak Ekran görüntüsünü devre dışı bırak Durum çubuğunu devre dışı bırak - AOSP\'de ekran kaydını da devre dışı bırak Otomatik saat Otomatik saat gerektir Otomatik saat dilimi @@ -126,10 +132,8 @@ Yedekleme servisi Bluetooth kişi paylaşımını devre dışı bırak Ortak kriter modu - Bilinmeyen etki USB sinyali Ekran kilidi - Ekran kilidini devre dışı bırakmak için parola ayarlanmamış olmalıdır Ekranı şimdi kilitle Kimlik doğrulama şifreleme anahtarını çıkar Hata raporu @@ -156,8 +160,9 @@ Lock task mode Görev kilitleme özelliği Lock task packages + Specify Activity Start lock task mode - App not allowed + App is not allowed Hepsini devre dışı bırak @@ -168,11 +173,11 @@ Küresel eylemlere izin ver Ekran kilidine izin ver Görevde etkinlik başlatmayı engelle - CA sertifikası - Lütfen bir sertifika seçin - Yüklenen sertifika: %1$s - Sertifika seç... - Tüm kullanıcı sertifikalarını kaldır + CA sertifikası + Lütfen bir sertifika seçin + Yüklenen sertifika: %1$s + Sertifika seç... + Tüm kullanıcı sertifikalarını kaldır Güvenlik kayıtları Yeniden başlatmadan önce güvenlik kayıtları Verileri sil @@ -213,7 +218,7 @@ Wi-Fi MAC adresi Minimum Wi-Fi güvenlik seviyesi - Açık + Açık Tercihli ağ hizmeti Yönetici tarafından yapılandırılmış ağı kilitle WiFi SSID politikası @@ -228,6 +233,13 @@ Geçersiz ana bilgisayar adı Güvenlik İstisnası DNS ana bilgisayarını ayarla + Recommended global proxy + No proxy + PAC proxy + Direct proxy + Specify port + Invalid config + Exclude hosts Ağ kayıtları Geri al WiFi anahtar çifti @@ -284,15 +296,11 @@ Yükleniyor Kullanıcı uygulamalarını göster Sistem uygulamalarını göster - Priv uygulamalarını göster - Apex uygulamalarını göster - İzin seçici Askıya al Gizle Mevcut olmayan uygulamalar gizlidir Her zaman açık VPN Enable lockdown - Current app: Clear current config İzin Kapsam: iş profili @@ -332,6 +340,7 @@ APK seç... Sessiz yükleme Yükleme isteği + Search Uygulama yükleyici: @@ -387,7 +396,6 @@ Bilinmeyen kaynakları global olarak yükle Bilinmeyen kaynakları yükle Uygulama kontrolü - Uygulama verilerini veya önbelleği temizle Varsayılan uygulamaları yapılandır Parlaklığı yapılandır @@ -411,11 +419,9 @@ İçerik yakalama İçerik önerileri Pencere oluştur - ör. tostlar, bildirim afişi Duvar kağıdı ayarla Cihaz yöneticisi yetkisi ver Eğlence - Oyunları devre dışı bırakabilir Hesapları değiştir Yerel ayarları yapılandır Tarihi veya saati yapılandır @@ -459,7 +465,7 @@ Kullanıcı simgesini değiştir Kare bir resim seçmelisiniz Galeri yerine dosya seçici kullan - Resim seç... + Resim seç... Bilinmeyen sonuç (başarısız olabilir) Başarısız: yönetilen profil Başarısız: mevcut kullanıcı @@ -488,17 +494,18 @@ Başarısız şifre denemeleri: %1$s Birleşik şifre: %1$s Şifre sıfırlama jetonu + Token + The token must be longer than 32-byte Jeton zaten etkinleştirildi Temizle Ayarla Lütfen bir jeton ayarlayın Şifre ayarlanmadığında jeton otomatik olarak etkinleştirilecektir. Şifreyi sıfırla + Confirm password Başlangıçta kimlik bilgilerini sorma Giriş gerektir - En az 4 haneli şifre gerektir Jeton ile şifreyi sıfırla - Şifreyi sıfırla Gereken şifre karmaşıklığı Yeni şifre ayarlanmasını iste Kilit ekranı özellikleri @@ -532,7 +539,6 @@ Material You rengi Android 12+ Hakkında - Cihazınızı tam kontrol altına almak için Cihaz yöneticisi, Profil sahibi ve Cihaz sahibi ayrıcalıklarını kullanın. Kullanıcı rehberi Kaynak kodu Tema @@ -543,7 +549,6 @@ OwnDroid\'u kilitle Biyometri ile doğrulama Doğrula - OwnDroid başlatıldığında ana ekran şifresi veya biyometri ile doğrulama Şifre kullan OwnDroid\'u şifre ile doğrula OwnDroid\'u biyometri ile doğrula @@ -558,6 +563,7 @@ Debug mode + Bildirim gönder Harici depolamayı oku Harici depolamaya yaz Medya oku (ses) @@ -572,17 +578,24 @@ Yaklaşık konuma eriş Kesin konuma eriş Arka planda konuma eriş + Bluetooth connect + Bluetooth scan + Bluetooth advertise + Nearby Wi-Fi devices Telefonla arama yap + Answer phone calls Telefon durumunu oku + Use SIP + UWB ranging SMS oku SMS al SMS gönder Arama kaydını oku Arama kaydına yaz + Receive WAP push Vücut sensörlerine eriş Arka planda vücut sensörlerine eriş Aktivite tanıma - Bildirim gönder Version name Version code diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 1faecb5..8b4041d 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -45,7 +45,6 @@ 复制 文件不存在 IO异常 - 当前状态: 开始 停止 允许全部 @@ -60,6 +59,8 @@ 激活... Profile owner Device owner + 重置设备策略 + Dhizuku将被停用 激活Device admin 设备信息 支持设备ID认证: @@ -78,7 +79,7 @@ 禁用账号管理 账号类型: 转移所有权 - 把Device owner或Profile owner权限转移到另一个应用 (目标必须是Device admin) + 把Device owner或Profile owner权限转移到另一个应用 目标包名 目标类名 锁屏提示信息 @@ -94,13 +95,19 @@ OwnDroid:已禁用 OwnDroid:创建工作资料成功 + + Dhizuku初始化失败 + Dhizuku未授权 + Dhizuku模式已禁用 + 检查Shizuku 列出Owners + 列出用户 + 列出账号 服务未启动 已授权(Shell) 已授权(Root) - 激活Profile owner 激活Device owner 激活由组织拥有的工作资料 Shizuku服务断开连接 @@ -113,7 +120,6 @@ 禁用相机 禁止屏幕捕获 禁用状态栏 - 对AOSP的录屏也起作用 自动设置时间 自动设置时区 要求自动时间 @@ -121,10 +127,8 @@ 备份服务 禁止蓝牙分享联系人 通用标准模式 - Common Criteria USB信号 锁屏 - 禁用需要无密码 立即锁屏 移除凭证加密密钥 错误报告 @@ -151,6 +155,7 @@ 锁定任务模式 锁定任务功能 锁定任务应用 + 指定Activity 启动锁定任务模式 应用未被允许 禁用全部 @@ -163,9 +168,9 @@ 阻止启动未允许的应用 包名 不存在 - Ca证书 - 请选择Ca证书 - 证书已安装:%1$s + CA证书 + 请选择CA证书 + 证书已安装:%1$s 选择证书... 卸载所有用户证书 安全日志 @@ -208,7 +213,7 @@ 网络 Wi-Fi Mac地址 最低WiFi安全等级 - 开放 + 开放 优先网络服务 锁定由管理员配置的网络 WiFi SSID策略 @@ -223,6 +228,13 @@ 无效主机名 安全错误 设置DNS主机 + 建议的全局代理 + 无代理 + PAC代理 + 直连代理 + 指定端口 + 无效配置 + 排除列表 收集网络日志 收集 WiFi密钥对 @@ -279,15 +291,11 @@ 加载中 显示用户应用 显示系统应用 - 显示priv-app - 显示apex应用 - 权限选择器 挂起 隐藏 如果隐藏,有可能是没安装 VPN保持打开 启用锁定 - 当前应用: 清除当前配置 权限 作用域: 工作资料 @@ -325,6 +333,8 @@ 请求安装 启用系统应用 重新启用一个默认被禁用的系统应用 + 搜索 + 应用安装器: 等待用户操作 被阻止 @@ -378,7 +388,6 @@ 安装未知来源应用(全局) 安装未知来源应用 控制应用 - 清空缓存/清空内部存储 修改默认App 调整亮度 @@ -402,11 +411,9 @@ 内容捕获 内容建议 创建窗口 - 可能包括Toast和浮动通知 更换壁纸 启用设备管理器 娱乐 - 会影响谷歌商店的游戏 修改账号设置 修改语言 修改日期、时间 @@ -459,7 +466,7 @@ 密码与锁屏 密码信息 - 留空可以清除密码,纯数字将使用PIN码 + 留空以清除密码 最大密码错误次数 达到该限制会恢复出厂设置,0为无限制 错误次数 @@ -479,17 +486,18 @@ 密码已错误次数:%1$s 个人与工作应用密码一致:%1$s 密码重置令牌 + 令牌 + 令牌必须大于32字节 令牌已经激活 清除 设置 请先设置令牌 没有密码时会自动激活令牌 重置密码 + 确认密码 启动(boot)时不要求密码 不允许其他设备管理员重置密码直至用户输入一次密码 - 需要4位密码 使用令牌重置密码 - 重置密码(弃用) 密码复杂度要求 要求设置新密码 锁屏功能 @@ -523,7 +531,6 @@ Material you 颜色 安卓12+ 关于 - 使用安卓的Device admin、Device owner、Profile owner,全方位掌控你的设备 使用教程 源代码 主题 @@ -534,7 +541,6 @@ 锁定OwnDroid 使用生物识别 验证 - 在OwnDroid启动时使用锁屏密码或生物识别进行验证 使用密码 使用密码进行验证 使用生物识别进行验证 @@ -549,6 +555,7 @@ 调试模式 + 发送通知 读取外部存储 写入外部存储 读取音频 @@ -563,17 +570,24 @@ 粗略位置 准确位置 后台获取位置 + 蓝牙连接 + 蓝牙扫描 + 使设备可被发现 + 附近的Wi-Fi设备 打电话 + 接电话 读取手机状态 + 使用SIP + 超宽频 读取短信 接收短信 发送短信 读取通话记录 写入通话记录 + 接收WAP推送 身体传感器 后台使用身体传感器 查看使用情况 - 发送通知 版本名 版本号 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fa3c1ad..bf6f001 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,7 +48,6 @@ Copy File not exist IO Exception - Current status: Start Stop Allow all @@ -63,9 +62,10 @@ Activate... Profile owner Device owner + Dhizuku will be deactivated + Reset device policy Activate Device admin dpm set-active-admin com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver - dpm set-profile-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver Device info Support Device ID attestation: @@ -85,7 +85,7 @@ Disable account management Account types: Transfer Ownership - Transfer device owner or profile owner privilege to another app. The target app must be a device admin. + Transfer device owner or profile owner privilege to another app. Target package name Target class name Lockscreen info @@ -101,17 +101,22 @@ OwnDroid: Disabled OwnDroid: Create work profile success + + Dhizuku + Failed to initialize Dhizuku + Dhizuku permission not granted + Dhizuku mode disabled Shizuku Check permission List owners + List users + List accounts Shizuku not started. dpm set-device-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver - dpm set-profile-owner com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver dpm set-active-admin com.bintianqi.owndroid/com.bintianqi.owndroid.Receiver Permission granted (Shell) Permission granted (Root) - Activate Profile owner Activate Device owner Activate organization-owned work profile Shizuku service disconnected @@ -124,7 +129,6 @@ Disable camera Disable screen capture Disable status bar - Also disable screen record in AOSP. Auto time Require auto time Auto timezone @@ -132,10 +136,8 @@ Backup service Disable bluetooth contact sharing Common criteria mode - Unknown effect USB signal Keyguard - Disable keyguard require no password is set. Lock screen now Evict credential encryption key Bug report @@ -162,8 +164,9 @@ Lock task mode Lock task feature Lock task packages + Specify Activity Start lock task mode - App not allowed + App is not allowed Disable all Allow system info @@ -173,11 +176,11 @@ Allow global actions Allow keyguard Block activity start in task - Ca certification - Please select a certification - Cert installed: %1$s - Select cert... - Uninstall all user cert + CA certificate + Please select a certificate + Certificate installed: %1$s + Select certificate... + Uninstall all user CA certificate Security logs Pre-reboot security logs Wipe data @@ -219,14 +222,14 @@ Network Wi-Fi Mac address Min Wi-Fi security level - Open + Open Preferential network service Lockdown admin configured network WiFi SSID policy SSID list: Cannot be empty Already exist - PrivateDNS + Private DNS Provide hostname Host not serving Set to opportunistic @@ -234,6 +237,13 @@ Invalid hostname Security Exception Set DNS host + Recommended global proxy + No proxy + PAC proxy + Direct proxy + Specify port + Invalid config + Exclude hosts Network logs Retrieve WiFi keypair @@ -293,15 +303,11 @@ Loading Show user apps Show system apps - Show priv-apps - Show apex apps - Permission picker Suspend Hide Non-existent apps is hidden Always-on VPN Enable lockdown - Current app: Clear current config Permission Scope: work profile @@ -340,6 +346,7 @@ Select APK... Silent install Request install + Search App installer: Pending user action @@ -394,7 +401,6 @@ Install unknown sources globally Install unknown sources Apps control - Clear app data or cache Configure default apps Configure brightness @@ -418,11 +424,9 @@ Content capture Content suggestions Create windows - e.g. toasts, notifications banner Set wallpaper Grant device admin Fun - May disable games Modify accounts Configure locale Configure date or time @@ -495,17 +499,18 @@ Password failed attempts: %1$s Unified password: %1$s Reset password token + Token + The token must be longer than 32-byte Token already activated Clear Set Please set a token Token will be automatically activated if no password is set. Reset password + Confirm password Do not ask credentials on boot Require entry - Require at least 4 digit password Reset password with token - Reset password Required password complexity Request to set a new password Keyguard features @@ -539,7 +544,6 @@ Material you color Android 12+ About - Use Device admin, Profile owner and Device owner privilege to take full control of your device. User guide Source code Theme @@ -550,7 +554,6 @@ Lock OwnDroid Auth with biometrics Authenticate - Authenticating with keyguard password or biometrics when OwnDroid launch Use password Authenticate OwnDroid with password Authenticate OwnDroid with biometrics @@ -565,6 +568,7 @@ Debug mode + Post notifications Read external storage Write external storage Read media (audio) @@ -579,17 +583,24 @@ Access coarse location Access fine location Access location in background + Bluetooth connect + Bluetooth scan + Bluetooth advertise + Nearby Wi-Fi devices Call phone + Answer phone calls Read phone state + Use SIP + UWB ranging Read SMS Receive SMS Send SMS Read call log Write call log + Receive WAP push Access body sensors Access body sensors in background Activity recognition - Post notifications Version name Version code diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 01038b9..fe108d8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,16 @@ [versions] agp = "8.5.0" -kt-android = "2.0.0" -cc = "2.0.0" +kotlin = "2.0.0" -androidx-activity-compose = "1.8.2" +androidx-activity-compose = "1.9.0" navigation-compose = "2.7.7" material3 = "1.2.1" accompanist-drawablepainter = "0.35.0-alpha" shizuku = "13.1.5" biometric = "1.2.0-alpha05" fragment = "1.8.0-beta01" +dhizuku = "2.5.2" +hiddenApiBypass = "4.3" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" } @@ -23,9 +24,11 @@ androidx-biometric = { group = "androidx.biometric", name = "biometric", version shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku" } shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" } +dhizuku-api = { module = "io.github.iamr0s:Dhizuku-API", version.ref = "dhizuku" } +hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" } androidx-fragment = { group = "androidx.fragment", name = "fragment", version.ref = "fragment" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kt-android" } -cc = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "cc" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +cc = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }