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 @@
Ağ
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" }