diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5eb926a..eaf90d9 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -19,10 +19,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Set up JDK 21
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
@@ -34,13 +34,13 @@ jobs:
run: ./gradlew build
- name: Upload Debug APK (testkey)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: OwnDroid-CI-${{ env.SHORT_SHA }}-debug-testkey
path: app/build/outputs/apk/debug/app-debug.apk
- name: Upload Release APK (testkey)
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: OwnDroid-CI-${{ env.SHORT_SHA }}-release-testkey
path: app/build/outputs/apk/release/app-release.apk
@@ -51,13 +51,13 @@ jobs:
./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
+ uses: actions/upload-artifact@v6
with:
name: OwnDroid-CI-${{ env.SHORT_SHA }}-debug-signed
path: app/build/outputs/apk/debug/app-debug.apk
- name: Upload Release APK
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: OwnDroid-CI-${{ env.SHORT_SHA }}-release-signed
path: app/build/outputs/apk/release/app-release.apk
@@ -68,10 +68,16 @@ jobs:
needs: build
steps:
- name: Download Artifacts
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v7
with:
path: artifacts
+ - 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
+ chmod +x ./binaries/telegram-bot-api
+
- name: Start API Server & Upload
env:
COMMIT_MESSAGE: |+
@@ -85,7 +91,8 @@ jobs:
mv ./$RELEASE_TEST_PWD/app-release.apk ./$RELEASE_TEST_PWD.apk && rm -rf ./$RELEASE_TEST_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://api.telegram.org/bot$token/sendMediaGroup?chat_id=-1002203528169&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" \
+ curl -v "http://127.0.0.1:8081/bot$token/sendMediaGroup?chat_id=-1002203528169&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 releaseSigned="@$RELEASE_SIGNED_PWD.apk"
diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml
new file mode 100644
index 0000000..c3ae2f0
--- /dev/null
+++ b/.github/workflows/dependency-submission.yml
@@ -0,0 +1,22 @@
+name: Dependency submission
+
+on:
+ push:
+ branches: [ "master" ]
+
+jobs:
+ dependency-submission:
+ name: Dependency submission
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v6
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: '21'
+
+ - name: Generate and submit dependency graph
+ uses: gradle/actions/dependency-submission@v5
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4e2288c..3218d34 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,12 +12,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repository
- uses: actions/checkout@v4
- with:
- ref: 'master'
+ uses: actions/checkout@v6
- name: Set up JDK 21
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
@@ -33,7 +31,4 @@ jobs:
- name: Create release
run: git tag -l ${{ github.ref_name }} --format="%(contents:body)" | gh release create ${{ github.ref_name }} -t ${{ github.ref_name }} *.apk -d -F -
env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Generate and submit dependency graph
- uses: gradle/actions/dependency-submission@v4
\ No newline at end of file
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/Readme-ja.md b/Readme-ja.md
index 4a8c3d5..eb017c8 100644
--- a/Readme-ja.md
+++ b/Readme-ja.md
@@ -1,7 +1,8 @@
[English](Readme.md) | [简体中文](Readme-zh_CN.md)
> [!important]
-> The Japanese readme need update
+> The Japanese readme is outdated
+> 日语的Readme已经过时
# OwnDroid
diff --git a/Readme-zh_CN.md b/Readme-zh_CN.md
index 349d306..1bd036f 100644
--- a/Readme-zh_CN.md
+++ b/Readme-zh_CN.md
@@ -35,27 +35,21 @@
## FAQ
-### 设备上有账号
+### 设备上已有账号
```text
java.lang.IllegalStateException: Not allowed to set the device owner because there are already some accounts on the device
```
-解决办法:
-- 冻结持有这些账号的app。
-- 删除这些账号。
+解决办法:冻结持有这些账号的app,或删除这些账号。
-### 设备上有多个用户
+### 设备上已有多个用户
```text
java.lang.IllegalStateException: Not allowed to set the device owner because there are already several users on the device
```
-解决办法:
-- 删除次级用户。
-
-> [!NOTE]
-> 一些系统有应用克隆、儿童空间等功能,它们通常是用户。
+解决办法:删除其他用户,包括工作资料、私密空间和应用分身。
### Device owner 已存在
@@ -89,7 +83,25 @@ user limit reached
三星限制了多用户功能,暂无解决办法。
-## API
+
+### 创建工作资料/用户
+
+在大部分设备上,设置device owner后不能创建工作资料,因为系统在设置device owner时会添加`no_add_managed_profile`等用户限制。
+Device owner不能修改系统设置的用户限制,但如果你有root权限,你可以在adb shell中执行以下命令以关闭这个限制。
+
+```shell
+pm set-user-restriction no_add_user 0
+pm set-user-restriction no_add_managed_profile 0
+pm set-user-restriction no_add_private_profile 0
+pm set-user-restriction no_add_clone_profile 0
+```
+
+一些系统在设置了device owner后不允许在安卓设置中创建用户,你可以在OwnDroid中创建用户。
+如果你有root,你也可以在adb shell中运行以上命令以解除限制。
+
+## 高级用户
+
+### API
OwnDroid提供了一个基于Intent的API。你需要在设置中设置密钥并启用API。括号中的数字是最小的安卓版本。
@@ -127,7 +139,9 @@ context.sendBroadcast(intent)
[可用的用户限制](https://developer.android.google.cn/reference/android/os/UserManager#constants_1)
-## 构建
+## 开发者
+
+### 构建
你可以在命令行中使用Gradle以构建OwnDroid
```shell
@@ -138,6 +152,10 @@ context.sendBroadcast(intent)
```
(在Windows系统中应使用`./gradlew.bat`)
+### 贡献
+
+请使用`dev`分支。
+
## 许可证
[License.md](LICENSE.md)
diff --git a/Readme.md b/Readme.md
index a462f4d..961e254 100644
--- a/Readme.md
+++ b/Readme.md
@@ -41,9 +41,7 @@ Use Android's DevicePolicyManager API to manage your device.
java.lang.IllegalStateException: Not allowed to set the device owner because there are already some accounts on the device
```
-Solutions:
-- Freeze apps who hold those accounts.
-- Delete these accounts.
+Solutions: freeze the accounts' holder apps, or delete those accounts.
### Already several users on the device
@@ -51,11 +49,7 @@ Solutions:
java.lang.IllegalStateException: Not allowed to set the device owner because there are already several users on the device
```
-Solutions:
-- Delete secondary users.
-
-> [!NOTE]
-> Some systems have features such as app cloning and children space, which are usually users.
+Solution: Delete secondary users, including work profile, private space and app cloning.
### Device owner is already set
@@ -63,7 +57,7 @@ Solutions:
java.lang.IllegalStateException: Trying to set the device owner (com.bintianqi.owndroid/.Receiver), but device owner (xxx) is already set.
```
-Only 1 device owner can exist on a device. Please deactivate the existing device owner first.
+Only one device owner can exist on a device. Please deactivate the existing device owner first.
### MIUI & HyperOS
@@ -91,7 +85,25 @@ user limit reached
Samsung restricts Android's multiple users feature. There is currently no solution.
-## API
+### Create work profile / user
+
+On most devices, creating work profile is not allowed by the system when the device owner exist.
+Because the system add `no_add_managed_profile` user restriction when a device owner is set.
+Device owner can't modify user restrictions set by the system, but if your device is rooted, you can disable this restriction by executing the following commands in adb shell.
+
+```shell
+pm set-user-restriction no_add_user 0
+pm set-user-restriction no_add_managed_profile 0
+pm set-user-restriction no_add_private_profile 0
+pm set-user-restriction no_add_clone_profile 0
+```
+
+Some systems disable the feature of adding users in Android settings once a device owner is set.
+You have to create users in OwnDroid. Or if you have root, run the above command in adb shell to remove that restriction.
+
+## For advanced users
+
+### API
OwnDroid provides an Intent-based API. You need to set the API key in settings and enable the API. The numbers in brackets represent the minimum Android version required.
@@ -129,7 +141,9 @@ context.sendBroadcast(intent)
[Available user restrictions](https://developer.android.com/reference/android/os/UserManager#constants_1)
-## Build
+## For developers
+
+### Build
You can use Gradle in command line to build OwnDroid.
```shell
@@ -140,6 +154,10 @@ You can use Gradle in command line to build OwnDroid.
```
(Use `./gradlew.bat` instead on Windows)
+### Contribute
+
+Please use the `dev` branch.
+
## License
[License.md](LICENSE.md)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index fa9225d..354a9f6 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -2,7 +2,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.android.application)
- alias(libs.plugins.kotlin.android)
alias(libs.plugins.compose)
alias(libs.plugins.serialization)
}
@@ -26,8 +25,8 @@ android {
applicationId = "com.bintianqi.owndroid"
minSdk = 23
targetSdk = 36
- versionCode = 42
- versionName = "7.3"
+ versionCode = 43
+ versionName = "8.0"
multiDexEnabled = false
}
@@ -63,10 +62,11 @@ android {
dependenciesInfo {
includeInApk = false
}
- composeCompiler {
- includeSourceInformation = false
- includeTraceMarkers = false
- }
+}
+
+composeCompiler {
+ includeSourceInformation = false
+ includeTraceMarkers = false
}
kotlin {
@@ -89,12 +89,14 @@ gradle.taskGraph.whenReady {
dependencies {
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.lifecycle.viewmodel)
+ implementation(libs.androidx.nav3.runtime)
+ implementation(libs.androidx.nav3.ui)
implementation(libs.androidx.compose.ui.tooling.preview)
debugImplementation(libs.androidx.compose.ui.tooling)
implementation(libs.accompanist.drawablepainter)
implementation(libs.accompanist.permissions)
implementation(libs.androidx.material3)
- implementation(libs.androidx.navigation.compose)
implementation(libs.material.icons.core)
implementation(libs.shizuku.provider)
implementation(libs.shizuku.api)
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 48b55e8..b19edd8 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -24,5 +24,3 @@
-dontwarn android.app.ActivityThread
-dontwarn android.app.ContextImpl
-dontwarn android.app.LoadedApk
-
--keep class com.bintianqi.owndroid.MyViewModel { *; }
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e328d50..38c707b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,72 +1,79 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:windowSoftInputMode="adjustResize|stateHidden">
-
-
-
-
+
+
+
+
+
-
+ android:name=".activity.ManageSpaceActivity"
+ android:theme="@style/Theme.Transparent">
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:exported="false"
+ android:permission="android.permission.BIND_DEVICE_ADMIN">
+ android:resource="@xml/device_admin" />
-
-
-
-
-
+
+
+
+
+
-
+ android:exported="true">
-
+ android:exported="true">
diff --git a/app/src/main/java/com/bintianqi/owndroid/ApiReceiver.kt b/app/src/main/java/com/bintianqi/owndroid/ApiReceiver.kt
index eca67d7..a224a5b 100644
--- a/app/src/main/java/com/bintianqi/owndroid/ApiReceiver.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/ApiReceiver.kt
@@ -5,70 +5,95 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
+import com.bintianqi.owndroid.utils.hash
-class ApiReceiver: BroadcastReceiver() {
+class ApiReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val requestKey = intent.getStringExtra("key")
var log = "OwnDroid API request received. action: ${intent.action}"
- val key = SP.apiKeyHash
- if(!key.isNullOrEmpty() && key == requestKey?.hash()) {
+ val myApp = context.applicationContext as MyApplication
+ val key = myApp.container.settingsRepo.data.apiKeyHash
+ if (key.isNotEmpty() && key == requestKey?.hash()) {
val app = intent.getStringExtra("package")
val permission = intent.getStringExtra("permission")
val restriction = intent.getStringExtra("restriction")
if (!app.isNullOrEmpty()) log += "\npackage: $app"
if (!permission.isNullOrEmpty()) log += "\npermission: $permission"
try {
- @SuppressWarnings("NewApi")
- when(intent.action?.removePrefix("com.bintianqi.owndroid.action.")) {
- "HIDE" -> Privilege.DPM.setApplicationHidden(Privilege.DAR, app, true)
- "UNHIDE" -> Privilege.DPM.setApplicationHidden(Privilege.DAR, app, false)
- "SUSPEND" -> Privilege.DPM.setPackagesSuspended(Privilege.DAR, arrayOf(app), true)
- "UNSUSPEND" -> Privilege.DPM.setPackagesSuspended(Privilege.DAR, arrayOf(app), false)
- "ADD_USER_RESTRICTION" -> { Privilege.DPM.addUserRestriction(Privilege.DAR, restriction) }
- "CLEAR_USER_RESTRICTION" -> { Privilege.DPM.clearUserRestriction(Privilege.DAR, restriction) }
- "SET_PERMISSION_DEFAULT" -> {
- Privilege.DPM.setPermissionGrantState(
- Privilege.DAR, app!!, permission!!,
- DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
- )
- }
- "SET_PERMISSION_GRANTED" -> {
- Privilege.DPM.setPermissionGrantState(
- Privilege.DAR, app!!, permission!!,
- DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
- )
- }
- "SET_PERMISSION_DENIED" -> {
- Privilege.DPM.setPermissionGrantState(
- Privilege.DAR, app!!, permission!!,
- DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
- )
- }
- "LOCK" -> { Privilege.DPM.lockNow() }
- "REBOOT" -> { Privilege.DPM.reboot(Privilege.DAR) }
- "SET_CAMERA_DISABLED" -> {
- Privilege.DPM.setCameraDisabled(Privilege.DAR, true)
- }
- "SET_CAMERA_ENABLED" -> {
- Privilege.DPM.setCameraDisabled(Privilege.DAR, false)
- }
- "SET_USB_DISABLED" -> {
- Privilege.DPM.isUsbDataSignalingEnabled = false
- }
- "SET_USB_ENABLED" -> {
- Privilege.DPM.isUsbDataSignalingEnabled = true
- }
- "SET_SCREEN_CAPTURE_DISABLED" -> {
- Privilege.DPM.setScreenCaptureDisabled(Privilege.DAR, true)
- }
- "SET_SCREEN_CAPTURE_ENABLED" -> {
- Privilege.DPM.setScreenCaptureDisabled(Privilege.DAR, false)
- }
- else -> {
- log += "\nInvalid action"
+ myApp.container.privilegeHelper.safeDpmCall {
+ @SuppressWarnings("NewApi")
+ when (intent.action?.removePrefix("com.bintianqi.owndroid.action.")) {
+ "HIDE" -> dpm.setApplicationHidden(dar, app, true)
+ "UNHIDE" -> dpm.setApplicationHidden(dar, app, false)
+ "SUSPEND" -> dpm.setPackagesSuspended(dar, arrayOf(app), true)
+ "UNSUSPEND" -> dpm.setPackagesSuspended(dar, arrayOf(app), false)
+ "ADD_USER_RESTRICTION" -> {
+ dpm.addUserRestriction(dar, restriction)
+ }
+
+ "CLEAR_USER_RESTRICTION" -> {
+ dpm.clearUserRestriction(dar, restriction)
+ }
+
+ "SET_PERMISSION_DEFAULT" -> {
+ dpm.setPermissionGrantState(
+ dar, app!!, permission!!,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
+ )
+ }
+
+ "SET_PERMISSION_GRANTED" -> {
+ dpm.setPermissionGrantState(
+ dar, app!!, permission!!,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+ )
+ }
+
+ "SET_PERMISSION_DENIED" -> {
+ dpm.setPermissionGrantState(
+ dar, app!!, permission!!,
+ DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
+ )
+ }
+
+ "LOCK" -> {
+ dpm.lockNow()
+ }
+
+ "REBOOT" -> {
+ dpm.reboot(dar)
+ }
+
+ "SET_CAMERA_DISABLED" -> {
+ dpm.setCameraDisabled(dar, true)
+ }
+
+ "SET_CAMERA_ENABLED" -> {
+ dpm.setCameraDisabled(dar, false)
+ }
+
+ "SET_USB_DISABLED" -> {
+ dpm.isUsbDataSignalingEnabled = false
+ }
+
+ "SET_USB_ENABLED" -> {
+ dpm.isUsbDataSignalingEnabled = true
+ }
+
+ "SET_SCREEN_CAPTURE_DISABLED" -> {
+ dpm.setScreenCaptureDisabled(dar, true)
+ }
+
+ "SET_SCREEN_CAPTURE_ENABLED" -> {
+ dpm.setScreenCaptureDisabled(dar, false)
+ }
+
+ else -> {
+ log += "\nInvalid action"
+ }
}
}
- } catch(e: Exception) {
+ } catch (e: Exception) {
e.printStackTrace()
val message = (e::class.qualifiedName ?: "Exception") + ": " + (e.message ?: "")
log += "\n$message"
@@ -78,6 +103,7 @@ class ApiReceiver: BroadcastReceiver() {
}
Log.d(TAG, log)
}
+
companion object {
private const val TAG = "API"
}
diff --git a/app/src/main/java/com/bintianqi/owndroid/AppContainer.kt b/app/src/main/java/com/bintianqi/owndroid/AppContainer.kt
new file mode 100644
index 0000000..23da9ce
--- /dev/null
+++ b/app/src/main/java/com/bintianqi/owndroid/AppContainer.kt
@@ -0,0 +1,38 @@
+package com.bintianqi.owndroid
+
+import com.bintianqi.owndroid.feature.applications.AppGroupRepository
+import com.bintianqi.owndroid.feature.network.NetworkLoggingRepository
+import com.bintianqi.owndroid.feature.privilege.DhizukuServerRepository
+import com.bintianqi.owndroid.feature.settings.SettingsRepository
+import com.bintianqi.owndroid.feature.system.SecurityLoggingRepository
+import com.bintianqi.owndroid.feature.work_profile.CrossProfileIntentFilterRepository
+import com.bintianqi.owndroid.utils.DhizukuError
+import com.bintianqi.owndroid.utils.PrivilegeStatus
+import com.bintianqi.owndroid.utils.ToastChannel
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class AppContainer(val app: MyApplication) {
+ val dbHelper = MyDbHelper(app)
+ val networkLoggingRepo = NetworkLoggingRepository(dbHelper)
+ val securityLoggingRepo = SecurityLoggingRepository(dbHelper)
+ val appGroupRepo = AppGroupRepository(dbHelper)
+ val dhizukuServerRepo = DhizukuServerRepository(dbHelper)
+ val cpifRepo = CrossProfileIntentFilterRepository(dbHelper)
+ val settingsRepo = SettingsRepository(app.filesDir.resolve("settings.json"))
+ val dhizukuErrorState = MutableStateFlow(null)
+ val privilegeHelper = PrivilegeHelper(
+ app, settingsRepo.data.privilege.dhizuku, dhizukuErrorState
+ )
+ val privilegeState = MutableStateFlow(PrivilegeStatus())
+ val appGroupsState = MutableStateFlow(appGroupRepo.getAppGroups())
+ val chosenPackage = Channel(1, BufferOverflow.DROP_LATEST)
+ val themeState = MutableStateFlow(settingsRepo.data.theme)
+ val toastChannel = ToastChannel(app)
+ val viewModelFactory = MyViewModelFactory(
+ app, privilegeHelper, settingsRepo, networkLoggingRepo, dhizukuServerRepo,
+ securityLoggingRepo, appGroupRepo, cpifRepo, appGroupsState, dhizukuErrorState,
+ privilegeState, themeState, toastChannel
+ )
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt
index 7fbb400..47161e9 100644
--- a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt
@@ -7,23 +7,27 @@ import androidx.activity.viewModels
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.fragment.app.FragmentActivity
-import com.bintianqi.owndroid.ui.AppInstaller
+import com.bintianqi.owndroid.feature.applications.AppInstaller
+import com.bintianqi.owndroid.feature.applications.AppInstallerViewModel
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
+import com.bintianqi.owndroid.utils.viewModelFactory
-class AppInstallerActivity:FragmentActivity() {
+class AppInstallerActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
- val vm by viewModels()
+ val myApp = application as MyApplication
+ val vm by viewModels {
+ viewModelFactory {
+ AppInstallerViewModel(myApp, myApp.container.settingsRepo)
+ }
+ }
vm.initialize(intent)
- vm.registerInstallerReceiver(this)
- val theme = ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme)
+ val themeState = myApp.container.themeState
setContent {
+ val theme by themeState.collectAsState()
OwnDroidTheme(theme) {
- val uiState by vm.uiState.collectAsState()
- AppInstaller(
- uiState, vm::onPackagesAdd, vm::onPackageRemove, vm::startInstall, vm::closeResultDialog
- )
+ AppInstaller(vm)
}
}
}
diff --git a/app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt b/app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt
index 51bce41..0701c4c 100644
--- a/app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt
@@ -24,7 +24,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.bintianqi.owndroid.feature.privilege.DhizukuClientInfo
+import com.bintianqi.owndroid.ui.screen.AppLockDialog
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
+import com.bintianqi.owndroid.utils.MyAdminComponent
+import com.bintianqi.owndroid.utils.getPackageSignature
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import com.rosan.dhizuku.aidl.IDhizukuClient
import com.rosan.dhizuku.aidl.IDhizukuRequestPermissionListener
@@ -32,21 +36,23 @@ import com.rosan.dhizuku.server_api.DhizukuProvider
import com.rosan.dhizuku.server_api.DhizukuService
import com.rosan.dhizuku.shared.DhizukuVariables
import kotlinx.coroutines.delay
-import kotlinx.serialization.Serializable
private const val TAG = "DhizukuServer"
-class MyDhizukuProvider(): DhizukuProvider() {
+class MyDhizukuProvider : DhizukuProvider() {
override fun onCreateService(client: IDhizukuClient): DhizukuService? {
Log.d(TAG, "Creating MyDhizukuService")
- return if (SP.dhizukuServer) MyDhizukuService(context!!, MyAdminComponent, client) else null
+ val settingsRepo = (context!!.applicationContext as MyApplication).container.settingsRepo
+ return if (settingsRepo.data.privilege.dhizukuServer)
+ MyDhizukuService(context!!, MyAdminComponent, client) else null
}
}
class MyDhizukuService(context: Context, admin: ComponentName, client: IDhizukuClient) :
DhizukuService(context, admin, client) {
override fun checkCallingPermission(func: String?, callingUid: Int, callingPid: Int): Boolean {
- if (!SP.dhizukuServer) return false
+ val settingsRepo = (mContext.applicationContext as MyApplication).container.settingsRepo
+ if (!settingsRepo.data.privilege.dhizukuServer) return false
val pm = mContext.packageManager
val packageInfo = pm.getPackageInfo(
pm.getNameForUid(callingUid) ?: return false,
@@ -59,11 +65,14 @@ class MyDhizukuService(context: Context, admin: ComponentName, client: IDhizukuC
"get_delegated_scopes", "set_delegated_scopes" -> "delegated_scopes"
else -> "other"
}
- val hasPermission = (mContext.applicationContext as MyApplication).myRepo
- .checkDhizukuClientPermission(
- callingUid, signature, requiredPermission
+ val hasPermission = (mContext.applicationContext as MyApplication)
+ .container.dhizukuServerRepo.checkDhizukuClientPermission(
+ callingUid, signature, requiredPermission
+ )
+ Log.d(
+ TAG,
+ "UID $callingUid, PID $callingPid, required permission: $requiredPermission, has permission: $hasPermission"
)
- Log.d(TAG, "UID $callingUid, PID $callingPid, required permission: $requiredPermission, has permission: $hasPermission")
return hasPermission
}
@@ -71,17 +80,20 @@ class MyDhizukuService(context: Context, admin: ComponentName, client: IDhizukuC
}
class DhizukuActivity : ComponentActivity() {
+ val settingsRepo = (application as MyApplication).container.settingsRepo
+
@OptIn(ExperimentalStdlibApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- if (!SP.dhizukuServer) {
+ if (!settingsRepo.data.privilege.dhizukuServer) {
finish()
return
}
val bundle = intent.extras ?: return
val uid = bundle.getInt(DhizukuVariables.PARAM_CLIENT_UID, -1)
if (uid == -1) return
- val binder = bundle.getBinder(DhizukuVariables.PARAM_CLIENT_REQUEST_PERMISSION_BINDER) ?: return
+ val binder =
+ bundle.getBinder(DhizukuVariables.PARAM_CLIENT_REQUEST_PERMISSION_BINDER) ?: return
val listener = IDhizukuRequestPermissionListener.Stub.asInterface(binder)
val packageName = packageManager.getPackagesForUid(uid)?.first() ?: return
val packageInfo = packageManager.getPackageInfo(
@@ -93,19 +105,19 @@ class DhizukuActivity : ComponentActivity() {
val label = appInfo.loadLabel(packageManager).toString()
fun close(grantPermission: Boolean) {
val clientInfo = DhizukuClientInfo(
- uid, getPackageSignature(packageInfo), if (grantPermission) DhizukuPermissions else emptyList()
+ uid, getPackageSignature(packageInfo),
+ if (grantPermission) DhizukuPermissions else emptyList()
)
- (application as MyApplication).myRepo.setDhizukuClient(clientInfo)
+ (application as MyApplication).container.dhizukuServerRepo.setDhizukuClient(clientInfo)
finish()
listener.onRequestPermission(
if (grantPermission) PackageManager.PERMISSION_GRANTED else PackageManager.PERMISSION_DENIED
)
}
enableEdgeToEdge()
- val theme = ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme)
setContent {
var appLockDialog by rememberSaveable { mutableStateOf(false) }
- OwnDroidTheme(theme) {
+ OwnDroidTheme(settingsRepo.data.theme) {
if (!appLockDialog) AlertDialog(
icon = {
Image(rememberDrawablePainter(icon), null, Modifier.size(35.dp))
@@ -125,7 +137,7 @@ class DhizukuActivity : ComponentActivity() {
}
}
TextButton({
- if (SP.lockPasswordHash.isNullOrEmpty()) {
+ if (settingsRepo.data.appLock.passwordHash.isEmpty()) {
close(true)
} else {
appLockDialog = true
@@ -144,17 +156,11 @@ class DhizukuActivity : ComponentActivity() {
},
onDismissRequest = { close(false) }
)
- else AppLockDialog({ close(true) }) { close(false) }
+ else AppLockDialog(settingsRepo.data.appLock, { close(true) }) { close(false) }
}
}
}
}
-val DhizukuPermissions = listOf("remote_transact", "remote_process", "user_service", "delegated_scopes", "other")
-
-@Serializable
-data class DhizukuClientInfo(
- val uid: Int,
- val signature: String?,
- val permissions: List = emptyList()
-)
\ No newline at end of file
+val DhizukuPermissions =
+ listOf("remote_transact", "remote_process", "user_service", "delegated_scopes", "other")
diff --git a/app/src/main/java/com/bintianqi/owndroid/LockTaskService.kt b/app/src/main/java/com/bintianqi/owndroid/LockTaskService.kt
index 301117d..7eddb49 100644
--- a/app/src/main/java/com/bintianqi/owndroid/LockTaskService.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/LockTaskService.kt
@@ -7,13 +7,12 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
-import android.content.pm.ServiceInfo
-import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
-import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
+import com.bintianqi.owndroid.utils.MyNotificationChannel
+import com.bintianqi.owndroid.utils.NotificationType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
@@ -50,10 +49,7 @@ class LockTaskService: Service() {
.setOngoing(true)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.build()
- ServiceCompat.startForeground(
- this, NotificationType.LockTaskMode.id, notification,
- if (Build.VERSION.SDK_INT < 34) 0 else ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
- )
+ startForeground(NotificationType.LockTaskMode.id, notification)
coroutineScope.launch {
val am = getSystemService(ActivityManager::class.java)
delay(3000)
@@ -71,14 +67,17 @@ class LockTaskService: Service() {
}
fun stopLockTask() {
- val features = Privilege.DPM.getLockTaskFeatures(Privilege.DAR)
- val packages = Privilege.DPM.getLockTaskPackages(Privilege.DAR)
- Privilege.DPM.setLockTaskPackages(Privilege.DAR, arrayOf())
- Privilege.DPM.setLockTaskPackages(Privilege.DAR, packages)
- Privilege.DPM.setLockTaskFeatures(Privilege.DAR, features)
+ val ph = (application as MyApplication).container.privilegeHelper
+ ph.safeDpmCall {
+ val features = dpm.getLockTaskFeatures(dar)
+ val packages = dpm.getLockTaskPackages(dar)
+ dpm.setLockTaskPackages(dar, arrayOf())
+ dpm.setLockTaskPackages(dar, packages)
+ dpm.setLockTaskFeatures(dar, features)
+ }
}
companion object {
const val STOP_ACTION = "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE"
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt
index 5aedebe..c890352 100644
--- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt
@@ -9,30 +9,13 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.LargeTopAppBar
-import androidx.compose.material3.MaterialTheme.colorScheme
-import androidx.compose.material3.MaterialTheme.typography
-import androidx.compose.material3.Scaffold
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
-import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -41,823 +24,165 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.input.nestedscroll.nestedScroll
-import androidx.compose.ui.input.pointer.pointerInput
-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.unit.dp
import androidx.compose.ui.window.DialogProperties
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-import androidx.navigation.toRoute
-import com.bintianqi.owndroid.dpm.AddApnSetting
-import com.bintianqi.owndroid.dpm.AddApnSettingScreen
-import com.bintianqi.owndroid.dpm.AddDelegatedAdmin
-import com.bintianqi.owndroid.dpm.AddDelegatedAdminScreen
-import com.bintianqi.owndroid.dpm.AddPreferentialNetworkServiceConfig
-import com.bintianqi.owndroid.dpm.AddPreferentialNetworkServiceConfigScreen
-import com.bintianqi.owndroid.dpm.AffiliationId
-import com.bintianqi.owndroid.dpm.AffiliationIdScreen
-import com.bintianqi.owndroid.dpm.AlwaysOnVpnPackage
-import com.bintianqi.owndroid.dpm.AlwaysOnVpnPackageScreen
-import com.bintianqi.owndroid.dpm.ApplicationDetails
-import com.bintianqi.owndroid.dpm.ApplicationDetailsScreen
-import com.bintianqi.owndroid.dpm.ApplicationsFeatures
-import com.bintianqi.owndroid.dpm.ApplicationsFeaturesScreen
-import com.bintianqi.owndroid.dpm.AutoTimePolicy
-import com.bintianqi.owndroid.dpm.AutoTimePolicyScreen
-import com.bintianqi.owndroid.dpm.AutoTimeZonePolicy
-import com.bintianqi.owndroid.dpm.AutoTimeZonePolicyScreen
-import com.bintianqi.owndroid.dpm.BlockUninstall
-import com.bintianqi.owndroid.dpm.CaCert
-import com.bintianqi.owndroid.dpm.CaCertScreen
-import com.bintianqi.owndroid.dpm.ChangeTime
-import com.bintianqi.owndroid.dpm.ChangeTimeScreen
-import com.bintianqi.owndroid.dpm.ChangeTimeZone
-import com.bintianqi.owndroid.dpm.ChangeTimeZoneScreen
-import com.bintianqi.owndroid.dpm.ChangeUsername
-import com.bintianqi.owndroid.dpm.ChangeUsernameScreen
-import com.bintianqi.owndroid.dpm.ClearAppStorage
-import com.bintianqi.owndroid.dpm.ClearAppStorageScreen
-import com.bintianqi.owndroid.dpm.ContentProtectionPolicy
-import com.bintianqi.owndroid.dpm.ContentProtectionPolicyScreen
-import com.bintianqi.owndroid.dpm.CreateUser
-import com.bintianqi.owndroid.dpm.CreateUserScreen
-import com.bintianqi.owndroid.dpm.CreateWorkProfile
-import com.bintianqi.owndroid.dpm.CreateWorkProfileScreen
-import com.bintianqi.owndroid.dpm.CredentialManagerPolicy
-import com.bintianqi.owndroid.dpm.CredentialManagerPolicyScreen
-import com.bintianqi.owndroid.dpm.CrossProfileIntentFilter
-import com.bintianqi.owndroid.dpm.CrossProfileIntentFilterScreen
-import com.bintianqi.owndroid.dpm.CrossProfilePackages
-import com.bintianqi.owndroid.dpm.CrossProfileWidgetProviders
-import com.bintianqi.owndroid.dpm.DelegatedAdmins
-import com.bintianqi.owndroid.dpm.DelegatedAdminsScreen
-import com.bintianqi.owndroid.dpm.DeleteWorkProfile
-import com.bintianqi.owndroid.dpm.DeleteWorkProfileScreen
-import com.bintianqi.owndroid.dpm.DeviceInfo
-import com.bintianqi.owndroid.dpm.DeviceInfoScreen
-import com.bintianqi.owndroid.dpm.DhizukuServerSettings
-import com.bintianqi.owndroid.dpm.DhizukuServerSettingsScreen
-import com.bintianqi.owndroid.dpm.DisableAccountManagement
-import com.bintianqi.owndroid.dpm.DisableAccountManagementScreen
-import com.bintianqi.owndroid.dpm.DisableMeteredData
-import com.bintianqi.owndroid.dpm.DisableUserControl
-import com.bintianqi.owndroid.dpm.EditAppGroup
-import com.bintianqi.owndroid.dpm.EditAppGroupScreen
-import com.bintianqi.owndroid.dpm.EnableSystemApp
-import com.bintianqi.owndroid.dpm.EnableSystemAppScreen
-import com.bintianqi.owndroid.dpm.FrpPolicy
-import com.bintianqi.owndroid.dpm.FrpPolicyScreen
-import com.bintianqi.owndroid.dpm.HardwareMonitor
-import com.bintianqi.owndroid.dpm.HardwareMonitorScreen
-import com.bintianqi.owndroid.dpm.Hide
-import com.bintianqi.owndroid.dpm.InstallExistingApp
-import com.bintianqi.owndroid.dpm.InstallExistingAppScreen
-import com.bintianqi.owndroid.dpm.InstallSystemUpdate
-import com.bintianqi.owndroid.dpm.InstallSystemUpdateScreen
-import com.bintianqi.owndroid.dpm.KeepUninstalledPackages
-import com.bintianqi.owndroid.dpm.Keyguard
-import com.bintianqi.owndroid.dpm.KeyguardDisabledFeatures
-import com.bintianqi.owndroid.dpm.KeyguardDisabledFeaturesScreen
-import com.bintianqi.owndroid.dpm.KeyguardScreen
-import com.bintianqi.owndroid.dpm.LockScreenInfo
-import com.bintianqi.owndroid.dpm.LockScreenInfoScreen
-import com.bintianqi.owndroid.dpm.LockTaskMode
-import com.bintianqi.owndroid.dpm.LockTaskModeScreen
-import com.bintianqi.owndroid.dpm.ManageAppGroups
-import com.bintianqi.owndroid.dpm.ManageAppGroupsScreen
-import com.bintianqi.owndroid.dpm.ManagedConfiguration
-import com.bintianqi.owndroid.dpm.ManagedConfigurationScreen
-import com.bintianqi.owndroid.dpm.MtePolicy
-import com.bintianqi.owndroid.dpm.MtePolicyScreen
-import com.bintianqi.owndroid.dpm.NearbyStreamingPolicy
-import com.bintianqi.owndroid.dpm.NearbyStreamingPolicyScreen
-import com.bintianqi.owndroid.dpm.Network
-import com.bintianqi.owndroid.dpm.NetworkLogging
-import com.bintianqi.owndroid.dpm.NetworkLoggingScreen
-import com.bintianqi.owndroid.dpm.NetworkOptions
-import com.bintianqi.owndroid.dpm.NetworkOptionsScreen
-import com.bintianqi.owndroid.dpm.NetworkScreen
-import com.bintianqi.owndroid.dpm.NetworkStatsScreen
-import com.bintianqi.owndroid.dpm.NetworkStatsViewer
-import com.bintianqi.owndroid.dpm.NetworkStatsViewerScreen
-import com.bintianqi.owndroid.dpm.OrganizationOwnedProfile
-import com.bintianqi.owndroid.dpm.OrganizationOwnedProfileScreen
-import com.bintianqi.owndroid.dpm.OverrideApn
-import com.bintianqi.owndroid.dpm.OverrideApnScreen
-import com.bintianqi.owndroid.dpm.PackageFunctionScreen
-import com.bintianqi.owndroid.dpm.Password
-import com.bintianqi.owndroid.dpm.PasswordInfo
-import com.bintianqi.owndroid.dpm.PasswordInfoScreen
-import com.bintianqi.owndroid.dpm.PasswordScreen
-import com.bintianqi.owndroid.dpm.PermissionPolicy
-import com.bintianqi.owndroid.dpm.PermissionPolicyScreen
-import com.bintianqi.owndroid.dpm.PermissionsManager
-import com.bintianqi.owndroid.dpm.PermissionsManagerScreen
-import com.bintianqi.owndroid.dpm.PermittedAccessibilityServices
-import com.bintianqi.owndroid.dpm.PermittedAsAndImPackages
-import com.bintianqi.owndroid.dpm.PermittedInputMethods
-import com.bintianqi.owndroid.dpm.PreferentialNetworkService
-import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceInfo
-import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceScreen
-import com.bintianqi.owndroid.dpm.PrivateDns
-import com.bintianqi.owndroid.dpm.PrivateDnsScreen
-import com.bintianqi.owndroid.dpm.QueryNetworkStats
-import com.bintianqi.owndroid.dpm.RecommendedGlobalProxy
-import com.bintianqi.owndroid.dpm.RecommendedGlobalProxyScreen
-import com.bintianqi.owndroid.dpm.RequiredPasswordComplexity
-import com.bintianqi.owndroid.dpm.RequiredPasswordComplexityScreen
-import com.bintianqi.owndroid.dpm.RequiredPasswordQuality
-import com.bintianqi.owndroid.dpm.RequiredPasswordQualityScreen
-import com.bintianqi.owndroid.dpm.ResetPassword
-import com.bintianqi.owndroid.dpm.ResetPasswordScreen
-import com.bintianqi.owndroid.dpm.ResetPasswordToken
-import com.bintianqi.owndroid.dpm.ResetPasswordTokenScreen
-import com.bintianqi.owndroid.dpm.SecurityLogging
-import com.bintianqi.owndroid.dpm.SecurityLoggingScreen
-import com.bintianqi.owndroid.dpm.SetDefaultDialer
-import com.bintianqi.owndroid.dpm.SetDefaultDialerScreen
-import com.bintianqi.owndroid.dpm.SetSystemUpdatePolicy
-import com.bintianqi.owndroid.dpm.SupportMessage
-import com.bintianqi.owndroid.dpm.SupportMessageScreen
-import com.bintianqi.owndroid.dpm.Suspend
-import com.bintianqi.owndroid.dpm.SuspendPersonalApp
-import com.bintianqi.owndroid.dpm.SuspendPersonalAppScreen
-import com.bintianqi.owndroid.dpm.SystemManager
-import com.bintianqi.owndroid.dpm.SystemManagerScreen
-import com.bintianqi.owndroid.dpm.SystemOptions
-import com.bintianqi.owndroid.dpm.SystemOptionsScreen
-import com.bintianqi.owndroid.dpm.SystemUpdatePolicyScreen
-import com.bintianqi.owndroid.dpm.TransferOwnership
-import com.bintianqi.owndroid.dpm.TransferOwnershipScreen
-import com.bintianqi.owndroid.dpm.UninstallApp
-import com.bintianqi.owndroid.dpm.UninstallAppScreen
-import com.bintianqi.owndroid.dpm.UpdateNetwork
-import com.bintianqi.owndroid.dpm.UpdateNetworkScreen
-import com.bintianqi.owndroid.dpm.UserInfo
-import com.bintianqi.owndroid.dpm.UserInfoScreen
-import com.bintianqi.owndroid.dpm.UserOperation
-import com.bintianqi.owndroid.dpm.UserOperationScreen
-import com.bintianqi.owndroid.dpm.UserRestriction
-import com.bintianqi.owndroid.dpm.UserRestrictionEditor
-import com.bintianqi.owndroid.dpm.UserRestrictionEditorScreen
-import com.bintianqi.owndroid.dpm.UserRestrictionOptions
-import com.bintianqi.owndroid.dpm.UserRestrictionOptionsScreen
-import com.bintianqi.owndroid.dpm.UserRestrictionScreen
-import com.bintianqi.owndroid.dpm.UserSessionMessage
-import com.bintianqi.owndroid.dpm.UserSessionMessageScreen
-import com.bintianqi.owndroid.dpm.Users
-import com.bintianqi.owndroid.dpm.UsersOptions
-import com.bintianqi.owndroid.dpm.UsersOptionsScreen
-import com.bintianqi.owndroid.dpm.UsersScreen
-import com.bintianqi.owndroid.dpm.WiFi
-import com.bintianqi.owndroid.dpm.WifiScreen
-import com.bintianqi.owndroid.dpm.WifiSecurityLevel
-import com.bintianqi.owndroid.dpm.WifiSecurityLevelScreen
-import com.bintianqi.owndroid.dpm.WifiSsidPolicyScreen
-import com.bintianqi.owndroid.dpm.WipeData
-import com.bintianqi.owndroid.dpm.WipeDataScreen
-import com.bintianqi.owndroid.dpm.WorkModes
-import com.bintianqi.owndroid.dpm.WorkModesScreen
-import com.bintianqi.owndroid.dpm.WorkProfile
-import com.bintianqi.owndroid.dpm.WorkProfileScreen
-import com.bintianqi.owndroid.dpm.dhizukuErrorStatus
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation3.runtime.rememberNavBackStack
+import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
+import androidx.navigation3.ui.NavDisplay
+import com.bintianqi.owndroid.feature.applications.AppChooserViewModel
import com.bintianqi.owndroid.ui.NavTransition
+import com.bintianqi.owndroid.ui.navigation.Destination
+import com.bintianqi.owndroid.ui.navigation.myEntryProvider
+import com.bintianqi.owndroid.ui.navigation.rememberSharedViewModelStoreNavEntryDecorator
+import com.bintianqi.owndroid.ui.screen.AppLockDialog
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
-import kotlinx.serialization.Serializable
-import java.util.Locale
+import com.bintianqi.owndroid.utils.DhizukuError
+import com.bintianqi.owndroid.utils.popToast
+import com.bintianqi.owndroid.utils.registerPackageRemovedReceiver
+import com.bintianqi.owndroid.utils.viewModelFactory
+import kotlinx.coroutines.launch
@ExperimentalMaterial3Api
class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
- val context = applicationContext
- val locale = context.resources?.configuration?.locale
- zhCN = locale == Locale.SIMPLIFIED_CHINESE || locale == Locale.CHINESE || locale == Locale.CHINA
- val vm by viewModels()
+ val context = this
+ val myApp = (application as MyApplication)
+ val settingsRepo = myApp.container.settingsRepo
if (
VERSION.SDK_INT >= 33 &&
- checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
+ checkSelfPermission(
+ Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED
) {
val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
+ val appChooserVm: AppChooserViewModel by viewModels(
+ factoryProducer = {
+ viewModelFactory { AppChooserViewModel(myApp) }
+ }
+ )
registerPackageRemovedReceiver(this) {
- vm.onPackageRemoved(it)
+ appChooserVm.onPackageRemoved(it)
}
- setContent {
- var appLockDialog by rememberSaveable { mutableStateOf(false) }
- val theme by vm.theme.collectAsStateWithLifecycle()
- OwnDroidTheme(theme) {
- Home(vm) { appLockDialog = true }
- if (appLockDialog) {
- AppLockDialog({ appLockDialog = false }) { moveTaskToBack(true) }
- }
+ if (
+ myApp.container.privilegeState.value.work &&
+ !settingsRepo.data.privilege.managedProfileActivated
+ ) {
+ myApp.container.privilegeHelper.dpm.setProfileEnabled(
+ myApp.container.privilegeHelper.dar
+ )
+ settingsRepo.update {
+ it.privilege.managedProfileActivated = true
}
- }
- }
-
-}
-
-@ExperimentalMaterial3Api
-@Composable
-fun Home(vm: MyViewModel, onLock: () -> Unit) {
- val navController = rememberNavController()
- val context = LocalContext.current
- val focusMgr = LocalFocusManager.current
- val lifecycleOwner = LocalLifecycleOwner.current
- fun navigateUp() { navController.navigateUp() }
- fun navigate(destination: Any) {
- navController.navigate(destination) {
- launchSingleTop = true
- }
- }
- fun choosePackage() {
- navController.navigate(ApplicationsList(false, true))
- }
- fun chooseSinglePackage() {
- navController.navigate(ApplicationsList(false, false))
- }
- fun navigateToAppGroups() {
- navController.navigate(ManageAppGroups)
- }
- LaunchedEffect(Unit) {
- if(!Privilege.status.value.activated) {
- navController.navigate(WorkModes(false)) {
- popUpTo { inclusive = true }
- }
- }
- }
- @Suppress("NewApi") NavHost(
- navController = navController,
- startDestination = Home,
- modifier = Modifier
- .fillMaxSize()
- .background(colorScheme.background)
- .pointerInput(Unit) { detectTapGestures(onTap = { focusMgr.clearFocus() }) },
- enterTransition = { NavTransition.enterTransition },
- exitTransition = { NavTransition.exitTransition },
- popEnterTransition = { NavTransition.popEnterTransition },
- popExitTransition = { NavTransition.popExitTransition }
- ) {
- composable { HomeScreen(::navigate) }
- composable {
- WorkModesScreen(vm, it.toRoute(), ::navigateUp, {
- navController.navigate(Home) {
- popUpTo { inclusive = true }
- }
- }, {
- navController.navigate(WorkModes(false)) {
- popUpTo(Home) { inclusive = true }
- }
- }, ::navigate)
- }
- composable {
- DhizukuServerSettingsScreen(vm.dhizukuClients, vm::getDhizukuClients,
- vm::updateDhizukuClient, vm::getDhizukuServerEnabled, vm::setDhizukuServerEnabled,
- ::navigateUp)
- }
-
- composable {
- DelegatedAdminsScreen(vm.delegatedAdmins, vm::getDelegatedAdmins, ::navigateUp, ::navigate)
- }
- composable{
- AddDelegatedAdminScreen(vm.chosenPackage, ::chooseSinglePackage, it.toRoute(),
- vm::setDelegatedAdmin, ::navigateUp)
- }
- composable { DeviceInfoScreen(vm, ::navigateUp) }
- composable {
- LockScreenInfoScreen(vm::getLockScreenInfo, vm::setLockScreenInfo, ::navigateUp)
- }
- composable {
- SupportMessageScreen(vm::getShortSupportMessage, vm::getLongSupportMessage,
- vm::setShortSupportMessage, vm::setLongSupportMessage, ::navigateUp)
- }
- composable {
- TransferOwnershipScreen(vm.deviceAdminReceivers, vm::getDeviceAdminReceivers,
- vm::transferOwnership, ::navigateUp) {
- navController.navigate(WorkModes(false)) {
- popUpTo(Home) { inclusive = true }
- }
- }
- }
-
- composable { SystemManagerScreen(vm, ::navigateUp, ::navigate) }
- composable { SystemOptionsScreen(vm, ::navigateUp) }
- composable {
- KeyguardScreen(vm::setKeyguardDisabled, vm::lockScreen, ::navigateUp)
- }
- composable {
- HardwareMonitorScreen(vm.hardwareProperties, vm::getHardwareProperties,
- vm::setHpRefreshInterval, ::navigateUp)
- }
- composable { ChangeTimeScreen(vm::setTime, ::navigateUp) }
- composable { ChangeTimeZoneScreen(vm::setTimeZone, ::navigateUp) }
- composable {
- AutoTimePolicyScreen(vm::getAutoTimePolicy, vm::setAutoTimePolicy, ::navigateUp)
- }
- composable {
- AutoTimeZonePolicyScreen(vm::getAutoTimeZonePolicy, vm::setAutoTimeZonePolicy,
- ::navigateUp)
- }
- //composable<> { KeyPairs(::navigateUp) }
- composable {
- ContentProtectionPolicyScreen(vm::getContentProtectionPolicy,
- vm::setContentProtectionPolicy, ::navigateUp)
- }
- composable {
- PermissionPolicyScreen(vm::getPermissionPolicy, vm::setPermissionPolicy, ::navigateUp)
- }
- composable {
- MtePolicyScreen(vm::getMtePolicy, vm::setMtePolicy, ::navigateUp)
- }
- composable {
- NearbyStreamingPolicyScreen(vm::getNsAppPolicy, vm::setNsAppPolicy,
- vm::getNsNotificationPolicy, vm::setNsNotificationPolicy, ::navigateUp)
- }
- composable {
- LockTaskModeScreen(
- vm.chosenPackage, ::chooseSinglePackage, ::choosePackage, vm.lockTaskPackages,
- vm::getLockTaskPackages, vm::setLockTaskPackage, vm::startLockTaskMode,
- vm:: getLockTaskFeatures, vm::setLockTaskFeatures, ::navigateUp
- )
- }
- composable {
- CaCertScreen(vm.installedCaCerts, vm::getCaCerts, vm.selectedCaCert, vm::selectCaCert, vm::installCaCert, vm::parseCaCert,
- vm::exportCaCert, vm::uninstallCaCert, vm::uninstallAllCaCerts, ::navigateUp)
- }
- composable {
- SecurityLoggingScreen(vm::getSecurityLoggingEnabled, vm::setSecurityLoggingEnabled,
- vm::exportSecurityLogs, vm::getSecurityLogsCount, vm::deleteSecurityLogs,
- vm::getPreRebootSecurityLogs, vm::exportPreRebootSecurityLogs, ::navigateUp)
- }
- composable {
- DisableAccountManagementScreen(vm.mdAccountTypes, vm::getMdAccountTypes,
- vm::setMdAccountType, ::navigateUp)
- }
- composable {
- SystemUpdatePolicyScreen(vm::getSystemUpdatePolicy, vm::setSystemUpdatePolicy,
- vm::getPendingSystemUpdate, ::navigateUp)
- }
- composable {
- InstallSystemUpdateScreen(vm::installSystemUpdate, ::navigateUp)
- }
- composable {
- FrpPolicyScreen(vm.getFrpPolicy(), vm::setFrpPolicy, ::navigateUp)
- }
- composable { WipeDataScreen(vm::wipeData, ::navigateUp) }
-
- composable { NetworkScreen(::navigateUp, ::navigate) }
- composable {
- WifiScreen(vm, ::navigateUp, ::navigate) { navController.navigate(UpdateNetwork(it)) }
- }
- composable {
- NetworkOptionsScreen(vm::getLanEnabled, vm::setLanEnabled, ::navigateUp)
- }
- composable {
- val info = vm.configuredNetworks.collectAsStateWithLifecycle().value[
- (it.toRoute() as UpdateNetwork).index
- ]
- UpdateNetworkScreen(info, vm::setWifi, ::navigateUp)
- }
- composable {
- WifiSecurityLevelScreen(vm::getMinimumWifiSecurityLevel,
- vm::setMinimumWifiSecurityLevel, ::navigateUp)
- }
- composable {
- WifiSsidPolicyScreen(vm::getSsidPolicy, vm::setSsidPolicy, ::navigateUp)
- }
- composable {
- NetworkStatsScreen(vm.chosenPackage, ::chooseSinglePackage, vm::getPackageUid,
- vm::queryNetworkStats, ::navigateUp) { navController.navigate(NetworkStatsViewer) }
- }
- composable {
- NetworkStatsViewerScreen(vm.networkStatsData, vm::clearNetworkStats, ::navigateUp)
- }
- composable {
- PrivateDnsScreen(vm::getPrivateDns, vm::setPrivateDns, ::navigateUp)
- }
- composable {
- AlwaysOnVpnPackageScreen(vm::getAlwaysOnVpnPackage, vm::getAlwaysOnVpnLockdown,
- vm::setAlwaysOnVpn, vm.chosenPackage, ::chooseSinglePackage, ::navigateUp)
- }
- composable {
- RecommendedGlobalProxyScreen(vm::setRecommendedGlobalProxy, ::navigateUp)
- }
- composable {
- NetworkLoggingScreen(vm::getNetworkLoggingEnabled, vm::setNetworkLoggingEnabled,
- vm::getNetworkLogsCount, vm::exportNetworkLogs, vm::deleteNetworkLogs, ::navigateUp)
- }
- //composable { WifiAuthKeypairScreen(::navigateUp) }
- composable {
- PreferentialNetworkServiceScreen(vm::getPnsEnabled, vm::setPnsEnabled, vm.pnsConfigs,
- vm::getPnsConfigs, ::navigateUp, ::navigate)
- }
- composable {
- val info = vm.pnsConfigs.collectAsStateWithLifecycle().value.getOrNull(
- it.toRoute().index
- ) ?: PreferentialNetworkServiceInfo()
- AddPreferentialNetworkServiceConfigScreen(info, vm::setPnsConfig, ::navigateUp)
- }
- composable {
- OverrideApnScreen(vm.apnConfigs, vm::getApnConfigs, vm::getApnEnabled,
- vm::setApnEnabled, ::navigateUp) { navController.navigate(AddApnSetting(it)) }
- }
- composable {
- val origin = vm.apnConfigs.collectAsStateWithLifecycle().value.getOrNull((it.toRoute() as AddApnSetting).index)
- AddApnSettingScreen(vm::setApnConfig, vm::removeApnConfig, origin, ::navigateUp)
- }
-
- composable { WorkProfileScreen(::navigateUp, ::navigate) }
- composable {
- OrganizationOwnedProfileScreen(vm::activateOrgProfileByShizuku, ::navigateUp)
- }
- composable {
- CreateWorkProfileScreen(vm::createWorkProfile, ::navigateUp)
- }
- composable {
- SuspendPersonalAppScreen(
- vm::getPersonalAppsSuspendedReason, vm::setPersonalAppsSuspended,
- vm::getProfileMaxTimeOff, vm::setProfileMaxTimeOff, ::navigateUp
- )
- }
- composable {
- CrossProfileIntentFilterScreen(vm::addCrossProfileIntentFilter, ::navigateUp)
- }
- composable { DeleteWorkProfileScreen(vm::wipeData, ::navigateUp) }
-
- composable {
- val params = it.toRoute()
- AppChooserScreen(
- params, vm.installedPackages, vm.refreshPackagesProgress, { name ->
- if (params.canSwitchView) {
- if (name == null) {
- navigateUp()
- } else {
- navigate(ApplicationDetails(name))
- }
- } else {
- if (name != null) vm.chosenPackage.trySend(name)
- navigateUp()
- }
- }, {
- SP.applicationsListView = false
- navController.navigate(ApplicationsFeatures) {
- popUpTo(Home)
- }
- }, vm::refreshPackageList, vm::setPackageSuspended, vm::setPackageHidden)
- }
- composable {
- ApplicationsFeaturesScreen(::navigateUp, ::navigate) {
- SP.applicationsListView = true
- navController.navigate(ApplicationsList(true, true)) {
- popUpTo(Home)
- }
- }
- }
- composable {
- ApplicationDetailsScreen(it.toRoute(), vm, ::navigateUp, ::navigate)
- }
- composable {
- PackageFunctionScreen(
- R.string.suspend, vm.suspendedPackages, vm::getSuspendedPackaged,
- vm::setPackageSuspended, ::navigateUp, vm.chosenPackage, ::choosePackage,
- ::navigateToAppGroups, vm.appGroups, R.string.info_suspend_app
- )
- }
- composable {
- PackageFunctionScreen(
- R.string.hide, vm.hiddenPackages, vm::getHiddenPackages, vm::setPackageHidden,
- ::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups
- )
- }
- composable {
- PackageFunctionScreen(
- R.string.block_uninstall, vm.ubPackages, vm::getUbPackages, vm::setPackageUb,
- ::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups
- )
- }
- composable {
- PackageFunctionScreen(
- R.string.disable_user_control, vm.ucdPackages, vm::getUcdPackages,
- vm::setPackageUcd, ::navigateUp, vm.chosenPackage, ::choosePackage,
- ::navigateToAppGroups, vm.appGroups, R.string.info_disable_user_control
- )
- }
- composable {
- PermissionsManagerScreen(
- vm.packagePermissions, vm::getPackagePermissions, vm::setPackagePermission,
- ::navigateUp, it.toRoute(), vm.chosenPackage, ::chooseSinglePackage
- )
- }
- composable {
- PackageFunctionScreen(
- R.string.disable_metered_data, vm.mddPackages, vm::getMddPackages,
- vm::setPackageMdd, ::navigateUp, vm.chosenPackage, ::choosePackage,
- ::navigateToAppGroups, vm.appGroups
- )
- }
- composable {
- ClearAppStorageScreen(
- vm.chosenPackage, ::chooseSinglePackage, vm::clearAppData, ::navigateUp
- )
- }
- composable {
- UninstallAppScreen(
- vm.chosenPackage, ::chooseSinglePackage, vm::uninstallPackage, ::navigateUp
- )
- }
- composable {
- PackageFunctionScreen(
- R.string.keep_uninstalled_packages, vm.kuPackages, vm::getKuPackages,
- vm::setPackageKu, ::navigateUp, vm.chosenPackage, ::choosePackage,
- ::navigateToAppGroups, vm.appGroups, R.string.info_keep_uninstalled_apps
- )
- }
- composable {
- InstallExistingAppScreen(
- vm.chosenPackage, ::chooseSinglePackage, vm::installExistingApp, ::navigateUp
- )
- }
- composable {
- PackageFunctionScreen(
- R.string.cross_profile_apps, vm.cpPackages,
- vm::getCpPackages, vm::setPackageCp, ::navigateUp, vm.chosenPackage,
- ::choosePackage, ::navigateToAppGroups, vm.appGroups
- )
- }
- composable {
- PackageFunctionScreen(R.string.cross_profile_widget, vm.cpwProviders,
- vm::getCpwProviders, vm::setCpwProvider, ::navigateUp, vm.chosenPackage,
- ::choosePackage, ::navigateToAppGroups, vm.appGroups)
- }
- composable {
- CredentialManagerPolicyScreen(
- vm.chosenPackage, ::choosePackage, vm.cmPackages, vm::getCmPolicy,
- vm::setCmPackage, vm::setCmPolicy, ::navigateUp
- )
- }
- composable {
- PermittedAsAndImPackages(
- R.string.permitted_accessibility_services,
- R.string.system_accessibility_always_allowed, vm.chosenPackage, ::choosePackage,
- vm.pasPackages, vm::getPasPackages, vm::setPasPackage, vm::setPasPolicy,
- ::navigateUp
- )
- }
- composable {
- PermittedAsAndImPackages(
- R.string.permitted_ime, R.string.system_ime_always_allowed,
- vm.chosenPackage, ::choosePackage, vm.pimPackages, vm::getPimPackages,
- vm::setPimPackage, vm::setPimPolicy, ::navigateUp
- )
- }
- composable {
- EnableSystemAppScreen(
- vm.chosenPackage, ::chooseSinglePackage, vm::enableSystemApp, ::navigateUp
- )
- }
- composable {
- SetDefaultDialerScreen(
- vm.chosenPackage, ::chooseSinglePackage, vm::setDefaultDialer, ::navigateUp
- )
- }
- composable {
- ManagedConfigurationScreen(
- it.toRoute(), vm.appRestrictions, vm::setAppRestrictions,
- vm::clearAppRestrictions, ::navigateUp
- )
- }
- composable {
- ManageAppGroupsScreen(
- vm.appGroups, vm::exportAppGroups, vm::importAppGroups,
- { id, name, apps -> navController.navigate(EditAppGroup(id, name, apps)) },
- ::navigateUp
- )
- }
- composable {
- EditAppGroupScreen(
- it.toRoute(), vm::getAppInfo, ::navigateUp, vm::setAppGroup,
- vm::deleteAppGroup, ::choosePackage, vm.chosenPackage
- )
- }
-
- composable {
- UserRestrictionScreen(vm::getUserRestrictions, ::navigateUp, ::navigate)
- }
- composable {
- UserRestrictionEditorScreen(vm.userRestrictions, vm::setUserRestriction, ::navigateUp)
- }
- composable {
- UserRestrictionOptionsScreen(it.toRoute(), vm.userRestrictions,
- vm::setUserRestriction, vm::createUserRestrictionShortcut, ::navigateUp)
- }
-
- composable { UsersScreen(vm, ::navigateUp, ::navigate) }
- composable { UserInfoScreen(vm::getUserInformation, ::navigateUp) }
- composable {
- UsersOptionsScreen(vm::getLogoutEnabled, vm::setLogoutEnabled, ::navigateUp)
- }
- composable {
- UserOperationScreen(vm::getUserIdentifiers, vm::doUserOperation,
- vm::createUserOperationShortcut, ::navigateUp)
- }
- composable { CreateUserScreen(vm::createUser, ::navigateUp) }
- composable { ChangeUsernameScreen(vm::setProfileName, ::navigateUp) }
- composable {
- UserSessionMessageScreen(vm::getUserSessionMessages, vm::setStartUserSessionMessage,
- vm::setEndUserSessionMessage, ::navigateUp)
- }
- composable {
- AffiliationIdScreen(vm.affiliationIds, vm::getAffiliationIds, vm::setAffiliationId,
- ::navigateUp)
- }
-
- composable { PasswordScreen(vm, ::navigateUp, ::navigate) }
- composable {
- PasswordInfoScreen(vm::getPasswordComplexity, vm::isPasswordComplexitySufficient,
- vm::isUsingUnifiedPassword, ::navigateUp)
- }
- composable {
- ResetPasswordTokenScreen(vm::getRpTokenState, vm::setRpToken,
- vm::createActivateRpTokenIntent, vm::clearRpToken, ::navigateUp)
- }
- composable { ResetPasswordScreen(vm::resetPassword, ::navigateUp) }
- composable {
- RequiredPasswordComplexityScreen(vm::getRequiredPasswordComplexity,
- vm::setRequiredPasswordComplexity, ::navigateUp)
- }
- composable {
- KeyguardDisabledFeaturesScreen(vm::getKeyguardDisableConfig,
- vm::setKeyguardDisableConfig, ::navigateUp)
- }
- composable { RequiredPasswordQualityScreen(::navigateUp) }
-
- composable { SettingsScreen(::navigateUp, ::navigate) }
- composable {
- SettingsOptionsScreen(vm::getDisplayDangerousFeatures, vm::getShortcutsEnabled,
- vm::setDisplayDangerousFeatures, vm::setShortcutsEnabled, ::navigateUp)
- }
- composable {
- AppearanceScreen(::navigateUp, vm.theme, vm::changeTheme)
- }
- composable {
- AppLockSettingsScreen(vm.getAppLockConfig(), vm::setAppLockConfig, ::navigateUp)
- }
- composable {
- ApiSettings(vm::getApiEnabled, vm::setApiKey, ::navigateUp)
- }
- composable {
- NotificationsScreen(vm.enabledNotifications, vm::getEnabledNotifications,
- vm::setNotificationEnabled, ::navigateUp)
- }
- composable { AboutScreen(::navigateUp) }
- }
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (
- (event == Lifecycle.Event.ON_CREATE && !SP.lockPasswordHash.isNullOrEmpty()) ||
- (event == Lifecycle.Event.ON_RESUME && SP.lockWhenLeaving)
- ) {
- onLock()
- }
- }
- lifecycleOwner.lifecycle.addObserver(observer)
- onDispose {
- lifecycleOwner.lifecycle.removeObserver(observer)
- }
- }
- LaunchedEffect(Unit) {
- val profileNotActivated = !SP.managedProfileActivated && Privilege.status.value.work
- if(profileNotActivated) {
- Privilege.DPM.setProfileEnabled(Privilege.DAR)
- SP.managedProfileActivated = true
context.popToast(R.string.work_profile_activated)
}
- }
- DhizukuErrorDialog {
- dhizukuErrorStatus.value = 0
- Privilege.updateStatus()
- navController.navigate(WorkModes(false)) {
- popUpTo { inclusive = true }
- launchSingleTop = true
- }
- }
-}
-
-@Serializable private object Home
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-private fun HomeScreen(onNavigate: (Any) -> Unit) {
- val privilege by Privilege.status.collectAsStateWithLifecycle()
- val sb = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
- Scaffold(
- Modifier.nestedScroll(sb.nestedScrollConnection),
- topBar = {
- LargeTopAppBar(
- { Text(stringResource(R.string.app_name)) },
- actions = {
- IconButton({ onNavigate(WorkModes(true)) }) { Icon(painterResource(R.drawable.security_fill0), null) }
- IconButton({ onNavigate(Settings) }) { Icon(Icons.Default.Settings, null) }
- },
- scrollBehavior = sb
- )
- },
- contentWindowInsets = adaptiveInsets()
- ) {
- Column(Modifier
- .fillMaxSize()
- .padding(it)
- .verticalScroll(rememberScrollState())) {
- if(privilege.device || privilege.profile) {
- HomePageItem(R.string.system, R.drawable.android_fill0) { onNavigate(SystemManager) }
- HomePageItem(R.string.network, R.drawable.wifi_fill0) { onNavigate(Network) }
+ lifecycleScope.launch {
+ while (true) {
+ val text = myApp.container.toastChannel.channel.receive()
+ context.popToast(text)
}
- if(privilege.work) {
- HomePageItem(R.string.work_profile, R.drawable.work_fill0) {
- onNavigate(WorkProfile)
- }
- }
- if(privilege.device || privilege.profile) {
- HomePageItem(R.string.applications, R.drawable.apps_fill0) {
- onNavigate(
- if (SP.applicationsListView) ApplicationsList(true, true)
- else ApplicationsFeatures
- )
- }
- if(VERSION.SDK_INT >= 24) {
- HomePageItem(R.string.user_restriction, R.drawable.person_off) { onNavigate(UserRestriction) }
- }
- HomePageItem(R.string.users,R.drawable.manage_accounts_fill0) { onNavigate(Users) }
- HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0) { onNavigate(Password) }
- }
- Spacer(Modifier.height(BottomPadding))
}
- }
-}
-
-@Composable
-fun HomePageItem(name: Int, imgVector: Int, onClick: () -> Unit) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .clickable(onClick = onClick)
- .padding(vertical = 12.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Spacer(Modifier.padding(start = 30.dp))
- Icon(
- painter = painterResource(imgVector),
- contentDescription = null
- )
- Spacer(Modifier.padding(start = 15.dp))
- Text(
- text = stringResource(name),
- style = typography.headlineSmall,
- modifier = Modifier.padding(bottom = if(zhCN) { 2 } else { 0 }.dp)
- )
- }
-}
-
-@Composable
-private fun DhizukuErrorDialog(onClose: () -> Unit) {
- val status by dhizukuErrorStatus.collectAsState()
- if (status != 0) {
- LaunchedEffect(Unit) {
- SP.dhizuku = false
- }
- AlertDialog(
- onDismissRequest = {},
- confirmButton = {
- TextButton(onClose) {
- Text(stringResource(R.string.confirm))
- }
- },
- title = { Text(stringResource(R.string.dhizuku)) },
- text = {
- val 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
- }
+ setContent {
+ val dhizukuError by myApp.container.dhizukuErrorState.collectAsState()
+ var appLockDialog by rememberSaveable { mutableStateOf(false) }
+ val theme by myApp.container.themeState.collectAsState()
+ OwnDroidTheme(theme) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.background)
)
- Text(text)
- },
- properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
- )
+ val backstack = rememberNavBackStack(Destination.Home)
+ LaunchedEffect(Unit) {
+ if (!myApp.container.privilegeState.value.activated) {
+ backstack.add(Destination.WorkingModes(false))
+ backstack.removeFirstOrNull()
+ }
+ }
+ NavDisplay(
+ backstack,
+ onBack = {
+ backstack.removeLastOrNull()
+ },
+ entryDecorators = listOf(
+ rememberSaveableStateHolderNavEntryDecorator(),
+ rememberSharedViewModelStoreNavEntryDecorator()
+ ),
+ transitionSpec = {
+ NavTransition.transition
+ },
+ popTransitionSpec = {
+ NavTransition.popTransition
+ },
+ predictivePopTransitionSpec = {
+ NavTransition.popTransition
+ }
+ ) {
+ myEntryProvider(it as Destination, backstack, appChooserVm, myApp.container)
+ }
+ val lifecycleOwner = LocalLifecycleOwner.current
+ if (appLockDialog) {
+ AppLockDialog(
+ myApp.container.settingsRepo.data.appLock, { appLockDialog = false }
+ ) { moveTaskToBack(true) }
+ }
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ if (
+ settingsRepo.data.appLock.passwordHash.isNotEmpty() &&
+ (event == Lifecycle.Event.ON_CREATE ||
+ (event == Lifecycle.Event.ON_RESUME &&
+ settingsRepo.data.appLock.lockWhenLeaving))
+ ) {
+ appLockDialog = true
+ }
+ }
+ lifecycleOwner.lifecycle.addObserver(observer)
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+ if (dhizukuError != null) DhizukuErrorDialog(dhizukuError!!) {
+ myApp.container.dhizukuErrorState.value = null
+ /*backstack += Destination.WorkingModes(false)
+ repeat(backstack.size - 1) {
+ backstack.removeFirstOrNull()
+ }*/
+ }
+ }
+ }
}
}
+
+@Composable
+private fun DhizukuErrorDialog(error: DhizukuError, onClose: () -> Unit) {
+ AlertDialog(
+ onDismissRequest = {},
+ confirmButton = {
+ TextButton(onClose) {
+ Text(stringResource(R.string.confirm))
+ }
+ },
+ title = { Text(stringResource(R.string.dhizuku)) },
+ text = {
+ val text = stringResource(
+ when (error) {
+ DhizukuError.Init -> R.string.failed_to_init_dhizuku
+ DhizukuError.Permission -> R.string.dhizuku_permission_not_granted
+ else -> R.string.failed_to_init_dhizuku
+ }
+ )
+ Text(text)
+ },
+ properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
+ )
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/MyApplication.kt b/app/src/main/java/com/bintianqi/owndroid/MyApplication.kt
index d6105c3..dea9196 100644
--- a/app/src/main/java/com/bintianqi/owndroid/MyApplication.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/MyApplication.kt
@@ -2,20 +2,18 @@ package com.bintianqi.owndroid
import android.app.Application
import android.os.Build.VERSION
+import com.bintianqi.owndroid.utils.NotificationUtils
+import com.bintianqi.owndroid.utils.getPrivilegeStatus
import org.lsposed.hiddenapibypass.HiddenApiBypass
class MyApplication : Application() {
- lateinit var myRepo: MyRepository
+ lateinit var container: AppContainer
+
override fun onCreate() {
super.onCreate()
if (VERSION.SDK_INT >= 28) HiddenApiBypass.setHiddenApiExemptions("")
- SP = SharedPrefs(applicationContext)
- val dbHelper = MyDbHelper(this)
- myRepo = MyRepository(dbHelper)
- Privilege.initialize(applicationContext)
+ container = AppContainer(this)
+ container.privilegeState.value = getPrivilegeStatus(container.privilegeHelper)
NotificationUtils.createChannels(this)
}
}
-
-lateinit var SP: SharedPrefs
- private set
diff --git a/app/src/main/java/com/bintianqi/owndroid/MyDbHelper.kt b/app/src/main/java/com/bintianqi/owndroid/MyDbHelper.kt
index dfa0805..53770ab 100644
--- a/app/src/main/java/com/bintianqi/owndroid/MyDbHelper.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/MyDbHelper.kt
@@ -4,12 +4,13 @@ import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
-class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 4) {
+class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 5) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(DHIZUKU_CLIENTS_TABLE)
db.execSQL(SECURITY_LOGS_TABLE)
db.execSQL(NETWORK_LOGS_TABLE)
db.execSQL(APP_GROUPS_TABLE)
+ db.execSQL(CP_INTENTS_TABLE)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion < 2) {
@@ -21,6 +22,9 @@ class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 4) {
if (oldVersion < 4) {
db.execSQL(APP_GROUPS_TABLE)
}
+ if (oldVersion < 5) {
+ db.execSQL(CP_INTENTS_TABLE)
+ }
}
companion object {
const val DHIZUKU_CLIENTS_TABLE = "CREATE TABLE dhizuku_clients (uid INTEGER PRIMARY KEY," +
@@ -33,5 +37,7 @@ class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 4) {
const val APP_GROUPS_TABLE = "CREATE TABLE app_groups(" +
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
"name TEXT, apps TEXT)"
+ const val CP_INTENTS_TABLE = "CREATE TABLE cross_profile_intent_filters (" +
+ "action_str TEXT, category TEXT, mime_type TEXT, direction INTEGER)"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/bintianqi/owndroid/MyRepository.kt b/app/src/main/java/com/bintianqi/owndroid/MyRepository.kt
deleted file mode 100644
index 62f7c57..0000000
--- a/app/src/main/java/com/bintianqi/owndroid/MyRepository.kt
+++ /dev/null
@@ -1,251 +0,0 @@
-package com.bintianqi.owndroid
-
-import android.app.admin.SecurityLog
-import android.content.ContentValues
-import android.database.DatabaseUtils
-import android.database.sqlite.SQLiteDatabase
-import android.os.Build.VERSION
-import androidx.annotation.RequiresApi
-import androidx.core.database.getIntOrNull
-import androidx.core.database.getLongOrNull
-import androidx.core.database.getStringOrNull
-import com.bintianqi.owndroid.dpm.AppGroup
-import com.bintianqi.owndroid.dpm.NetworkLog
-import com.bintianqi.owndroid.dpm.SecurityEvent
-import com.bintianqi.owndroid.dpm.SecurityEventWithData
-import com.bintianqi.owndroid.dpm.transformSecurityEventData
-import kotlinx.serialization.json.ClassDiscriminatorMode
-import kotlinx.serialization.json.Json
-import java.io.OutputStream
-
-class MyRepository(val dbHelper: MyDbHelper) {
- fun getDhizukuClients(): List {
- val list = mutableListOf()
- dbHelper.readableDatabase.rawQuery("SELECT * FROM dhizuku_clients", null).use { cursor ->
- while (cursor.moveToNext()) {
- list += DhizukuClientInfo(
- cursor.getInt(0), cursor.getString(1),
- cursor.getString(2).split(",").filter { it.isNotEmpty() }
- )
- }
- }
- return list
- }
- fun checkDhizukuClientPermission(uid: Int, signature: String?, permission: String): Boolean {
- val cursor = if (signature == null) {
- dbHelper.readableDatabase.rawQuery(
- "SELECT permissions FROM dhizuku_clients WHERE uid = $uid AND signature IS NULL",
- null
- )
- } else {
- dbHelper.readableDatabase.rawQuery(
- "SELECT permissions FROM dhizuku_clients WHERE uid = $uid AND signature = ?",
- arrayOf(signature)
- )
- }
- return cursor.use {
- it.moveToNext() && permission in it.getString(0).split(",")
- }
- }
- fun setDhizukuClient(info: DhizukuClientInfo) {
- val cv = ContentValues()
- cv.put("uid", info.uid)
- cv.put("signature", info.signature)
- cv.put("permissions", info.permissions.joinToString(","))
- dbHelper.writableDatabase.insertWithOnConflict("dhizuku_clients", null, cv,
- SQLiteDatabase.CONFLICT_REPLACE)
- }
- fun deleteDhizukuClient(info: DhizukuClientInfo) {
- dbHelper.writableDatabase.delete("dhizuku_clients", "uid = ${info.uid}", null)
- }
-
- fun getSecurityLogsCount(): Long {
- return DatabaseUtils.queryNumEntries(dbHelper.readableDatabase, "security_logs")
- }
- @RequiresApi(24)
- fun writeSecurityLogs(events: List) {
- val db = dbHelper.writableDatabase
- val json = Json {
- classDiscriminatorMode = ClassDiscriminatorMode.NONE
- }
- val statement = db.compileStatement("INSERT INTO security_logs VALUES (?, ?, ?, ?, ?)")
- db.beginTransaction()
- events.forEach { event ->
- try {
- if (VERSION.SDK_INT >= 28) {
- statement.bindLong(1, event.id)
- statement.bindLong(3, event.logLevel.toLong())
- } else {
- statement.bindNull(1)
- statement.bindNull(3)
- }
- statement.bindLong(2, event.tag.toLong())
- statement.bindLong(4, event.timeNanos / 1000000)
- val dataObject = transformSecurityEventData(event.tag, event.data)
- if (dataObject == null) {
- statement.bindNull(5)
- } else {
- statement.bindString(5, json.encodeToString(dataObject))
- }
- statement.executeInsert()
- } catch (e: Exception) {
- e.printStackTrace()
- } finally {
- statement.clearBindings()
- }
- }
- db.setTransactionSuccessful()
- db.endTransaction()
- statement.close()
- }
- fun exportSecurityLogs(stream: OutputStream) {
- var offset = 0
- val json = Json {
- explicitNulls = false
- }
- var addComma = false
- val bw = stream.bufferedWriter()
- bw.write("[")
- while (true) {
- dbHelper.readableDatabase.rawQuery(
- "SELECT * FROM security_logs LIMIT ? OFFSET ?",
- arrayOf(100.toString(), offset.toString())
- ).use { cursor ->
- if (cursor.count == 0) {
- break
- }
- while (cursor.moveToNext()) {
- if (addComma) bw.write(",")
- addComma = true
- val event = SecurityEvent(
- cursor.getLong(0), cursor.getInt(1), cursor.getInt(2), cursor.getLong(3),
- cursor.getStringOrNull(4)?.let { json.decodeFromString(it) }
- )
- bw.write(json.encodeToString(event))
- }
- offset += 100
- }
- }
- bw.write("]")
- bw.close()
- }
- @RequiresApi(24)
- fun exportPRSecurityLogs(logs: List, stream: OutputStream) {
- val bw = stream.bufferedWriter()
- bw.write("[")
- val json = Json {
- explicitNulls = false
- classDiscriminatorMode = ClassDiscriminatorMode.NONE
- }
- var addComma = false
- logs.forEach { log ->
- try {
- if (addComma) bw.write(",")
- addComma = true
- val event = SecurityEventWithData(
- if (VERSION.SDK_INT >= 28) log.id else null, log.tag,
- if (VERSION.SDK_INT >= 28) log.logLevel else null, log.timeNanos / 1000000,
- transformSecurityEventData(log.tag, log.data)
- )
- bw.write(json.encodeToString(event))
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
- bw.write("]")
- bw.close()
- }
- fun deleteSecurityLogs() {
- dbHelper.writableDatabase.execSQL("DELETE FROM security_logs")
- }
-
- fun getNetworkLogsCount(): Long {
- return DatabaseUtils.queryNumEntries(dbHelper.readableDatabase, "network_logs")
- }
- fun writeNetworkLogs(logs: List) {
- val db = dbHelper.writableDatabase
- val statement = db.compileStatement(
- "INSERT INTO network_logs VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
- )
- db.beginTransaction()
- logs.forEach { event ->
- if (event.id == null) statement.bindNull(1)
- else statement.bindLong(1, event.id)
- statement.bindString(2, event.packageName)
- statement.bindLong(3, event.time)
- statement.bindString(4, event.type)
- if (event.host == null) statement.bindNull(5)
- else statement.bindString(5, event.host)
- if (event.count == null) statement.bindNull(6)
- else statement.bindLong(6, event.count.toLong())
- if (event.addresses == null) statement.bindNull(7)
- else statement.bindString(7, event.addresses.joinToString(","))
- if (event.address == null) statement.bindNull(8)
- else statement.bindString(8, event.address)
- if (event.port == null) statement.bindNull(9)
- else statement.bindLong(9, event.port.toLong())
- statement.executeInsert()
- statement.clearBindings()
- }
- db.setTransactionSuccessful()
- db.endTransaction()
- statement.close()
- }
- fun exportNetworkLogs(stream: OutputStream) {
- val bw = stream.bufferedWriter()
- val json = Json {
- explicitNulls = false
- }
- var offset = 0
- var addComma = false
- bw.write("[")
- while (true) {
- val cursor = dbHelper.readableDatabase.rawQuery(
- "SELECT * FROM network_logs LIMIT ? OFFSET ?",
- arrayOf(100.toString(), offset.toString())
- )
- if (cursor.count == 0) break
- while (cursor.moveToNext()) {
- if (addComma) bw.write(",")
- addComma = true
- val log = NetworkLog(
- cursor.getLongOrNull(0), cursor.getString(1), cursor.getLong(2),
- cursor.getString(3), cursor.getStringOrNull(4), cursor.getIntOrNull(5),
- cursor.getStringOrNull(6)?.split(',')?.filter { it.isNotEmpty() },
- cursor.getStringOrNull(7), cursor.getIntOrNull(8)
- )
- bw.write(json.encodeToString(log))
- offset += 100
- }
- cursor.close()
- }
- bw.write("]")
- bw.close()
- }
- fun deleteNetworkLogs() {
- dbHelper.writableDatabase.execSQL("DELETE FROM network_logs")
- }
-
- fun getAppGroups(): List {
- val list = mutableListOf()
- dbHelper.readableDatabase.rawQuery("SELECT * FROM app_groups", null).use {
- while (it.moveToNext()) {
- list += AppGroup(it.getInt(0), it.getString(1), it.getString(2).split(','))
- }
- }
- return list
- }
- fun setAppGroup(id: Int?, name: String, apps: List) {
- val cv = ContentValues()
- cv.put("name", name)
- cv.put("apps", apps.joinToString(","))
- if (id == null) {
- dbHelper.writableDatabase.insert("app_groups", null, cv)
- } else {
- dbHelper.writableDatabase.update("app_groups", cv, "id = ?", arrayOf(id.toString()))
- }
- }
- fun deleteAppGroup(id: Int) {
- dbHelper.writableDatabase.delete("app_groups", "id = ?", arrayOf(id.toString()))
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt
deleted file mode 100644
index 47b1b95..0000000
--- a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt
+++ /dev/null
@@ -1,2040 +0,0 @@
-package com.bintianqi.owndroid
-
-import android.accounts.Account
-import android.annotation.SuppressLint
-import android.app.ActivityOptions
-import android.app.Application
-import android.app.KeyguardManager
-import android.app.PendingIntent
-import android.app.admin.DeviceAdminInfo
-import android.app.admin.DeviceAdminReceiver
-import android.app.admin.DevicePolicyManager
-import android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback
-import android.app.admin.FactoryResetProtectionPolicy
-import android.app.admin.IDevicePolicyManager
-import android.app.admin.PackagePolicy
-import android.app.admin.PreferentialNetworkServiceConfig
-import android.app.admin.SecurityLog
-import android.app.admin.SystemUpdateInfo
-import android.app.admin.SystemUpdatePolicy
-import android.app.admin.WifiSsidPolicy
-import android.app.usage.NetworkStats
-import android.app.usage.NetworkStatsManager
-import android.content.BroadcastReceiver
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.content.RestrictionEntry
-import android.content.RestrictionsManager
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageInstaller
-import android.content.pm.PackageManager
-import android.graphics.Bitmap
-import android.net.IpConfiguration
-import android.net.LinkAddress
-import android.net.ProxyInfo
-import android.net.StaticIpConfiguration
-import android.net.Uri
-import android.net.wifi.WifiConfiguration
-import android.net.wifi.WifiManager
-import android.net.wifi.WifiSsid
-import android.os.Binder
-import android.os.Build.VERSION
-import android.os.Bundle
-import android.os.HardwarePropertiesManager
-import android.os.UserHandle
-import android.os.UserManager
-import android.telephony.data.ApnSetting
-import androidx.annotation.RequiresApi
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.toArgb
-import androidx.core.content.ContextCompat
-import androidx.core.graphics.drawable.toDrawable
-import androidx.core.net.toUri
-import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.application
-import androidx.lifecycle.viewModelScope
-import com.bintianqi.owndroid.Privilege.DAR
-import com.bintianqi.owndroid.Privilege.DPM
-import com.bintianqi.owndroid.dpm.ACTIVATE_DEVICE_OWNER_COMMAND
-import com.bintianqi.owndroid.dpm.ApnAuthType
-import com.bintianqi.owndroid.dpm.ApnConfig
-import com.bintianqi.owndroid.dpm.ApnMvnoType
-import com.bintianqi.owndroid.dpm.ApnProtocol
-import com.bintianqi.owndroid.dpm.AppGroup
-import com.bintianqi.owndroid.dpm.AppRestriction
-import com.bintianqi.owndroid.dpm.AppStatus
-import com.bintianqi.owndroid.dpm.BasicAppGroup
-import com.bintianqi.owndroid.dpm.CaCertInfo
-import com.bintianqi.owndroid.dpm.CreateUserResult
-import com.bintianqi.owndroid.dpm.CreateWorkProfileOptions
-import com.bintianqi.owndroid.dpm.DelegatedAdmin
-import com.bintianqi.owndroid.dpm.DeviceAdmin
-import com.bintianqi.owndroid.dpm.FrpPolicyInfo
-import com.bintianqi.owndroid.dpm.HardwareProperties
-import com.bintianqi.owndroid.dpm.IntentFilterDirection
-import com.bintianqi.owndroid.dpm.IntentFilterOptions
-import com.bintianqi.owndroid.dpm.IpMode
-import com.bintianqi.owndroid.dpm.KeyguardDisableConfig
-import com.bintianqi.owndroid.dpm.KeyguardDisableMode
-import com.bintianqi.owndroid.dpm.NetworkStatsData
-import com.bintianqi.owndroid.dpm.NetworkStatsTarget
-import com.bintianqi.owndroid.dpm.PasswordComplexity
-import com.bintianqi.owndroid.dpm.PendingSystemUpdateInfo
-import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceInfo
-import com.bintianqi.owndroid.dpm.PrivateDnsConfiguration
-import com.bintianqi.owndroid.dpm.PrivateDnsMode
-import com.bintianqi.owndroid.dpm.ProxyMode
-import com.bintianqi.owndroid.dpm.ProxyType
-import com.bintianqi.owndroid.dpm.QueryNetworkStatsParams
-import com.bintianqi.owndroid.dpm.RecommendedProxyConf
-import com.bintianqi.owndroid.dpm.RpTokenState
-import com.bintianqi.owndroid.dpm.SsidPolicy
-import com.bintianqi.owndroid.dpm.SsidPolicyType
-import com.bintianqi.owndroid.dpm.SystemOptionsStatus
-import com.bintianqi.owndroid.dpm.SystemUpdatePolicyInfo
-import com.bintianqi.owndroid.dpm.UserIdentifier
-import com.bintianqi.owndroid.dpm.UserInformation
-import com.bintianqi.owndroid.dpm.UserOperationType
-import com.bintianqi.owndroid.dpm.WifiInfo
-import com.bintianqi.owndroid.dpm.WifiSecurity
-import com.bintianqi.owndroid.dpm.WifiStatus
-import com.bintianqi.owndroid.dpm.activateOrgProfileCommand
-import com.bintianqi.owndroid.dpm.delegatedScopesList
-import com.bintianqi.owndroid.dpm.doUserOperationWithContext
-import com.bintianqi.owndroid.dpm.getPackageInstaller
-import com.bintianqi.owndroid.dpm.handlePrivilegeChange
-import com.bintianqi.owndroid.dpm.isValidPackageName
-import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage
-import com.bintianqi.owndroid.dpm.runtimePermissions
-import com.bintianqi.owndroid.dpm.temperatureTypes
-import com.rosan.dhizuku.api.Dhizuku
-import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
-import com.topjohnwu.superuser.Shell
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.addJsonObject
-import kotlinx.serialization.json.buildJsonArray
-import kotlinx.serialization.json.put
-import java.net.InetAddress
-import java.security.MessageDigest
-import java.security.cert.CertificateException
-import java.security.cert.CertificateFactory
-import java.security.cert.X509Certificate
-import java.time.ZoneId
-import java.time.ZonedDateTime
-import java.util.concurrent.Executors
-import kotlin.reflect.jvm.jvmErasure
-
-class MyViewModel(application: Application): AndroidViewModel(application) {
- val myRepo = getApplication().myRepo
- val PM = application.packageManager
-
- val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme))
- fun changeTheme(newTheme: ThemeSettings) {
- theme.value = newTheme
- SP.materialYou = newTheme.materialYou
- SP.darkTheme = newTheme.darkTheme
- SP.blackTheme = newTheme.blackTheme
- }
- fun getDisplayDangerousFeatures(): Boolean {
- return SP.displayDangerousFeatures
- }
- fun getShortcutsEnabled(): Boolean {
- return SP.shortcuts
- }
- fun setDisplayDangerousFeatures(state: Boolean) {
- SP.displayDangerousFeatures = state
- }
- fun setShortcutsEnabled(enabled: Boolean) {
- SP.shortcuts = enabled
- ShortcutUtils.setAllShortcuts(application, enabled)
- }
- fun getAppLockConfig(): AppLockConfig {
- val passwordHash = SP.lockPasswordHash
- return AppLockConfig(passwordHash?.ifEmpty { null }, SP.biometricsUnlock, SP.lockWhenLeaving)
- }
- fun setAppLockConfig(config: AppLockConfig) {
- if (config.password == null) {
- SP.lockPasswordHash = ""
- } else if (!config.password.isEmpty()) {
- SP.lockPasswordHash = config.password.hash()
- }
- SP.biometricsUnlock = config.biometrics
- SP.lockWhenLeaving = config.whenLeaving
- }
- fun getApiEnabled(): Boolean {
- return SP.apiKeyHash?.isNotEmpty() ?: false
- }
- fun setApiKey(key: String) {
- SP.apiKeyHash = if (key.isEmpty()) "" else key.hash()
- }
- val enabledNotifications = MutableStateFlow(emptyList())
- fun getEnabledNotifications() {
- val list = SP.notifications?.split(',')?.mapNotNull { it.toIntOrNull() }
- enabledNotifications.value = list ?: NotificationType.entries.map { it.id }
- }
- fun setNotificationEnabled(type: NotificationType, enabled: Boolean) {
- enabledNotifications.update { list ->
- if (enabled) list.plus(type.id) else list.minus(type.id)
- }
- SP.notifications = enabledNotifications.value.joinToString(",") { it.toString() }
- }
-
- val chosenPackage = Channel(1, BufferOverflow.DROP_LATEST)
-
- val installedPackages = MutableStateFlow(emptyList())
- val refreshPackagesProgress = MutableStateFlow(0F)
- fun refreshPackageList() {
- viewModelScope.launch(Dispatchers.IO) {
- installedPackages.value = emptyList()
- val apps = PM.getInstalledApplications(getInstalledAppsFlags)
- apps.forEachIndexed { index, info ->
- installedPackages.update {
- it + getAppInfo(info)
- }
- refreshPackagesProgress.value = (index + 1).toFloat() / apps.size
- }
- }
- }
- fun onPackageRemoved(name: String) {
- installedPackages.update { list ->
- list.filter { it.name != name }
- }
- }
- fun getAppInfo(info: ApplicationInfo) =
- AppInfo(info.packageName, info.loadLabel(PM).toString(), info.loadIcon(PM), info.flags)
- fun getAppInfo(name: String): AppInfo {
- return try {
- getAppInfo(PM.getApplicationInfo(name, getInstalledAppsFlags))
- } catch (_: PackageManager.NameNotFoundException) {
- AppInfo(name, "???", Color.Transparent.toArgb().toDrawable(), 0)
- }
- }
-
- val suspendedPackages = MutableStateFlow(emptyList())
- @RequiresApi(24)
- fun getSuspendedPackaged() {
- val packages = PM.getInstalledApplications(getInstalledAppsFlags).filter {
- DPM.isPackageSuspended(DAR, it.packageName)
- }
- suspendedPackages.value = packages.map { getAppInfo(it) }
- }
- @RequiresApi(24)
- fun setPackageSuspended(packages: List, status: Boolean) {
- DPM.setPackagesSuspended(DAR, packages.toTypedArray(), status)
- getSuspendedPackaged()
- }
-
- val hiddenPackages = MutableStateFlow(emptyList())
- fun getHiddenPackages() {
- hiddenPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter {
- DPM.isApplicationHidden(DAR, it.packageName)
- }.map { getAppInfo(it) }
- }
- fun setPackageHidden(packages: List, status: Boolean) {
- for (name in packages) {
- DPM.setApplicationHidden(DAR, name, status)
- }
- getHiddenPackages()
- }
-
- // Uninstall blocked packages
- val ubPackages = MutableStateFlow(emptyList())
- fun getUbPackages() {
- ubPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter {
- DPM.isUninstallBlocked(DAR, it.packageName)
- }.map { getAppInfo(it) }
- }
- fun setPackageUb(packages: List, status: Boolean) {
- for (name in packages) {
- DPM.setUninstallBlocked(DAR, name, status)
- }
- getUbPackages()
- }
-
- // User control disabled packages
- val ucdPackages = MutableStateFlow(emptyList())
- @RequiresApi(30)
- fun getUcdPackages() {
- ucdPackages.value = DPM.getUserControlDisabledPackages(DAR).distinct().map {
- getAppInfo(it)
- }
- }
- @RequiresApi(30)
- fun setPackageUcd(packages: List, status: Boolean) {
- DPM.setUserControlDisabledPackages(
- DAR,
- ucdPackages.value.map { it.name }.run {
- if (status) plus(packages) else minus(packages)
- }
- )
- getUcdPackages()
- }
-
- val packagePermissions = MutableStateFlow(emptyMap())
- fun getPackagePermissions(name: String) {
- if (name.isValidPackageName) {
- packagePermissions.value = runtimePermissions.associate {
- it.id to DPM.getPermissionGrantState(DAR, name, it.id)
- }
- } else {
- packagePermissions.value = emptyMap()
- }
- }
- fun setPackagePermission(name: String, permission: String, status: Int): Boolean {
- val result = DPM.setPermissionGrantState(DAR, name, permission, status)
- getPackagePermissions(name)
- return result
- }
-
- // Metered data disabled packages
- val mddPackages = MutableStateFlow(emptyList())
- @RequiresApi(28)
- fun getMddPackages() {
- mddPackages.value = DPM.getMeteredDataDisabledPackages(DAR).distinct().map { getAppInfo(it) }
- }
- @RequiresApi(28)
- fun setPackageMdd(packages: List, status: Boolean) {
- DPM.setMeteredDataDisabledPackages(
- DAR, mddPackages.value.map { it.name }.run {
- if (status) plus(packages) else minus(packages)
- }
- )
- getMddPackages()
- }
-
- // Keep uninstalled packages
- val kuPackages = MutableStateFlow(emptyList())
- @RequiresApi(28)
- fun getKuPackages() {
- kuPackages.value = DPM.getKeepUninstalledPackages(DAR)?.distinct()?.map { getAppInfo(it) } ?: emptyList()
- }
- @RequiresApi(28)
- fun setPackageKu(packages: List, status: Boolean) {
- DPM.setKeepUninstalledPackages(
- DAR, kuPackages.value.map { it.name }.run {
- if (status) plus(packages) else minus(packages)
- }
- )
- getKuPackages()
- }
-
- // Cross profile packages
- val cpPackages = MutableStateFlow(emptyList())
- @RequiresApi(30)
- fun getCpPackages() {
- cpPackages.value = DPM.getCrossProfilePackages(DAR).map { getAppInfo(it) }
- }
- @RequiresApi(30)
- fun setPackageCp(packages: List, status: Boolean) {
- DPM.setCrossProfilePackages(
- DAR,
- cpPackages.value.map { it.name }.toSet().run {
- if (status) plus(packages) else minus(packages)
- }
- )
- getCpPackages()
- }
-
- // Cross-profile widget providers
- val cpwProviders = MutableStateFlow(emptyList())
- fun getCpwProviders() {
- cpwProviders.value = DPM.getCrossProfileWidgetProviders(DAR).distinct().map { getAppInfo(it) }
- }
- fun setCpwProvider(packages: List, status: Boolean) {
- for (name in packages) {
- if (status) {
- DPM.addCrossProfileWidgetProvider(DAR, name)
- } else {
- DPM.removeCrossProfileWidgetProvider(DAR, name)
- }
- }
- getCpwProviders()
- }
-
- @RequiresApi(28)
- fun clearAppData(name: String, callback: (Boolean) -> Unit) {
- DPM.clearApplicationUserData(DAR, name, Executors.newSingleThreadExecutor()) { _, result ->
- callback(result)
- }
- }
-
- fun uninstallPackage(packageName: String, onComplete: (String?) -> Unit) {
- val action = "com.bintianqi.owndroid.action.PACKAGE_UNINSTALLED"
- val receiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999)
- if(statusExtra == PackageInstaller.STATUS_PENDING_USER_ACTION) {
- @SuppressWarnings("UnsafeIntentLaunch")
- context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT) as Intent?)
- } else {
- context.unregisterReceiver(this)
- if (statusExtra == PackageInstaller.STATUS_SUCCESS) {
- onComplete(null)
- } else {
- onComplete(parsePackageInstallerMessage(context, intent))
- }
- }
- }
- }
- ContextCompat.registerReceiver(
- application, receiver, IntentFilter(action), null,
- null, ContextCompat.RECEIVER_NOT_EXPORTED
- )
- val intent = Intent(action).setPackage(application.packageName)
- val pi = if(VERSION.SDK_INT >= 34) {
- PendingIntent.getBroadcast(
- application, 0, intent,
- PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE
- ).intentSender
- } else {
- PendingIntent.getBroadcast(application, 0, intent, PendingIntent.FLAG_MUTABLE).intentSender
- }
- application.getPackageInstaller().uninstall(packageName, pi)
- }
-
- @RequiresApi(28)
- fun installExistingApp(name: String): Boolean {
- return DPM.installExistingPackage(DAR, name)
- }
-
- // Credential manager policy
- val cmPackages = MutableStateFlow(emptyList())
- @RequiresApi(34)
- fun getCmPolicy(): Int {
- return DPM.credentialManagerPolicy?.let { policy ->
- cmPackages.value = policy.packageNames.distinct().map { getAppInfo(it) }
- policy.policyType
- } ?: -1
- }
- fun setCmPackage(packages: List, status: Boolean) {
- cmPackages.update {
- updateAppInfoList(it, packages, status)
- }
- }
- @RequiresApi(34)
- fun setCmPolicy(type: Int) {
- DPM.credentialManagerPolicy = if (type != -1 && cmPackages.value.isNotEmpty()) {
- PackagePolicy(type, cmPackages.value.map { it.name }.toSet())
- } else null
- getCmPolicy()
- }
-
- fun updateAppInfoList(
- origin: List, input: List, status: Boolean
- ): List {
- return if (status) {
- origin + input.map { getAppInfo(it) }
- } else {
- origin.filter { it.name !in input }
- }
- }
-
- // Permitted input method
- val pimPackages = MutableStateFlow(emptyList())
- fun getPimPackages(): Boolean {
- return DPM.getPermittedInputMethods(DAR).let { packages ->
- pimPackages.value = packages?.distinct()?.map { getAppInfo(it) } ?: emptyList()
- packages == null
- }
- }
- fun setPimPackage(packages: List, status: Boolean) {
- pimPackages.update {
- updateAppInfoList(it, packages, status)
- }
- }
- fun setPimPolicy(allowAll: Boolean): Boolean {
- val result = DPM.setPermittedInputMethods(
- DAR, if (allowAll) null else pimPackages.value.map { it.name })
- getPimPackages()
- return result
- }
-
- // Permitted accessibility services
- val pasPackages = MutableStateFlow(emptyList())
- fun getPasPackages(): Boolean {
- return DPM.getPermittedAccessibilityServices(DAR).let { packages ->
- pasPackages.value = packages?.distinct()?.map { getAppInfo(it) } ?: emptyList()
- packages == null
- }
- }
- fun setPasPackage(packages: List, status: Boolean) {
- pasPackages.update {
- updateAppInfoList(it, packages, status)
- }
- }
- fun setPasPolicy(allowAll: Boolean): Boolean {
- val result = DPM.setPermittedAccessibilityServices(
- DAR, if (allowAll) null else pasPackages.value.map { it.name })
- getPasPackages()
- return result
- }
-
- fun enableSystemApp(name: String) {
- DPM.enableSystemApp(DAR, name)
- }
-
- val appStatus = MutableStateFlow(AppStatus(false, false, false, false, false, false))
- fun getAppStatus(name: String) {
- appStatus.value = AppStatus(
- if (VERSION.SDK_INT >= 24) DPM.isPackageSuspended(DAR, name) else false,
- DPM.isApplicationHidden(DAR, name),
- DPM.isUninstallBlocked(DAR, name),
- if (VERSION.SDK_INT >= 30) name in DPM.getUserControlDisabledPackages(DAR) else false,
- if (VERSION.SDK_INT >= 28) name in DPM.getMeteredDataDisabledPackages(DAR) else false,
- if (VERSION.SDK_INT >= 28 && Privilege.status.value.device)
- DPM.getKeepUninstalledPackages(DAR)?.contains(name) == true
- else false
- )
- }
- // Application details
- @RequiresApi(24)
- fun adSetPackageSuspended(name: String, status: Boolean) {
- try {
- DPM.setPackagesSuspended(DAR, arrayOf(name), status)
- appStatus.update { it.copy(suspend = DPM.isPackageSuspended(DAR, name)) }
- } catch (_: Exception) {}
- }
- fun adSetPackageHidden(name: String, status: Boolean) {
- DPM.setApplicationHidden(DAR, name, status)
- appStatus.update { it.copy(hide = DPM.isApplicationHidden(DAR, name)) }
- }
- fun adSetPackageUb(name: String, status: Boolean) {
- DPM.setUninstallBlocked(DAR, name, status)
- appStatus.update { it.copy(uninstallBlocked = DPM.isUninstallBlocked(DAR, name)) }
- }
- @RequiresApi(30)
- fun adSetPackageUcd(name: String, status: Boolean) {
- DPM.setUserControlDisabledPackages(DAR,
- DPM.getUserControlDisabledPackages(DAR).run { if (status) plus(name) else minus(name) })
- appStatus.update {
- it.copy(userControlDisabled = name in DPM.getUserControlDisabledPackages(DAR))
- }
- }
- @RequiresApi(28)
- fun adSetPackageMdd(name: String, status: Boolean) {
- DPM.setMeteredDataDisabledPackages(DAR,
- DPM.getMeteredDataDisabledPackages(DAR).run { if (status) plus(name) else minus(name) })
- appStatus.update {
- it.copy(meteredDataDisabled = name in DPM.getMeteredDataDisabledPackages(DAR))
- }
- }
- @RequiresApi(28)
- fun adSetPackageKu(name: String, status: Boolean) {
- DPM.setKeepUninstalledPackages(DAR,
- DPM.getKeepUninstalledPackages(DAR)?.run { if (status) plus(name) else minus(name) } ?: emptyList())
- appStatus.update {
- it.copy(keepUninstalled = DPM.getKeepUninstalledPackages(DAR)?.contains(name) == true )
- }
- }
-
- @RequiresApi(34)
- fun setDefaultDialer(name: String): Boolean {
- return try {
- DPM.setDefaultDialerApplication(name)
- true
- } catch (e: IllegalArgumentException) {
- e.printStackTrace()
- false
- }
- }
-
- val appRestrictions = MutableStateFlow(emptyList())
-
- fun getAppRestrictions(name: String) {
- val rm = application.getSystemService(RestrictionsManager::class.java)
- try {
- val bundle = DPM.getApplicationRestrictions(DAR, name)
- appRestrictions.value = rm.getManifestRestrictions(name)?.mapNotNull {
- transformRestrictionEntry(it)
- }?.map {
- if (bundle.containsKey(it.key)) {
- when (it) {
- is AppRestriction.BooleanItem -> it.value = bundle.getBoolean(it.key)
- is AppRestriction.StringItem -> it.value = bundle.getString(it.key)
- is AppRestriction.IntItem -> it.value = bundle.getInt(it.key)
- is AppRestriction.ChoiceItem -> it.value = bundle.getString(it.key)
- is AppRestriction.MultiSelectItem -> it.value = bundle.getStringArray(it.key)
- }
- }
- it
- } ?: emptyList()
- } catch (e: Exception) {
- e.printStackTrace()
- appRestrictions.value = emptyList()
- }
- }
-
- fun setAppRestrictions(name: String, item: AppRestriction) {
- viewModelScope.launch(Dispatchers.IO) {
- val bundle = transformAppRestriction(
- appRestrictions.value.filter { it.key != item.key }.plus(item)
- )
- DPM.setApplicationRestrictions(DAR, name, bundle)
- getAppRestrictions(name)
- }
- }
-
- fun clearAppRestrictions(name: String) {
- viewModelScope.launch(Dispatchers.IO) {
- DPM.setApplicationRestrictions(DAR, name, Bundle())
- getAppRestrictions(name)
- }
- }
-
- fun transformRestrictionEntry(e: RestrictionEntry): AppRestriction? {
- return when (e.type) {
- RestrictionEntry.TYPE_INTEGER ->
- AppRestriction.IntItem(e.key, e.title, e.description, null)
- RestrictionEntry.TYPE_STRING ->
- AppRestriction.StringItem(e.key, e.title, e.description, null)
- RestrictionEntry.TYPE_BOOLEAN ->
- AppRestriction.BooleanItem(e.key, e.title, e.description, null)
- RestrictionEntry.TYPE_CHOICE -> AppRestriction.ChoiceItem(e.key, e.title,
- e.description, e.choiceEntries, e.choiceValues, null)
- RestrictionEntry.TYPE_MULTI_SELECT -> AppRestriction.MultiSelectItem(e.key, e.title,
- e.description, e.choiceEntries, e.choiceValues, null)
- else -> null
- }
- }
-
- fun transformAppRestriction(list: List): Bundle {
- val b = Bundle()
- for (r in list) {
- when (r) {
- is AppRestriction.IntItem -> r.value?.let { b.putInt(r.key, it) }
- is AppRestriction.StringItem -> r.value?.let { b.putString(r.key, it) }
- is AppRestriction.BooleanItem -> r.value?.let { b.putBoolean(r.key, it) }
- is AppRestriction.ChoiceItem -> r.value?.let { b.putString(r.key, it) }
- is AppRestriction.MultiSelectItem -> r.value?.let { b.putStringArray(r.key, r.value) }
- }
- }
- return b
- }
-
- val appGroups = MutableStateFlow(emptyList())
- init {
- getAppGroups()
- }
- fun getAppGroups() {
- appGroups.value = myRepo.getAppGroups()
- }
- fun setAppGroup(id: Int?, name: String, apps: List) {
- myRepo.setAppGroup(id, name, apps)
- getAppGroups()
- }
- fun deleteAppGroup(id: Int) {
- myRepo.deleteAppGroup(id)
- appGroups.update { group ->
- group.filter { it.id != id }
- }
- }
- fun exportAppGroups(uri: Uri) {
- application.contentResolver.openOutputStream(uri)!!.use {
- val list: List = appGroups.value
- it.write(Json.encodeToString(list).encodeToByteArray())
- }
- }
- fun importAppGroups(uri: Uri) {
- application.contentResolver.openInputStream(uri)!!.use {
- Json.decodeFromString>(it.readBytes().decodeToString())
- }.forEach {
- myRepo.setAppGroup(null, it.name, it.apps)
- }
- getAppGroups()
- }
-
- @RequiresApi(24)
- fun reboot() {
- DPM.reboot(DAR)
- }
- @RequiresApi(24)
- fun requestBugReport(): Boolean {
- return try {
- DPM.requestBugreport(DAR)
- } catch (e: Exception) {
- e.printStackTrace()
- false
- }
- }
- @SuppressLint("PrivateApi")
- @RequiresApi(24)
- fun getOrgName(): String {
- return try {
- DPM.getOrganizationName(DAR)?.toString() ?: ""
- } catch (_: Exception) {
- try {
- val method = DevicePolicyManager::class.java.getDeclaredMethod(
- "getDeviceOwnerOrganizationName"
- )
- method.isAccessible = true
- (method.invoke(DPM) as CharSequence).toString()
- } catch (_: Exception) {
- ""
- }
- }
- }
- @RequiresApi(24)
- fun setOrgName(name: String) {
- DPM.setOrganizationName(DAR, name)
- }
- @RequiresApi(31)
- fun setOrgId(id: String): Boolean {
- return try {
- DPM.setOrganizationId(id)
- true
- } catch (_: IllegalStateException) {
- false
- }
- }
- @RequiresApi(31)
- fun getEnrollmentSpecificId(): String {
- return DPM.enrollmentSpecificId
- }
- val systemOptionsStatus = MutableStateFlow(SystemOptionsStatus())
- fun getSystemOptionsStatus() {
- val privilege = Privilege.status.value
- systemOptionsStatus.value = SystemOptionsStatus(
- cameraDisabled = DPM.getCameraDisabled(null),
- screenCaptureDisabled = DPM.getScreenCaptureDisabled(null),
- statusBarDisabled = if (VERSION.SDK_INT >= 34 &&
- privilege.run { device || (profile && affiliated) })
- DPM.isStatusBarDisabled else false,
- autoTimeEnabled = if (VERSION.SDK_INT >= 30 && (privilege.device || privilege.org))
- DPM.getAutoTimeEnabled(DAR) else false,
- autoTimeZoneEnabled = if (VERSION.SDK_INT >= 30 && (privilege.device || privilege.org))
- DPM.getAutoTimeZoneEnabled(DAR) else false,
- autoTimeRequired = if (VERSION.SDK_INT < 30) DPM.autoTimeRequired else false,
- masterVolumeMuted = DPM.isMasterVolumeMuted(DAR),
- backupServiceEnabled = if (VERSION.SDK_INT >= 26) DPM.isBackupServiceEnabled(DAR) else false,
- btContactSharingDisabled = if (privilege.work)
- DPM.getBluetoothContactSharingDisabled(DAR) else false,
- commonCriteriaMode = if (VERSION.SDK_INT >= 30 && (privilege.device || privilege.org))
- DPM.isCommonCriteriaModeEnabled(DAR) else false,
- usbSignalEnabled = if (VERSION.SDK_INT >= 31) DPM.isUsbDataSignalingEnabled else false,
- canDisableUsbSignal = if (VERSION.SDK_INT >= 31) DPM.canUsbDataSignalingBeDisabled() else false
- )
- }
- fun setCameraDisabled(disabled: Boolean) {
- DPM.setCameraDisabled(DAR, disabled)
- ShortcutUtils.setShortcut(application, MyShortcut.DisableCamera, !disabled)
- systemOptionsStatus.update { it.copy(cameraDisabled = DPM.getCameraDisabled(null)) }
- }
- fun setScreenCaptureDisabled(disabled: Boolean) {
- DPM.setScreenCaptureDisabled(DAR, disabled)
- systemOptionsStatus.update {
- it.copy(screenCaptureDisabled = DPM.getScreenCaptureDisabled(null))
- }
- }
- fun setStatusBarDisabled(disabled: Boolean) {
- val result = DPM.setStatusBarDisabled(DAR, disabled)
- if (result) systemOptionsStatus.update { it.copy(statusBarDisabled = disabled) }
- }
- @RequiresApi(30)
- fun setAutoTimeEnabled(enabled: Boolean) {
- DPM.setAutoTimeEnabled(DAR, enabled)
- systemOptionsStatus.update { it.copy(autoTimeEnabled = DPM.getAutoTimeEnabled(DAR)) }
- }
- @RequiresApi(30)
- fun setAutoTimeZoneEnabled(enabled: Boolean) {
- DPM.setAutoTimeZoneEnabled(DAR, enabled)
- systemOptionsStatus.update {
- it.copy(autoTimeZoneEnabled = DPM.getAutoTimeZoneEnabled(DAR))
- }
- }
- @Suppress("DEPRECATION")
- fun setAutoTimeRequired(required: Boolean) {
- DPM.setAutoTimeRequired(DAR, required)
- systemOptionsStatus.update { it.copy(autoTimeRequired = DPM.autoTimeRequired) }
- }
- fun setMasterVolumeMuted(muted: Boolean) {
- DPM.setMasterVolumeMuted(DAR, muted)
- ShortcutUtils.setShortcut(application, MyShortcut.Mute, !muted)
- systemOptionsStatus.update { it.copy(masterVolumeMuted = DPM.isMasterVolumeMuted(DAR)) }
- }
- @RequiresApi(26)
- fun setBackupServiceEnabled(enabled: Boolean) {
- DPM.setBackupServiceEnabled(DAR, enabled)
- systemOptionsStatus.update {
- it.copy(backupServiceEnabled = DPM.isBackupServiceEnabled(DAR))
- }
- }
- fun setBtContactSharingDisabled(disabled: Boolean) {
- DPM.setBluetoothContactSharingDisabled(DAR, disabled)
- systemOptionsStatus.update {
- it.copy(btContactSharingDisabled = DPM.getBluetoothContactSharingDisabled(DAR))
- }
- }
- @RequiresApi(30)
- fun setCommonCriteriaModeEnabled(enabled: Boolean) {
- DPM.setCommonCriteriaModeEnabled(DAR, enabled)
- systemOptionsStatus.update {
- it.copy(commonCriteriaMode = DPM.isCommonCriteriaModeEnabled(DAR))
- }
- }
- @RequiresApi(31)
- fun setUsbSignalEnabled(enabled: Boolean) {
- DPM.isUsbDataSignalingEnabled = enabled
- systemOptionsStatus.update { it.copy(usbSignalEnabled = DPM.isUsbDataSignalingEnabled) }
- }
- fun setKeyguardDisabled(disabled: Boolean): Boolean {
- return DPM.setKeyguardDisabled(DAR, disabled)
- }
- fun lockScreen(evictKey: Boolean) {
- if (VERSION.SDK_INT >= 26 && Privilege.status.value.work) {
- DPM.lockNow(if (evictKey) DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY else 0)
- } else {
- DPM.lockNow()
- }
- }
- val hardwareProperties = MutableStateFlow(HardwareProperties())
- var hpRefreshInterval = 1000L
- fun setHpRefreshInterval(interval: Float) {
- hpRefreshInterval = (interval * 1000).toLong()
- }
- @RequiresApi(24)
- suspend fun getHardwareProperties() {
- val hpm = application.getSystemService(HardwarePropertiesManager::class.java)
- while (true) {
- val properties = HardwareProperties(
- temperatureTypes.map { (type, _) ->
- type to hpm.getDeviceTemperatures(type, HardwarePropertiesManager.TEMPERATURE_CURRENT).toList()
- }.toMap(),
- hpm.cpuUsages.map { it.active to it.total },
- hpm.fanSpeeds.toList()
- )
- if (properties.cpuUsages.isEmpty() && properties.fanSpeeds.isEmpty() &&
- properties.temperatures.isEmpty()) {
- break
- }
- hardwareProperties.value = properties
- delay(hpRefreshInterval)
- }
- }
- @RequiresApi(28)
- fun setTime(time: Long, useCurrentTz: Boolean): Boolean {
- val offset = if (useCurrentTz) {
- ZonedDateTime.now(ZoneId.systemDefault()).offset.totalSeconds * 1000L
- } else 0L
- return DPM.setTime(DAR, time - offset)
- }
- @RequiresApi(28)
- fun setTimeZone(tz: String): Boolean {
- return DPM.setTimeZone(DAR, tz)
- }
- @RequiresApi(36)
- fun getAutoTimePolicy(): Int {
- return DPM.autoTimePolicy
- }
- @RequiresApi(36)
- fun setAutoTimePolicy(policy: Int) {
- DPM.autoTimePolicy = policy
- }
- @RequiresApi(36)
- fun getAutoTimeZonePolicy(): Int {
- return DPM.autoTimeZonePolicy
- }
- @RequiresApi(36)
- fun setAutoTimeZonePolicy(policy: Int) {
- DPM.autoTimeZonePolicy = policy
- }
- @RequiresApi(35)
- fun getContentProtectionPolicy(): Int {
- return DPM.getContentProtectionPolicy(DAR)
- }
- @RequiresApi(35)
- fun setContentProtectionPolicy(policy: Int) {
- DPM.setContentProtectionPolicy(DAR, policy)
- }
- fun getPermissionPolicy(): Int {
- return DPM.getPermissionPolicy(DAR)
- }
- fun setPermissionPolicy(policy: Int) {
- DPM.setPermissionPolicy(DAR, policy)
- }
- @RequiresApi(34)
- fun getMtePolicy(): Int {
- return DPM.mtePolicy
- }
- @RequiresApi(34)
- fun setMtePolicy(policy: Int): Boolean {
- return try {
- DPM.mtePolicy = policy
- true
- } catch (_: UnsupportedOperationException) {
- false
- }
- }
- @RequiresApi(31)
- fun getNsAppPolicy(): Int {
- return DPM.nearbyAppStreamingPolicy
- }
- @RequiresApi(31)
- fun setNsAppPolicy(policy: Int) {
- DPM.nearbyAppStreamingPolicy = policy
- }
- @RequiresApi(31)
- fun getNsNotificationPolicy(): Int {
- return DPM.nearbyNotificationStreamingPolicy
- }
- @RequiresApi(31)
- fun setNsNotificationPolicy(policy: Int) {
- DPM.nearbyNotificationStreamingPolicy = policy
- }
- val lockTaskPackages = MutableStateFlow(emptyList())
- @RequiresApi(26)
- fun getLockTaskPackages() {
- lockTaskPackages.value = DPM.getLockTaskPackages(DAR).map { getAppInfo(it) }
- }
- @RequiresApi(26)
- fun setLockTaskPackage(name: String, status: Boolean) {
- DPM.setLockTaskPackages(DAR,
- lockTaskPackages.value.map { it.name }
- .run { if (status) plus(name) else minus(name) }
- .toTypedArray()
- )
- getLockTaskPackages()
- }
- @RequiresApi(28)
- fun startLockTaskMode(
- packageName: String, activity: String, clearTask: Boolean, showNotification: Boolean
- ): Boolean {
- if (!DPM.isLockTaskPermitted(packageName)) {
- val list = lockTaskPackages.value.map { it.name } + packageName
- DPM.setLockTaskPackages(DAR, list.toTypedArray())
- getLockTaskPackages()
- }
- if (showNotification) {
- DPM.setLockTaskFeatures(
- DAR,
- DPM.getLockTaskFeatures(DAR) or
- DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS or
- DevicePolicyManager.LOCK_TASK_FEATURE_HOME
- )
- }
- val options = ActivityOptions.makeBasic().setLockTaskEnabled(true)
- val intent = if(activity.isNotEmpty()) {
- Intent().setComponent(ComponentName(packageName, activity))
- } else PM.getLaunchIntentForPackage(packageName)
- if (intent != null) {
- intent.addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK
- or (if (clearTask) Intent.FLAG_ACTIVITY_CLEAR_TASK else 0)
- )
- application.startActivity(intent, options.toBundle())
- if (showNotification) {
- application.startForegroundService(Intent(application, LockTaskService::class.java))
- }
- return true
- } else {
- return false
- }
- }
- @RequiresApi(28)
- fun getLockTaskFeatures(): Int {
- return DPM.getLockTaskFeatures(DAR)
- }
- @RequiresApi(28)
- fun setLockTaskFeatures(flags: Int): String? {
- try {
- DPM.setLockTaskFeatures(DAR, flags)
- return null
- } catch (e: IllegalArgumentException) {
- return e.message
- }
- }
- val installedCaCerts = MutableStateFlow(emptyList())
- val selectedCaCert = MutableStateFlow(null)
- fun getCaCerts() {
- installedCaCerts.value = DPM.getInstalledCaCerts(DAR).mapNotNull { parseCaCert(it) }
- }
- fun selectCaCert(cert: CaCertInfo) {
- selectedCaCert.value = cert
- }
- fun parseCaCert(uri: Uri) {
- try {
- application.contentResolver.openInputStream(uri)?.use {
- selectedCaCert.value = parseCaCert(it.readBytes())
- }
- } catch(e: Exception) {
- e.printStackTrace()
- }
- }
- fun parseCaCert(bytes: ByteArray): CaCertInfo? {
- val hash = MessageDigest.getInstance("SHA-256").digest(bytes).toHexString()
- return try {
- val factory = CertificateFactory.getInstance("X.509")
- val cert = factory.generateCertificate(bytes.inputStream()) as X509Certificate
- CaCertInfo(
- hash, cert.serialNumber.toString(16),
- cert.issuerX500Principal.name, cert.subjectX500Principal.name,
- cert.notBefore.time, cert.notAfter.time, bytes
- )
- } catch (e: CertificateException) {
- e.printStackTrace()
- null
- }
- }
- fun installCaCert(): Boolean {
- val result = DPM.installCaCert(DAR, selectedCaCert.value!!.bytes)
- if (result) getCaCerts()
- return result
- }
- fun uninstallCaCert() {
- DPM.uninstallCaCert(DAR, selectedCaCert.value!!.bytes)
- getCaCerts()
- }
- fun uninstallAllCaCerts() {
- DPM.uninstallAllUserCaCerts(DAR)
- getCaCerts()
- }
- fun exportCaCert(uri: Uri) {
- application.contentResolver.openOutputStream(uri)?.use {
- it.write(selectedCaCert.value!!.bytes)
- }
- }
- val mdAccountTypes = MutableStateFlow(emptyList())
- fun getMdAccountTypes() {
- mdAccountTypes.value = DPM.accountTypesWithManagementDisabled?.toList() ?: emptyList()
- }
- fun setMdAccountType(type: String, disabled: Boolean) {
- DPM.setAccountManagementDisabled(DAR, type, disabled)
- getMdAccountTypes()
- }
- @RequiresApi(30)
- fun getFrpPolicy(): FrpPolicyInfo {
- return try {
- val policy = DPM.getFactoryResetProtectionPolicy(DAR)
- FrpPolicyInfo(
- true, policy != null, policy?.isFactoryResetProtectionEnabled ?: false,
- policy?.factoryResetProtectionAccounts ?: emptyList()
- )
- } catch (_: UnsupportedOperationException) {
- FrpPolicyInfo(false, false, false, emptyList())
- }
- }
- @RequiresApi(30)
- fun setFrpPolicy(info: FrpPolicyInfo) {
- val policy = if (info.usePolicy) {
- FactoryResetProtectionPolicy.Builder()
- .setFactoryResetProtectionEnabled(info.enabled)
- .setFactoryResetProtectionAccounts(info.accounts)
- .build()
- } else null
- DPM.setFactoryResetProtectionPolicy(DAR, policy)
- }
- fun wipeData(wipeDevice: Boolean, flags: Int, reason: String) {
- if (wipeDevice && VERSION.SDK_INT >= 34) {
- DPM.wipeDevice(flags)
- } else {
- if(VERSION.SDK_INT >= 28 && reason.isNotEmpty()) {
- DPM.wipeData(flags, reason)
- } else {
- DPM.wipeData(flags)
- }
- }
- }
- fun getSystemUpdatePolicy(): SystemUpdatePolicyInfo {
- val policy = DPM.systemUpdatePolicy
- return SystemUpdatePolicyInfo(
- policy?.policyType ?: -1, policy?.installWindowStart ?: 0, policy?.installWindowEnd ?: 0
- )
- }
- fun setSystemUpdatePolicy(info: SystemUpdatePolicyInfo) {
- val policy = when (info.type) {
- SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC -> SystemUpdatePolicy.createAutomaticInstallPolicy()
- SystemUpdatePolicy.TYPE_INSTALL_WINDOWED ->
- SystemUpdatePolicy.createWindowedInstallPolicy(info.start, info.end)
- SystemUpdatePolicy.TYPE_POSTPONE -> SystemUpdatePolicy.createPostponeInstallPolicy()
- else -> null
- }
- DPM.setSystemUpdatePolicy(DAR, policy)
- }
- @RequiresApi(26)
- fun getPendingSystemUpdate(): PendingSystemUpdateInfo {
- val update = DPM.getPendingSystemUpdate(DAR)
- return PendingSystemUpdateInfo(update != null, update?.receivedTime ?: 0,
- update?.securityPatchState == SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE)
- }
- @RequiresApi(29)
- fun installSystemUpdate(uri: Uri, callback: (String) -> Unit) {
- val callback = object: InstallSystemUpdateCallback() {
- override fun onInstallUpdateError(errorCode: Int, errorMessage: String) {
- super.onInstallUpdateError(errorCode, errorMessage)
- val errDetail = when(errorCode) {
- UPDATE_ERROR_BATTERY_LOW -> R.string.battery_low
- UPDATE_ERROR_UPDATE_FILE_INVALID -> R.string.update_file_invalid
- UPDATE_ERROR_INCORRECT_OS_VERSION -> R.string.incorrect_os_ver
- UPDATE_ERROR_FILE_NOT_FOUND -> R.string.file_not_exist
- else -> R.string.unknown_error
- }
- callback(application.getString(errDetail) + "\n$errorMessage")
- }
- }
- DPM.installSystemUpdate(DAR, uri, application.mainExecutor, callback)
- }
- @RequiresApi(24)
- fun getSecurityLoggingEnabled(): Boolean {
- return DPM.isSecurityLoggingEnabled(DAR)
- }
- @RequiresApi(24)
- fun setSecurityLoggingEnabled(enabled: Boolean) {
- DPM.setSecurityLoggingEnabled(DAR, enabled)
- }
- fun exportSecurityLogs(uri: Uri, callback: () -> Unit) {
- viewModelScope.launch(Dispatchers.IO) {
- application.contentResolver.openOutputStream(uri)?.use {
- myRepo.exportSecurityLogs(it)
- }
- withContext(Dispatchers.Main) {
- callback()
- }
- }
- }
- fun getSecurityLogsCount(): Int {
- return myRepo.getSecurityLogsCount().toInt()
- }
- fun deleteSecurityLogs() {
- myRepo.deleteSecurityLogs()
- }
- var preRebootSecurityLogs = emptyList()
- @RequiresApi(24)
- fun getPreRebootSecurityLogs(): Boolean {
- if (preRebootSecurityLogs.isNotEmpty()) return true
- return try {
- val logs = DPM.retrievePreRebootSecurityLogs(DAR)
- if (logs != null && logs.isNotEmpty()) {
- preRebootSecurityLogs = logs
- true
- } else false
- } catch (_: SecurityException) {
- false
- }
- }
- @RequiresApi(24)
- fun exportPreRebootSecurityLogs(uri: Uri, callback: () -> Unit) {
- viewModelScope.launch(Dispatchers.IO) {
- val stream = application.contentResolver.openOutputStream(uri) ?: return@launch
- myRepo.exportPRSecurityLogs(preRebootSecurityLogs, stream)
- stream.close()
- withContext(Dispatchers.Main) { callback() }
- }
- }
-
- @RequiresApi(24)
- fun isCreatingWorkProfileAllowed(): Boolean {
- return DPM.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
- }
- fun activateDoByShizuku(callback: (Boolean, String?) -> Unit) {
- viewModelScope.launch(Dispatchers.IO) {
- useShizuku(application) { service ->
- try {
- val result = IUserService.Stub.asInterface(service)
- .execute(ACTIVATE_DEVICE_OWNER_COMMAND)
- if (result == null || result.getInt("code", -1) != 0) {
- callback(false, null)
- } else {
- Privilege.updateStatus()
- handlePrivilegeChange(application)
- callback(
- true, result.getString("output") + "\n" + result.getString("error")
- )
- }
- } catch (e: Exception) {
- e.printStackTrace()
- callback(false, null)
- }
- }
- }
- }
- fun activateDoByRoot(callback: (Boolean, String?) -> Unit) {
- Shell.getShell { shell ->
- if(shell.isRoot) {
- val result = Shell.cmd(ACTIVATE_DEVICE_OWNER_COMMAND).exec()
- val output = result.out.joinToString("\n") + "\n" + result.err.joinToString("\n")
- if (result.isSuccess) {
- Privilege.updateStatus()
- handlePrivilegeChange(application)
- }
- callback(result.isSuccess, output)
- } else {
- callback(false, application.getString(R.string.permission_denied))
- }
- }
- }
- @RequiresApi(28)
- fun activateDoByDhizuku(callback: (Boolean, String?) -> Unit) {
- DPM.transferOwnership(DAR, MyAdminComponent, null)
- SP.dhizuku = false
- Privilege.initialize(application)
- handlePrivilegeChange(application)
- callback(true, null)
- }
- fun activateDhizukuMode(callback: (Boolean, String?) -> Unit) {
- fun onSucceed() {
- SP.dhizuku = true
- Privilege.initialize(application)
- handlePrivilegeChange(application)
- callback(true, null)
- }
- if (Dhizuku.init(application)) {
- if (Dhizuku.isPermissionGranted()) {
- onSucceed()
- } else {
- Dhizuku.requestPermission(object : DhizukuRequestPermissionListener() {
- override fun onRequestPermission(grantResult: Int) {
- if (grantResult == PackageManager.PERMISSION_GRANTED) onSucceed()
- else callback(false, application.getString(R.string.dhizuku_permission_not_granted))
- }
- })
- }
- } else {
- callback(false, application.getString(R.string.failed_to_init_dhizuku))
- }
- }
- fun clearDeviceOwner() {
- DPM.clearDeviceOwnerApp(application.packageName)
- }
- @RequiresApi(24)
- fun clearProfileOwner() {
- DPM.clearProfileOwner(MyAdminComponent)
- }
- fun deactivateDhizukuMode() {
- SP.dhizuku = false
- Privilege.initialize(application)
- }
- val dhizukuClients = MutableStateFlow(emptyList>())
- fun getDhizukuClients() {
- viewModelScope.launch(Dispatchers.IO) {
- dhizukuClients.value = myRepo.getDhizukuClients().mapNotNull {
- val packageName = PM.getNameForUid(it.uid)
- if (packageName == null) {
- myRepo.deleteDhizukuClient(it)
- null
- } else {
- it to getAppInfo(packageName)
- }
- }
- }
- }
- fun getDhizukuServerEnabled(): Boolean {
- return SP.dhizukuServer
- }
- fun setDhizukuServerEnabled(status: Boolean) {
- SP.dhizukuServer = status
- }
- fun updateDhizukuClient(info: DhizukuClientInfo) {
- myRepo.setDhizukuClient(info)
- dhizukuClients.update { list ->
- val ml = list.toMutableList()
- val index = ml.indexOfFirst { it.first.uid == info.uid }
- ml[index] = info to ml[index].second
- ml
- }
- }
- @RequiresApi(24)
- fun getLockScreenInfo(): String {
- return DPM.deviceOwnerLockScreenInfo?.toString() ?: ""
- }
- @RequiresApi(24)
- fun setLockScreenInfo(text: String) {
- DPM.setDeviceOwnerLockScreenInfo(DAR, text)
- }
- val delegatedAdmins = MutableStateFlow(emptyList())
- @RequiresApi(26)
- fun getDelegatedAdmins() {
- val list = mutableListOf()
- delegatedScopesList.forEach { scope ->
- DPM.getDelegatePackages(DAR, scope.id)?.forEach { pkg ->
- val index = list.indexOfFirst { it.app.name == pkg }
- if (index == -1) {
- list += DelegatedAdmin(getAppInfo(pkg), listOf(scope.id))
- } else {
- list[index] = DelegatedAdmin(list[index].app, list[index].scopes + scope.id)
- }
- }
- }
- delegatedAdmins.value = list
- }
- @RequiresApi(26)
- fun setDelegatedAdmin(name: String, scopes: List) {
- DPM.setDelegatedScopes(DAR, name, scopes)
- getDelegatedAdmins()
- }
- @RequiresApi(34)
- fun getDeviceFinanced(): Boolean {
- return DPM.isDeviceFinanced
- }
- @RequiresApi(33)
- fun getDpmRh(): String? {
- return DPM.devicePolicyManagementRoleHolderPackage
- }
- fun getStorageEncryptionStatus(): Int {
- return DPM.storageEncryptionStatus
- }
- @RequiresApi(28)
- fun getDeviceIdAttestationSupported(): Boolean {
- return DPM.isDeviceIdAttestationSupported
- }
- @RequiresApi(30)
- fun getUniqueDeviceAttestationSupported(): Boolean {
- return DPM.isUniqueDeviceAttestationSupported
- }
- fun getActiveAdmins(): String {
- return DPM.activeAdmins?.joinToString("\n") {
- it.flattenToShortString()
- } ?: application.getString(R.string.none)
- }
- @RequiresApi(24)
- fun getShortSupportMessage(): String {
- return DPM.getShortSupportMessage(DAR)?.toString() ?: ""
- }
- @RequiresApi(24)
- fun getLongSupportMessage(): String {
- return DPM.getLongSupportMessage(DAR)?.toString() ?: ""
- }
- @RequiresApi(24)
- fun setShortSupportMessage(text: String?) {
- DPM.setShortSupportMessage(DAR, text)
- }
- @RequiresApi(24)
- fun setLongSupportMessage(text: String?) {
- DPM.setLongSupportMessage(DAR, text)
- }
- val deviceAdminReceivers = MutableStateFlow(emptyList())
- fun getDeviceAdminReceivers() {
- viewModelScope.launch(Dispatchers.IO) {
- deviceAdminReceivers.value = PM.queryBroadcastReceivers(
- Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
- PackageManager.GET_META_DATA
- ).mapNotNull {
- try {
- DeviceAdminInfo(application, it)
- } catch(_: Exception) {
- null
- }
- }.filter {
- it.isVisible && it.packageName != "com.bintianqi.owndroid" &&
- it.activityInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 0
- }.map {
- DeviceAdmin(getAppInfo(it.packageName), it.component)
- }
- }
- }
- @RequiresApi(28)
- fun transferOwnership(component: ComponentName) {
- DPM.transferOwnership(DAR, component, null)
- Privilege.updateStatus()
- }
- val userRestrictions = MutableStateFlow(emptyMap())
- @RequiresApi(24)
- fun getUserRestrictions() {
- val bundle = DPM.getUserRestrictions(DAR)
- userRestrictions.value = bundle.keySet().associateWith { bundle.getBoolean(it) }
- }
- fun setUserRestriction(name: String, state: Boolean): Boolean {
- return try {
- if (state) {
- DPM.addUserRestriction(DAR, name)
- } else {
- DPM.clearUserRestriction(DAR, name)
- }
- userRestrictions.update { it.plus(name to state) }
- ShortcutUtils.updateUserRestrictionShortcut(application, name, !state, true)
- true
- } catch (_: SecurityException) {
- false
- }
- }
- fun createUserRestrictionShortcut(id: String): Boolean {
- return ShortcutUtils.setUserRestrictionShortcut(
- application, id, userRestrictions.value[id] ?: true
- )
- }
- fun createWorkProfile(options: CreateWorkProfileOptions): Intent {
- val intent = Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
- intent.putExtra(
- DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
- MyAdminComponent
- )
- if (options.migrateAccount) {
- intent.putExtra(
- DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE,
- Account(options.accountName, options.accountType)
- )
- if (VERSION.SDK_INT >= 26) {
- intent.putExtra(
- DevicePolicyManager.EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION,
- options.keepAccount
- )
- }
- }
- if (VERSION.SDK_INT >= 24) {
- intent.putExtra(
- DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION,
- options.skipEncrypt
- )
- }
- if (VERSION.SDK_INT >= 33) {
- intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_ALLOW_OFFLINE, options.offline)
- }
- return intent
- }
- fun activateOrgProfileByShizuku(callback: (Boolean) -> Unit) {
- viewModelScope.launch(Dispatchers.IO) {
- var succeed = false
- useShizuku(application) { service ->
- val result = IUserService.Stub.asInterface(service).execute(activateOrgProfileCommand)
- succeed = result?.getInt("code", -1) == 0
- callback(succeed)
- }
- if (succeed) Privilege.updateStatus()
- }
- }
- @RequiresApi(30)
- fun getPersonalAppsSuspendedReason(): Int {
- return DPM.getPersonalAppsSuspendedReasons(DAR)
- }
- @RequiresApi(30)
- fun setPersonalAppsSuspended(suspended: Boolean) {
- DPM.setPersonalAppsSuspended(DAR, suspended)
- }
- @RequiresApi(30)
- fun getProfileMaxTimeOff(): Long {
- return DPM.getManagedProfileMaximumTimeOff(DAR)
- }
- @RequiresApi(30)
- fun setProfileMaxTimeOff(time: Long) {
- DPM.setManagedProfileMaximumTimeOff(DAR, time)
- }
- fun addCrossProfileIntentFilter(options: IntentFilterOptions) {
- val filter = IntentFilter(options.action)
- if (options.category.isNotEmpty()) filter.addCategory(options.category)
- if (options.mimeType.isNotEmpty()) filter.addDataType(options.mimeType)
- val flags = when(options.direction) {
- IntentFilterDirection.ToManaged -> DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED
- IntentFilterDirection.ToParent -> DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT
- IntentFilterDirection.Both -> DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED or
- DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT
- }
- DPM.addCrossProfileIntentFilter(DAR, filter, flags)
- }
-
- val UM = application.getSystemService(Context.USER_SERVICE) as UserManager
- @RequiresApi(28)
- fun getLogoutEnabled(): Boolean {
- return DPM.isLogoutEnabled
- }
- @RequiresApi(28)
- fun setLogoutEnabled(enabled: Boolean) {
- DPM.setLogoutEnabled(DAR, enabled)
- }
- fun getUserInformation(): UserInformation {
- val uh = Binder.getCallingUserHandle()
- return UserInformation(
- if (VERSION.SDK_INT >= 24) UserManager.supportsMultipleUsers() else false,
- if (VERSION.SDK_INT >= 31) UserManager.isHeadlessSystemUserMode() else false,
- UM.isSystemUser,
- if (VERSION.SDK_INT >= 34) UM.isAdminUser else false,
- if (VERSION.SDK_INT >= 25) UM.isDemoUser else false,
- UM.getUserCreationTime(uh),
- if (VERSION.SDK_INT >= 28) DPM.isLogoutEnabled else false,
- if (VERSION.SDK_INT >= 28) DPM.isEphemeralUser(DAR) else false,
- if (VERSION.SDK_INT >= 28) DPM.isAffiliatedUser else false,
- UM.getSerialNumberForUser(uh)
- )
- }
- @Suppress("PrivateApi")
- @RequiresApi(28)
- fun getUserIdentifiers(): List {
- return DPM.getSecondaryUsers(DAR)?.mapNotNull {
- try {
- val field = UserHandle::class.java.getDeclaredField("mHandle")
- field.isAccessible = true
- UserIdentifier(field.get(it) as Int, UM.getSerialNumberForUser(it))
- } catch (e: Exception) {
- e.printStackTrace()
- null
- }
- } ?: emptyList()
- }
- fun doUserOperation(type: UserOperationType, id: Int, isUserId: Boolean): Boolean {
- return doUserOperationWithContext(application, type, id, isUserId)
- }
- fun createUserOperationShortcut(type: UserOperationType, id: Int, isUserId: Boolean): Boolean {
- val serial = if (isUserId && VERSION.SDK_INT >= 24) {
- UM.getSerialNumberForUser(UserHandle.getUserHandleForUid(id * 100000))
- } else id
- return ShortcutUtils.setUserOperationShortcut(application, type, serial.toInt())
- }
- fun getUserOperationResultText(code: Int): Int {
- return when (code) {
- UserManager.USER_OPERATION_SUCCESS -> R.string.success
- UserManager.USER_OPERATION_ERROR_UNKNOWN -> R.string.unknown_error
- UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE-> R.string.fail_managed_profile
- UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS -> R.string.limit_reached
- UserManager.USER_OPERATION_ERROR_MAX_USERS -> R.string.limit_reached
- UserManager.USER_OPERATION_ERROR_CURRENT_USER -> R.string.fail_current_user
- else -> R.string.unknown
- }
- }
- @RequiresApi(24)
- fun createUser(name: String, flags: Int, callback: (CreateUserResult) -> Unit) {
- viewModelScope.launch(Dispatchers.IO) {
- try {
- val uh = DPM.createAndManageUser(DAR, name, DAR, null, flags)
- if (uh == null) {
- callback(CreateUserResult(R.string.failed))
- } else {
- callback(CreateUserResult(R.string.succeeded, UM.getSerialNumberForUser(uh)))
- }
- } catch (e: Exception) {
- e.printStackTrace()
- if (VERSION.SDK_INT >= 28 && e is UserManager.UserOperationException) {
- callback(CreateUserResult(getUserOperationResultText(e.userOperationResult)))
- } else {
- callback(CreateUserResult(R.string.error))
- }
- }
- }
- }
- val affiliationIds = MutableStateFlow(emptyList())
- @RequiresApi(26)
- fun getAffiliationIds() {
- affiliationIds.value = DPM.getAffiliationIds(DAR).toList()
- }
- @RequiresApi(26)
- fun setAffiliationId(id: String, state: Boolean) {
- val newList = affiliationIds.value.run { if (state) plus(id) else minus(id) }
- DPM.setAffiliationIds(DAR, newList.toSet())
- affiliationIds.value = newList
- }
- fun setProfileName(name: String) {
- DPM.setProfileName(DAR, name)
- }
- fun setUserIcon(bitmap: Bitmap) {
- DPM.setUserIcon(DAR, bitmap)
- }
- @RequiresApi(28)
- fun getUserSessionMessages(): Pair {
- return (DPM.getStartUserSessionMessage(DAR)?.toString() ?: "") to
- (DPM.getEndUserSessionMessage(DAR)?.toString() ?: "")
- }
- @RequiresApi(28)
- fun setStartUserSessionMessage(message: String?) {
- DPM.setStartUserSessionMessage(DAR, message)
- }
- @RequiresApi(28)
- fun setEndUserSessionMessage(message: String?) {
- DPM.setEndUserSessionMessage(DAR, message)
- }
- @RequiresApi(28)
- fun logoutUser(): Int {
- return getUserOperationResultText(DPM.logoutUser(DAR))
- }
-
- val WM = application.getSystemService(Context.WIFI_SERVICE) as WifiManager
- // Lockdown admin configured networks
- @RequiresApi(30)
- fun getLanEnabled(): Boolean {
- return DPM.hasLockdownAdminConfiguredNetworks(DAR)
- }
- @RequiresApi(30)
- fun setLanEnabled(state: Boolean) {
- DPM.setConfiguredNetworksLockdownState(DAR, state)
- }
- fun setWifiEnabled(enabled: Boolean): Boolean {
- return WM.setWifiEnabled(enabled)
- }
- fun disconnectWifi(): Boolean {
- return WM.disconnect()
- }
- fun reconnectWifi(): Boolean {
- return WM.reconnect()
- }
- @RequiresApi(24)
- fun getWifiMac(): String? {
- return DPM.getWifiMacAddress(DAR)
- }
- val configuredNetworks = MutableStateFlow(emptyList())
- fun getConfiguredNetworks() {
- configuredNetworks.value = WM.configuredNetworks.distinctBy { it.networkId }.map { conf ->
- WifiInfo(
- conf.networkId, conf.SSID.removeSurrounding("\""), null, conf.BSSID ?: "", null,
- WifiStatus.entries.find { it.id == conf.status }!!, null, "", null, null, null, null
- )
- }
- }
- fun enableNetwork(id: Int): Boolean {
- return WM.enableNetwork(id, false)
- }
- fun disableNetwork(id: Int): Boolean {
- return WM.disableNetwork(id)
- }
- fun removeNetwork(id: Int): Boolean{
- return WM.removeNetwork(id)
- }
- fun setWifi(info: WifiInfo): Boolean {
- val conf = WifiConfiguration()
- conf.SSID = "\"" + info.ssid + "\""
- info.hiddenSsid?.let { conf.hiddenSSID = it }
- if (VERSION.SDK_INT >= 30) info.security?.let { conf.setSecurityParams(it.id) }
- if (info.security == WifiSecurity.Psk) conf.preSharedKey = info.password
- if (VERSION.SDK_INT >= 33) info.macRandomization?.let { conf.macRandomizationSetting = it.id }
- if (VERSION.SDK_INT >= 33 && info.ipMode != null) {
- val ipConf = if (info.ipMode == IpMode.Static && info.ipConf != null) {
- val constructor = LinkAddress::class.constructors.find {
- it.parameters.size == 1 && it.parameters[0].type.jvmErasure == String::class
- }
- val address = constructor!!.call(info.ipConf.address)
- val staticIpConf = StaticIpConfiguration.Builder()
- .setIpAddress(address)
- .setGateway(InetAddress.getByName(info.ipConf.gateway))
- .setDnsServers(info.ipConf.dns.map { InetAddress.getByName(it) })
- .build()
- IpConfiguration.Builder().setStaticIpConfiguration(staticIpConf).build()
- } else null
- conf.setIpConfiguration(ipConf)
- }
- if (VERSION.SDK_INT >= 26 && info.proxyMode != null) {
- val proxy = if (info.proxyMode == ProxyMode.Http) {
- info.proxyConf?.let {
- ProxyInfo.buildDirectProxy(it.host, it.port, it.exclude)
- }
- } else null
- conf.httpProxy = proxy
- }
- val result = if (info.id != -1) {
- conf.networkId = info.id
- WM.updateNetwork(conf)
- } else {
- WM.addNetwork(conf)
- }
- if (result != -1) {
- when (info.status) {
- WifiStatus.Current -> WM.enableNetwork(result, true)
- WifiStatus.Enabled -> WM.enableNetwork(result, false)
- WifiStatus.Disabled -> WM.disableNetwork(result)
- }
- }
- return result != -1
- }
- @RequiresApi(33)
- fun getMinimumWifiSecurityLevel(): Int {
- return DPM.minimumRequiredWifiSecurityLevel
- }
- @RequiresApi(33)
- fun setMinimumWifiSecurityLevel(level: Int) {
- DPM.minimumRequiredWifiSecurityLevel = level
- }
- @RequiresApi(33)
- fun getSsidPolicy(): SsidPolicy {
- val policy = DPM.wifiSsidPolicy
- return SsidPolicy(
- SsidPolicyType.entries.find { it.id == policy?.policyType } ?: SsidPolicyType.None,
- policy?.ssids?.map { it.bytes.decodeToString() } ?: emptyList()
- )
- }
- @RequiresApi(33)
- fun setSsidPolicy(policy: SsidPolicy) {
- val newPolicy = if (policy.type != SsidPolicyType.None) {
- WifiSsidPolicy(
- policy.type.id, policy.list.map { WifiSsid.fromBytes(it.encodeToByteArray()) }.toSet()
- )
- } else null
- DPM.wifiSsidPolicy = newPolicy
- }
- @RequiresApi(24)
- fun getPackageUid(name: String): Int {
- return PM.getPackageUid(name, 0)
- }
- var networkStatsData = emptyList()
- fun readNetworkStats(stats: NetworkStats): List {
- val list = mutableListOf()
- while (stats.hasNextBucket()) {
- val bucket = NetworkStats.Bucket()
- stats.getNextBucket(bucket)
- list += readNetworkStatsBucket(bucket)
- }
- stats.close()
- return list
- }
- fun readNetworkStatsBucket(bucket: NetworkStats.Bucket): NetworkStatsData {
- return NetworkStatsData(
- bucket.rxBytes, bucket.rxPackets, bucket.txBytes, bucket.txPackets,
- bucket.uid, bucket.state, bucket.startTimeStamp, bucket.endTimeStamp,
- if (VERSION.SDK_INT >= 24) bucket.tag else null,
- if (VERSION.SDK_INT >= 24) bucket.roaming else null,
- if (VERSION.SDK_INT >= 26) bucket.metered else null
- )
- }
- @Suppress("NewApi")
- fun queryNetworkStats(params: QueryNetworkStatsParams, callback: (String?) -> Unit) {
- viewModelScope.launch(Dispatchers.IO) {
- val nsm = application.getSystemService(NetworkStatsManager::class.java)
- try {
- val data = when (params.target) {
- NetworkStatsTarget.Device -> listOf(readNetworkStatsBucket(
- nsm.querySummaryForDevice(
- params.networkType.type, null, params.startTime, params.endTime
- )
- ))
- NetworkStatsTarget.User -> listOf(readNetworkStatsBucket(
- nsm.querySummaryForUser(
- params.networkType.type, null, params.startTime, params.endTime
- )
- ))
- NetworkStatsTarget.Uid -> readNetworkStats(nsm.queryDetailsForUid(
- params.networkType.type, null, params.startTime, params.endTime, params.uid
- ))
- NetworkStatsTarget.UidTag -> readNetworkStats(nsm.queryDetailsForUidTag(
- params.networkType.type, null, params.startTime, params.endTime,
- params.uid, params.tag
- ))
- NetworkStatsTarget.UidTagState -> readNetworkStats(
- nsm.queryDetailsForUidTagState(
- params.networkType.type, null, params.startTime, params.endTime,
- params.uid, params.tag, params.state.id
- )
- )
- }
- networkStatsData = data
- withContext(Dispatchers.Main) {
- if (data.isEmpty()) {
- callback(application.getString(R.string.no_data))
- } else {
- callback(null)
- }
- }
- } catch(e: Exception) {
- e.printStackTrace()
- withContext(Dispatchers.Main) {
- callback(e.message ?: "")
- }
- }
- }
- }
- fun clearNetworkStats() {
- networkStatsData = emptyList()
- }
- @RequiresApi(29)
- fun getPrivateDns(): PrivateDnsConfiguration {
- val mode = DPM.getGlobalPrivateDnsMode(DAR)
- return PrivateDnsConfiguration(
- PrivateDnsMode.entries.find { it.id == mode }, DPM.getGlobalPrivateDnsHost(DAR) ?: ""
- )
- }
- @Suppress("PrivateApi")
- @RequiresApi(29)
- fun setPrivateDns(conf: PrivateDnsConfiguration): Boolean {
- return try {
- val field = DevicePolicyManager::class.java.getDeclaredField("mService")
- field.isAccessible = true
- val dpm = field.get(DPM) as IDevicePolicyManager
- val host = if (conf.mode == PrivateDnsMode.Host) conf.host else null
- val result = dpm.setGlobalPrivateDns(DAR, conf.mode!!.id, host)
- result == DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR
- } catch (e: Exception) {
- e.printStackTrace()
- false
- }
- }
- @RequiresApi(24)
- fun getAlwaysOnVpnPackage(): String {
- return DPM.getAlwaysOnVpnPackage(DAR) ?: ""
- }
- @RequiresApi(29)
- fun getAlwaysOnVpnLockdown(): Boolean {
- return DPM.isAlwaysOnVpnLockdownEnabled(DAR)
- }
- @RequiresApi(24)
- fun setAlwaysOnVpn(name: String?, lockdown: Boolean): Int {
- return try {
- DPM.setAlwaysOnVpnPackage(DAR, name, lockdown)
- R.string.succeeded
- } catch (_: UnsupportedOperationException) {
- R.string.unsupported
- } catch (_: PackageManager.NameNotFoundException) {
- R.string.not_installed
- }
- }
- fun setRecommendedGlobalProxy(conf: RecommendedProxyConf) {
- val info = when (conf.type) {
- ProxyType.Off -> null
- ProxyType.Pac -> {
- if (VERSION.SDK_INT >= 30 && conf.specifyPort) {
- ProxyInfo.buildPacProxy(conf.url.toUri(), conf.port)
- } else {
- ProxyInfo.buildPacProxy(conf.url.toUri())
- }
- }
- ProxyType.Direct -> {
- ProxyInfo.buildDirectProxy(conf.host, conf.port, conf.exclude)
- }
- }
- DPM.setRecommendedGlobalProxy(DAR, info)
- }
- // PNS: preferential network service
- @RequiresApi(31)
- fun getPnsEnabled(): Boolean {
- return DPM.isPreferentialNetworkServiceEnabled
- }
- @RequiresApi(31)
- fun setPnsEnabled(enabled: Boolean) {
- DPM.isPreferentialNetworkServiceEnabled = enabled
- }
- val pnsConfigs = MutableStateFlow(emptyList())
- @RequiresApi(33)
- fun getPnsConfigs() {
- pnsConfigs.value = DPM.preferentialNetworkServiceConfigs.map {
- PreferentialNetworkServiceInfo(
- it.isEnabled, it.networkId, it.isFallbackToDefaultConnectionAllowed,
- if (VERSION.SDK_INT >= 34) it.shouldBlockNonMatchingNetworks() else false,
- it.excludedUids.toList(), it.includedUids.toList()
- )
- }
- }
- @RequiresApi(33)
- fun buildPnsConfig(
- info: PreferentialNetworkServiceInfo
- ): PreferentialNetworkServiceConfig {
- return PreferentialNetworkServiceConfig.Builder().apply {
- setEnabled(info.enabled)
- @Suppress("WrongConstant")
- setNetworkId(info.id)
- setFallbackToDefaultConnectionAllowed(info.allowFallback)
- if (VERSION.SDK_INT >= 34) setShouldBlockNonMatchingNetworks(info.blockNonMatching)
- setIncludedUids(info.includedUids.toIntArray())
- setExcludedUids(info.excludedUids.toIntArray())
- }.build()
- }
- @RequiresApi(33)
- fun setPnsConfig(info: PreferentialNetworkServiceInfo, state: Boolean) {
- val configs = pnsConfigs.value.run {
- if (state) plus(info) else minus(info)
- }.map { buildPnsConfig(it) }
- DPM.preferentialNetworkServiceConfigs = configs
- }
- val apnConfigs = MutableStateFlow(listOf())
- @RequiresApi(28)
- fun getApnEnabled(): Boolean {
- return DPM.isOverrideApnEnabled(DAR)
- }
- @RequiresApi(28)
- fun setApnEnabled(enabled: Boolean) {
- DPM.setOverrideApnsEnabled(DAR, enabled)
- }
- @RequiresApi(28)
- fun getApnConfigs() {
- apnConfigs.value = DPM.getOverrideApns(DAR).map {
- val proxy = if (VERSION.SDK_INT >= 29) it.proxyAddressAsString else it.proxyAddress.hostName
- val mmsProxy = if (VERSION.SDK_INT >= 29) it.mmsProxyAddressAsString else it.mmsProxyAddress.hostName
- ApnConfig(
- it.isEnabled, it.entryName, it.apnName, proxy, it.proxyPort,
- it.user, it.password, it.apnTypeBitmask, it.mmsc.toString(),
- mmsProxy, it.mmsProxyPort,
- ApnAuthType.entries.find { type -> type.id == it.authType }!!,
- ApnProtocol.entries.find { protocol -> protocol.id == it.protocol }!!,
- ApnProtocol.entries.find { protocol -> protocol.id == it.roamingProtocol }!!,
- it.networkTypeBitmask,
- if (VERSION.SDK_INT >= 33) it.profileId else 0,
- if (VERSION.SDK_INT >= 29) it.carrierId else 0,
- if (VERSION.SDK_INT >= 33) it.mtuV4 else 0,
- if (VERSION.SDK_INT >= 33) it.mtuV6 else 0,
- ApnMvnoType.entries.find { type -> type.id == it.mvnoType }!!,
- it.operatorNumeric,
- if (VERSION.SDK_INT >= 33) it.isPersistent else true,
- if (VERSION.SDK_INT >= 35) it.isAlwaysOn else true,
- it.id
- )
- }
- }
- @RequiresApi(28)
- fun buildApnSetting(config: ApnConfig): ApnSetting? {
- val builder = ApnSetting.Builder()
- builder.setCarrierEnabled(config.enabled)
- builder.setEntryName(config.name)
- builder.setApnName(config.apn)
- if (VERSION.SDK_INT >= 29) builder.setProxyAddress(config.proxy)
- else builder.setProxyAddress(InetAddress.getByName(config.proxy))
- config.port?.let { builder.setProxyPort(it) }
- builder.setUser(config.username)
- builder.setPassword(config.password)
- builder.setApnTypeBitmask(config.apnType)
- builder.setMmsc(config.mmsc.toUri())
- if (VERSION.SDK_INT >= 29) builder.setMmsProxyAddress(config.mmsProxy)
- else builder.setMmsProxyAddress(InetAddress.getByName(config.mmsProxy))
- builder.setAuthType(config.authType.id)
- builder.setProtocol(config.protocol.id)
- builder.setRoamingProtocol(config.roamingProtocol.id)
- builder.setNetworkTypeBitmask(config.networkType)
- if (VERSION.SDK_INT >= 33) config.profileId?.let { builder.setProfileId(it) }
- if (VERSION.SDK_INT >= 29) config.carrierId?.let { builder.setCarrierId(it) }
- if (VERSION.SDK_INT >= 33) {
- config.mtuV4?.let { builder.setMtuV4(it) }
- config.mtuV6?.let { builder.setMtuV6(it) }
- }
- builder.setMvnoType(config.mvno.id)
- builder.setOperatorNumeric(config.operatorNumeric)
- if (VERSION.SDK_INT >= 33) builder.setPersistent(config.persistent)
- if (VERSION.SDK_INT >= 35) builder.setAlwaysOn(config.alwaysOn)
- return builder.build()
- }
- @RequiresApi(28)
- fun setApnConfig(config: ApnConfig): Boolean {
- val settings = buildApnSetting(config)
- if (settings == null) return false
- return if (config.id == -1) {
- DPM.addOverrideApn(DAR, settings) != -1
- } else {
- DPM.updateOverrideApn(DAR, config.id, settings)
- }
- }
- @RequiresApi(28)
- fun removeApnConfig(id: Int): Boolean {
- return DPM.removeOverrideApn(DAR, id)
- }
- @RequiresApi(26)
- fun getNetworkLoggingEnabled(): Boolean {
- return DPM.isNetworkLoggingEnabled(DAR)
- }
- @RequiresApi(26)
- fun setNetworkLoggingEnabled(enabled: Boolean) {
- DPM.setNetworkLoggingEnabled(DAR, enabled)
- }
- fun getNetworkLogsCount(): Int {
- return myRepo.getNetworkLogsCount().toInt()
- }
- fun exportNetworkLogs(uri: Uri, callback: () -> Unit) {
- viewModelScope.launch(Dispatchers.IO) {
- application.contentResolver.openOutputStream(uri)?.use {
- myRepo.exportNetworkLogs(it)
- }
- withContext(Dispatchers.Main) { callback() }
- }
- }
- fun deleteNetworkLogs() {
- myRepo.deleteNetworkLogs()
- }
-
- @RequiresApi(29)
- fun getPasswordComplexity(): PasswordComplexity {
- val complexity = DPM.passwordComplexity
- return PasswordComplexity.entries.find { it.id == complexity }!!
- }
- fun isPasswordComplexitySufficient(): Boolean {
- return DPM.isActivePasswordSufficient
- }
- @RequiresApi(28)
- fun isUsingUnifiedPassword(): Boolean {
- return DPM.isUsingUnifiedPassword(DAR)
- }
- // Reset password token
- @RequiresApi(26)
- fun getRpTokenState(): RpTokenState {
- return try {
- RpTokenState(true, DPM.isResetPasswordTokenActive(DAR))
- } catch (_: IllegalStateException) {
- RpTokenState(false, false)
- }
- }
- @RequiresApi(26)
- fun setRpToken(token: String): Boolean {
- return try {
- DPM.setResetPasswordToken(DAR, token.encodeToByteArray())
- } catch (e: Exception) {
- e.printStackTrace()
- false
- }
- }
- @RequiresApi(26)
- fun clearRpToken(): Boolean {
- return DPM.clearResetPasswordToken(DAR)
- }
- @RequiresApi(26)
- fun createActivateRpTokenIntent(): Intent? {
- val km = application.getSystemService(KeyguardManager::class.java)
- val title = application.getString(R.string.activate_reset_password_token)
- return km.createConfirmDeviceCredentialIntent(title, "")
- }
- fun resetPassword(password: String, token: String, flags: Int): Boolean {
- return if (VERSION.SDK_INT >= 26) {
- DPM.resetPasswordWithToken(DAR, password, token.encodeToByteArray(), flags)
- } else {
- DPM.resetPassword(password, flags)
- }
- }
- @RequiresApi(31)
- fun getRequiredPasswordComplexity(): PasswordComplexity {
- val complexity = DPM.requiredPasswordComplexity
- return PasswordComplexity.entries.find { it.id == complexity }!!
- }
- @RequiresApi(31)
- fun setRequiredPasswordComplexity(complexity: PasswordComplexity) {
- DPM.requiredPasswordComplexity = complexity.id
- }
- fun getKeyguardDisableConfig(): KeyguardDisableConfig {
- val flags = DPM.getKeyguardDisabledFeatures(DAR)
- val mode = when (flags) {
- DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE -> KeyguardDisableMode.None
- DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL -> KeyguardDisableMode.All
- else -> KeyguardDisableMode.Custom
- }
- return KeyguardDisableConfig(mode, flags)
- }
- fun setKeyguardDisableConfig(config: KeyguardDisableConfig) {
- val flags = when (config.mode) {
- KeyguardDisableMode.None -> DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
- KeyguardDisableMode.All -> DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
- else -> config.flags
- }
- DPM.setKeyguardDisabledFeatures(DAR, flags)
- }
- fun getMaxTimeToLock(): Long {
- return DPM.getMaximumTimeToLock(DAR)
- }
- @RequiresApi(26)
- fun getRequiredStrongAuthTimeout(): Long {
- return DPM.getRequiredStrongAuthTimeout(DAR)
- }
- fun getPasswordExpirationTimeout(): Long {
- return DPM.getPasswordExpirationTimeout(DAR)
- }
- fun getMaxFailedPasswordsForWipe(): Int {
- return DPM.getMaximumFailedPasswordsForWipe(DAR)
- }
- fun getPasswordHistoryLength(): Int {
- return DPM.getPasswordHistoryLength(DAR)
- }
- fun setMaxTimeToLock(time: Long) {
- DPM.setMaximumTimeToLock(DAR, time)
- }
- @RequiresApi(26)
- fun setRequiredStrongAuthTimeout(time: Long) {
- DPM.setRequiredStrongAuthTimeout(DAR, time)
- }
- fun setPasswordExpirationTimeout(time: Long) {
- DPM.setPasswordExpirationTimeout(DAR, time)
- }
- fun setMaxFailedPasswordsForWipe(times: Int) {
- DPM.setMaximumFailedPasswordsForWipe(DAR, times)
- }
- fun setPasswordHistoryLength(length: Int) {
- DPM.setPasswordHistoryLength(DAR, length)
- }
-}
-
-data class ThemeSettings(
- val materialYou: Boolean = false,
- val darkTheme: Int = -1,
- val blackTheme: Boolean = false
-)
diff --git a/app/src/main/java/com/bintianqi/owndroid/MyViewModelFactory.kt b/app/src/main/java/com/bintianqi/owndroid/MyViewModelFactory.kt
new file mode 100644
index 0000000..7c67269
--- /dev/null
+++ b/app/src/main/java/com/bintianqi/owndroid/MyViewModelFactory.kt
@@ -0,0 +1,150 @@
+package com.bintianqi.owndroid
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.bintianqi.owndroid.feature.applications.AppChooserViewModel
+import com.bintianqi.owndroid.feature.applications.AppFeaturesViewModel
+import com.bintianqi.owndroid.feature.applications.AppGroup
+import com.bintianqi.owndroid.feature.applications.AppGroupRepository
+import com.bintianqi.owndroid.feature.applications.AppGroupViewModel
+import com.bintianqi.owndroid.feature.network.NetworkLoggingRepository
+import com.bintianqi.owndroid.feature.network.NetworkLoggingViewModel
+import com.bintianqi.owndroid.feature.network.NetworkStatsViewModel
+import com.bintianqi.owndroid.feature.network.NetworkViewModel
+import com.bintianqi.owndroid.feature.network.OverrideApnViewModel
+import com.bintianqi.owndroid.feature.network.PreferentialNetworkViewModel
+import com.bintianqi.owndroid.feature.network.WifiViewModel
+import com.bintianqi.owndroid.feature.password.PasswordViewModel
+import com.bintianqi.owndroid.feature.privilege.DelegatedAdminsViewModel
+import com.bintianqi.owndroid.feature.privilege.DhizukuServerRepository
+import com.bintianqi.owndroid.feature.privilege.DhizukuServerViewModel
+import com.bintianqi.owndroid.feature.privilege.TransferOwnershipViewModel
+import com.bintianqi.owndroid.feature.privilege.WorkingModesViewModel
+import com.bintianqi.owndroid.feature.settings.MySettings
+import com.bintianqi.owndroid.feature.settings.SettingsRepository
+import com.bintianqi.owndroid.feature.settings.SettingsViewModel
+import com.bintianqi.owndroid.feature.system.CaCertViewModel
+import com.bintianqi.owndroid.feature.system.HardwareMonitorViewModel
+import com.bintianqi.owndroid.feature.system.LockTaskModeViewModel
+import com.bintianqi.owndroid.feature.system.SecurityLoggingRepository
+import com.bintianqi.owndroid.feature.system.SecurityLoggingViewModel
+import com.bintianqi.owndroid.feature.system.SystemOptionsViewModel
+import com.bintianqi.owndroid.feature.system.SystemUpdateViewModel
+import com.bintianqi.owndroid.feature.system.SystemViewModel
+import com.bintianqi.owndroid.feature.system.TimeViewModel
+import com.bintianqi.owndroid.feature.user_restriction.UserRestrictionViewModel
+import com.bintianqi.owndroid.feature.users.UsersViewModel
+import com.bintianqi.owndroid.feature.work_profile.CrossProfileIntentFilterRepository
+import com.bintianqi.owndroid.feature.work_profile.CrossProfileIntentFilterViewModel
+import com.bintianqi.owndroid.feature.work_profile.WorkProfileViewModel
+import com.bintianqi.owndroid.utils.DhizukuError
+import com.bintianqi.owndroid.utils.PrivilegeStatus
+import com.bintianqi.owndroid.utils.ToastChannel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlin.reflect.KClass
+
+class MyViewModelFactory(
+ val app: MyApplication, val ph: PrivilegeHelper,
+ val sr: SettingsRepository, val nlRepo: NetworkLoggingRepository,
+ val dsRepo: DhizukuServerRepository, val slRepo: SecurityLoggingRepository,
+ val agRepo: AppGroupRepository, val cpifRepo: CrossProfileIntentFilterRepository,
+ val agState: MutableStateFlow>,
+ val de: MutableStateFlow, val ps: MutableStateFlow,
+ val ts: MutableStateFlow, val tc: ToastChannel
+) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ fun checkType(cls: KClass<*>): Boolean {
+ return cls.java.isAssignableFrom(modelClass)
+ }
+ if (checkType(NetworkStatsViewModel::class)) {
+ return NetworkStatsViewModel(app, ps) as T
+ }
+ if (checkType(WifiViewModel::class)) {
+ return WifiViewModel(app, ph, tc, ps) as T
+ }
+ if (checkType(OverrideApnViewModel::class)) {
+ return OverrideApnViewModel(ph, tc) as T
+ }
+ if (checkType(PreferentialNetworkViewModel::class)) {
+ return PreferentialNetworkViewModel(ph) as T
+ }
+ if (checkType(NetworkLoggingViewModel::class)) {
+ return NetworkLoggingViewModel(app, ph, nlRepo) as T
+ }
+ if (checkType(NetworkViewModel::class)) {
+ return NetworkViewModel(ph, tc, ps) as T
+ }
+
+ if (checkType(DelegatedAdminsViewModel::class)) {
+ return DelegatedAdminsViewModel(app, ph) as T
+ }
+ if (checkType(TransferOwnershipViewModel::class)) {
+ return TransferOwnershipViewModel(app, ph, ps) as T
+ }
+ if (checkType(WorkingModesViewModel::class)) {
+ return WorkingModesViewModel(app, ph, sr, ps, tc) as T
+ }
+ if (checkType(DhizukuServerViewModel::class)) {
+ return DhizukuServerViewModel(app, dsRepo, sr) as T
+ }
+
+ if (checkType(SecurityLoggingViewModel::class)) {
+ return SecurityLoggingViewModel(app, ph, slRepo, tc) as T
+ }
+ if (checkType(CaCertViewModel::class)) {
+ return CaCertViewModel(app, ph, tc) as T
+ }
+ if (checkType(LockTaskModeViewModel::class)) {
+ return LockTaskModeViewModel(app, ph, tc) as T
+ }
+ if (checkType(SystemOptionsViewModel::class)) {
+ return SystemOptionsViewModel(app, ph, sr, ps) as T
+ }
+ if (checkType(SystemUpdateViewModel::class)) {
+ return SystemUpdateViewModel(app, ph, tc) as T
+ }
+ if (checkType(HardwareMonitorViewModel::class)) {
+ return HardwareMonitorViewModel(app) as T
+ }
+ if (checkType(TimeViewModel::class)) {
+ return TimeViewModel(ph, tc) as T
+ }
+ if (checkType(SystemViewModel::class)) {
+ return SystemViewModel(app, ph, sr, ps, tc) as T
+ }
+
+ if (checkType(AppGroupViewModel::class)) {
+ return AppGroupViewModel(app, agRepo, agState) as T
+ }
+ if (checkType(AppFeaturesViewModel::class)) {
+ return AppFeaturesViewModel(app, ph, ps, tc) as T
+ }
+ if (checkType(AppChooserViewModel::class)) {
+ return AppChooserViewModel(app) as T
+ }
+
+ if (checkType(WorkProfileViewModel::class)) {
+ return WorkProfileViewModel(ph, ps, tc) as T
+ }
+ if (checkType(CrossProfileIntentFilterViewModel::class)) {
+ return CrossProfileIntentFilterViewModel(app, ph, cpifRepo, tc) as T
+ }
+
+ if (checkType(UserRestrictionViewModel::class)) {
+ return UserRestrictionViewModel(app, ph, sr, ps, tc) as T
+ }
+
+ if (checkType(UsersViewModel::class)) {
+ return UsersViewModel(app, ph, tc, sr, ps) as T
+ }
+
+ if (checkType(PasswordViewModel::class)) {
+ return PasswordViewModel(app, ph, sr, ps, tc) as T
+ }
+
+ if (checkType(SettingsViewModel::class)) {
+ return SettingsViewModel(app, sr, ph, ps, tc, ts) as T
+ }
+ throw Exception("Unknown ViewModel")
+ }
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/Privilege.kt b/app/src/main/java/com/bintianqi/owndroid/Privilege.kt
deleted file mode 100644
index 5b2f284..0000000
--- a/app/src/main/java/com/bintianqi/owndroid/Privilege.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.bintianqi.owndroid
-
-import android.app.admin.DevicePolicyManager
-import android.content.ComponentName
-import android.content.Context
-import android.os.Binder
-import android.os.Build
-import com.bintianqi.owndroid.dpm.binderWrapperDevicePolicyManager
-import com.bintianqi.owndroid.dpm.dhizukuErrorStatus
-import com.rosan.dhizuku.api.Dhizuku
-import kotlinx.coroutines.flow.MutableStateFlow
-
-object Privilege {
- fun initialize(context: Context) {
- if (SP.dhizuku) {
- if (Dhizuku.init(context)) try {
- if (Dhizuku.isPermissionGranted()) {
- val dhizukuDpm = binderWrapperDevicePolicyManager(context)
- if (dhizukuDpm != null) {
- DPM = dhizukuDpm
- DAR = Dhizuku.getOwnerComponent()
- updateStatus()
- return
- }
- }
- } catch(e: Exception) {
- e.printStackTrace()
- }
- dhizukuErrorStatus.value = 2
- }
- DPM = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
- DAR = MyAdminComponent
- updateStatus()
- }
- lateinit var DPM: DevicePolicyManager
- private set
- lateinit var DAR: ComponentName
- private set
-
- data class Status(
- val device: Boolean = false,
- val profile: Boolean = false,
- val dhizuku: Boolean = false,
- val work: Boolean = false,
- val org: Boolean = false,
- val affiliated: Boolean = false
- ) {
- val activated = device || profile
- val primary = Binder.getCallingUid() / 100000 == 0 // Primary user
- }
- val status = MutableStateFlow(Status())
- fun updateStatus() {
- val profile = DPM.isProfileOwnerApp(DAR.packageName)
- val work = profile && Build.VERSION.SDK_INT >= 24 && DPM.isManagedProfile(DAR)
- status.value = Status(
- device = DPM.isDeviceOwnerApp(DAR.packageName),
- profile = profile,
- dhizuku = SP.dhizuku,
- work = work,
- org = work && Build.VERSION.SDK_INT >= 30 && DPM.isOrganizationOwnedDeviceWithManagedProfile,
- affiliated = Build.VERSION.SDK_INT >= 28 && DPM.isAffiliatedUser
- )
- }
-}
diff --git a/app/src/main/java/com/bintianqi/owndroid/PrivilegeHelper.kt b/app/src/main/java/com/bintianqi/owndroid/PrivilegeHelper.kt
new file mode 100644
index 0000000..31eb54e
--- /dev/null
+++ b/app/src/main/java/com/bintianqi/owndroid/PrivilegeHelper.kt
@@ -0,0 +1,50 @@
+package com.bintianqi.owndroid
+
+import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
+import android.content.Context
+import com.bintianqi.owndroid.utils.DhizukuError
+import com.bintianqi.owndroid.utils.DhizukuException
+import com.bintianqi.owndroid.utils.binderWrapperDevicePolicyManager
+import com.rosan.dhizuku.api.Dhizuku
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class PrivilegeHelper(
+ val context: Context, var dhizuku: Boolean, val dhizukuError: MutableStateFlow
+) {
+ val myDpm = context.getSystemService(DevicePolicyManager::class.java)!!
+ val myDar = ComponentName(context, Receiver::class.java)
+
+ val dpm: DevicePolicyManager
+ get() {
+ return if (dhizuku) getDhizukuDpm() else myDpm
+ }
+
+ val dar: ComponentName
+ get() {
+ return if (dhizuku) Dhizuku.getOwnerComponent() else myDar
+ }
+
+ class SafeDpmCallScope(val dpm: DevicePolicyManager, val dar: ComponentName)
+
+ fun safeDpmCall(block: SafeDpmCallScope.() -> Unit) {
+ try {
+ SafeDpmCallScope(dpm, dar).block()
+ } catch (e: DhizukuException) {
+ dhizukuError.value = e.reason
+ }
+ }
+
+ private fun getDhizukuDpm(): DevicePolicyManager {
+ try {
+ if (!Dhizuku.init(context)) throw DhizukuException(DhizukuError.Init)
+ if (!Dhizuku.isPermissionGranted()) throw DhizukuException(DhizukuError.Permission)
+ return binderWrapperDevicePolicyManager(context)
+ } catch(e: Exception) {
+ if (e !is DhizukuException) {
+ throw DhizukuException(DhizukuError.Binder, e)
+ }
+ throw e
+ }
+ }
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt
index f5cd676..6707725 100644
--- a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt
@@ -3,32 +3,26 @@ package com.bintianqi.owndroid
import android.app.admin.DeviceAdminReceiver
import android.content.Context
import android.content.Intent
-import android.os.Binder
import android.os.Build.VERSION
import android.os.UserHandle
import android.os.UserManager
-import com.bintianqi.owndroid.dpm.handlePrivilegeChange
-import com.bintianqi.owndroid.dpm.retrieveNetworkLogs
-import com.bintianqi.owndroid.dpm.retrieveSecurityLogs
+import com.bintianqi.owndroid.utils.NotificationType
+import com.bintianqi.owndroid.utils.NotificationUtils
+import com.bintianqi.owndroid.utils.ShortcutUtils
+import com.bintianqi.owndroid.utils.formatDate
+import com.bintianqi.owndroid.utils.popToast
+import com.bintianqi.owndroid.utils.retrieveNetworkLogs
+import com.bintianqi.owndroid.utils.retrieveSecurityLogs
class Receiver : DeviceAdminReceiver() {
- override fun onEnabled(context: Context, intent: Intent) {
- super.onEnabled(context, intent)
- Privilege.updateStatus()
- if (Binder.getCallingUid() / 100000 != 0) handlePrivilegeChange(context)
- }
-
- override fun onDisabled(context: Context, intent: Intent) {
- super.onDisabled(context, intent)
- Privilege.updateStatus()
- }
-
override fun onProfileProvisioningComplete(context: Context, intent: Intent) {
super.onProfileProvisioningComplete(context, intent)
context.popToast(R.string.create_work_profile_success)
}
- override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) {
+ override fun onNetworkLogsAvailable(
+ context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int
+ ) {
super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount)
if (VERSION.SDK_INT >= 26) {
retrieveNetworkLogs(context.applicationContext as MyApplication, batchToken)
@@ -78,28 +72,34 @@ class Receiver : DeviceAdminReceiver() {
override fun onBugreportShared(context: Context, intent: Intent, hash: String) {
super.onBugreportShared(context, intent, hash)
- NotificationUtils.notifyEvent(context, NotificationType.BugReportShared, "SHA-256 hash: $hash")
+ NotificationUtils.notifyEvent(
+ context, getSr(context), NotificationType.BugReportShared, "SHA-256 hash: $hash"
+ )
}
override fun onBugreportSharingDeclined(context: Context, intent: Intent) {
super.onBugreportSharingDeclined(context, intent)
- NotificationUtils.notifyEvent(context, NotificationType.BugReportSharingDeclined, "")
+ NotificationUtils.notifyEvent(context, getSr(context), NotificationType.BugReportSharingDeclined, "")
}
override fun onBugreportFailed(context: Context, intent: Intent, failureCode: Int) {
super.onBugreportFailed(context, intent, failureCode)
- val message = when(failureCode) {
+ val message = when (failureCode) {
BUGREPORT_FAILURE_FAILED_COMPLETING -> R.string.bug_report_failure_failed_completing
BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE -> R.string.bug_report_failure_no_longer_available
else -> R.string.place_holder
}
- NotificationUtils.notifyEvent(context, NotificationType.BugReportFailed, context.getString(message))
+ NotificationUtils.notifyEvent(
+ context, getSr(context), NotificationType.BugReportFailed, context.getString(message)
+ )
}
override fun onSystemUpdatePending(context: Context, intent: Intent, receivedTime: Long) {
super.onSystemUpdatePending(context, intent, receivedTime)
val text = context.getString(R.string.received_time) + ": " + formatDate(receivedTime)
- NotificationUtils.notifyEvent(context, NotificationType.SystemUpdatePending, text)
+ NotificationUtils.notifyEvent(
+ context, getSr(context), NotificationType.SystemUpdatePending, text
+ )
}
private fun sendUserRelatedNotification(
@@ -108,6 +108,11 @@ class Receiver : DeviceAdminReceiver() {
val um = context.getSystemService(Context.USER_SERVICE) as UserManager
val serial = um.getSerialNumberForUser(userHandle)
val text = context.getString(R.string.serial_number) + ": $serial"
- NotificationUtils.notifyEvent(context, type, text)
+ NotificationUtils.notifyEvent(context, getSr(context), type, text)
+ }
+
+ companion object {
+ fun getSr(context: Context) =
+ (context.applicationContext as MyApplication).container.settingsRepo
}
}
diff --git a/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt b/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt
deleted file mode 100644
index 27a880e..0000000
--- a/app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.bintianqi.owndroid
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.os.Build
-import androidx.core.content.edit
-import kotlin.properties.ReadWriteProperty
-import kotlin.reflect.KProperty
-
-class SharedPrefs(context: Context) {
- val sharedPrefs: SharedPreferences = context.getSharedPreferences("data", Context.MODE_PRIVATE)
- var managedProfileActivated by BooleanSharedPref("managed_profile_activated")
- var dhizuku by BooleanSharedPref("dhizuku_mode")
- var isDefaultAffiliationIdSet by BooleanSharedPref("default_affiliation_id_set")
- var displayDangerousFeatures by BooleanSharedPref("display_dangerous_features")
- var apiKeyHash by StringSharedPref("api_key_hash")
- var materialYou by BooleanSharedPref("theme.material_you", Build.VERSION.SDK_INT >= 31)
- /** -1: follow system, 0: off, 1: on */
- var darkTheme by IntSharedPref("theme.dark", -1)
- var blackTheme by BooleanSharedPref("theme.black")
- var lockPasswordHash by StringSharedPref("lock.password.sha256")
- var biometricsUnlock by BooleanSharedPref("lock.biometrics")
- var lockWhenLeaving by BooleanSharedPref("lock.onleave")
- var applicationsListView by BooleanSharedPref("applications.list_view", true)
- var shortcuts by BooleanSharedPref("shortcuts")
- var dhizukuServer by BooleanSharedPref("dhizuku_server")
- var notifications by StringSharedPref("notifications")
- var shortcutKey by StringSharedPref("shortcut_key")
-}
-
-private class BooleanSharedPref(val key: String, val defValue: Boolean = false): ReadWriteProperty {
- override fun getValue(thisRef: SharedPrefs, property: KProperty<*>): Boolean =
- thisRef.sharedPrefs.getBoolean(key, defValue)
- override fun setValue(thisRef: SharedPrefs, property: KProperty<*>, value: Boolean) =
- thisRef.sharedPrefs.edit(true) { putBoolean(key, value) }
-}
-
-private class StringSharedPref(val key: String): ReadWriteProperty {
- override fun getValue(thisRef: SharedPrefs, property: KProperty<*>): String? =
- thisRef.sharedPrefs.getString(key, null)
- override fun setValue(thisRef: SharedPrefs, property: KProperty<*>, value: String?) =
- thisRef.sharedPrefs.edit(true) { putString(key, value) }
-}
-
-private class IntSharedPref(val key: String, val defValue: Int = 0): ReadWriteProperty {
- override fun getValue(thisRef: SharedPrefs, property: KProperty<*>): Int =
- thisRef.sharedPrefs.getInt(key, defValue)
- override fun setValue(thisRef: SharedPrefs, property: KProperty<*>, value: Int) =
- thisRef.sharedPrefs.edit(true) { putInt(key, value) }
-}
diff --git a/app/src/main/java/com/bintianqi/owndroid/ShizukuService.kt b/app/src/main/java/com/bintianqi/owndroid/ShizukuService.kt
index 99c5b0d..8d192c0 100644
--- a/app/src/main/java/com/bintianqi/owndroid/ShizukuService.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/ShizukuService.kt
@@ -7,8 +7,8 @@ import android.content.pm.PackageManager
import android.os.Bundle
import android.os.IBinder
import androidx.annotation.Keep
+import com.bintianqi.owndroid.utils.popToast
import rikka.shizuku.Shizuku
-import rikka.sui.Sui
import kotlin.system.exitProcess
@Keep
@@ -54,13 +54,14 @@ fun useShizuku(context: Context, action: (IBinder?) -> Unit) {
Shizuku.bindUserService(getShizukuArgs(context), connection)
} else if(Shizuku.shouldShowRequestPermissionRationale()) {
context.popToast(R.string.permission_denied)
+ action(null)
} else {
- Sui.init(context.packageName)
fun requestPermissionResultListener(requestCode: Int, grantResult: Int) {
if (grantResult == PackageManager.PERMISSION_GRANTED) {
Shizuku.bindUserService(getShizukuArgs(context), connection)
} else {
context.popToast(R.string.permission_denied)
+ action(null)
}
Shizuku.removeRequestPermissionResultListener(::requestPermissionResultListener)
}
@@ -69,5 +70,6 @@ fun useShizuku(context: Context, action: (IBinder?) -> Unit) {
}
} catch (e: Exception) {
e.printStackTrace()
+ action(null)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/ShortcutsReceiverActivity.kt b/app/src/main/java/com/bintianqi/owndroid/ShortcutsReceiverActivity.kt
index 5d0c1f4..7eac514 100644
--- a/app/src/main/java/com/bintianqi/owndroid/ShortcutsReceiverActivity.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/ShortcutsReceiverActivity.kt
@@ -3,46 +3,60 @@ package com.bintianqi.owndroid
import android.app.Activity
import android.os.Bundle
import android.util.Log
-import com.bintianqi.owndroid.dpm.UserOperationType
-import com.bintianqi.owndroid.dpm.doUserOperationWithContext
+import com.bintianqi.owndroid.feature.users.UserOperationType
+import com.bintianqi.owndroid.utils.doUserOperationWithContext
+import com.bintianqi.owndroid.utils.MyShortcut
+import com.bintianqi.owndroid.utils.ShortcutUtils
+import com.bintianqi.owndroid.utils.showOperationResultToast
class ShortcutsReceiverActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ val myApp = application as MyApplication
+ val context = this
+ val sr = myApp.container.settingsRepo
+ val ph = myApp.container.privilegeHelper
+ val settings = sr.data.shortcut
try {
val action = intent.action?.removePrefix("com.bintianqi.owndroid.action.")
- val key = SP.shortcutKey
val requestKey = intent?.getStringExtra("key")
- if (action != null && key != null && requestKey == key) {
- when (action) {
- "LOCK" -> Privilege.DPM.lockNow()
- "DISABLE_CAMERA" -> {
- val state = Privilege.DPM.getCameraDisabled(Privilege.DAR)
- Privilege.DPM.setCameraDisabled(Privilege.DAR, !state)
- ShortcutUtils.setShortcut(this, MyShortcut.DisableCamera, state)
- }
- "MUTE" -> {
- val state = Privilege.DPM.isMasterVolumeMuted(Privilege.DAR)
- Privilege.DPM.setMasterVolumeMuted(Privilege.DAR, !state)
- ShortcutUtils.setShortcut(this, MyShortcut.Mute, state)
- }
- "USER_RESTRICTION" -> {
- val state = intent?.getBooleanExtra("state", false)
- val id = intent?.getStringExtra("restriction")
- if (state == null || id == null) return
- if (state) {
- Privilege.DPM.addUserRestriction(Privilege.DAR, id)
- } else {
- Privilege.DPM.clearUserRestriction(Privilege.DAR, id)
+ if (action != null && settings.enabled && requestKey == settings.key) {
+ ph.safeDpmCall {
+ when (action) {
+ "LOCK" -> dpm.lockNow()
+ "DISABLE_CAMERA" -> {
+ val state = dpm.getCameraDisabled(dar)
+ dpm.setCameraDisabled(dar, !state)
+ ShortcutUtils.setShortcut(context, sr, MyShortcut.DisableCamera, state)
+ }
+
+ "MUTE" -> {
+ val state = dpm.isMasterVolumeMuted(dar)
+ dpm.setMasterVolumeMuted(dar, !state)
+ ShortcutUtils.setShortcut(context, sr, MyShortcut.Mute, state)
+ }
+
+ "USER_RESTRICTION" -> {
+ val state = intent?.getBooleanExtra("state", false)
+ val id = intent?.getStringExtra("restriction")
+ if (state == null || id == null) return@safeDpmCall
+ if (state) {
+ dpm.addUserRestriction(dar, id)
+ } else {
+ dpm.clearUserRestriction(dar, id)
+ }
+ ShortcutUtils.updateUserRestrictionShortcut(
+ context, sr, id, !state, false
+ )
+ }
+
+ "USER_OPERATION" -> {
+ val typeName = intent.getStringExtra("operation") ?: return@safeDpmCall
+ val type = UserOperationType.valueOf(typeName)
+ val serial = intent.getIntExtra("serial", -1)
+ if (serial == -1) return@safeDpmCall
+ doUserOperationWithContext(context, ph.dpm, ph.dar, type, serial, false)
}
- ShortcutUtils.updateUserRestrictionShortcut(this, id, !state, false)
- }
- "USER_OPERATION" -> {
- val typeName = intent.getStringExtra("operation") ?: return
- val type = UserOperationType.valueOf(typeName)
- val serial = intent.getIntExtra("serial", -1)
- if (serial == -1) return
- doUserOperationWithContext(this, type, serial, false)
}
}
Log.d(TAG, "Received intent: $action")
@@ -50,12 +64,13 @@ class ShortcutsReceiverActivity : Activity() {
} else {
showOperationResultToast(false)
}
- } catch(e: Exception) {
+ } catch (e: Exception) {
e.printStackTrace()
} finally {
finish()
}
}
+
companion object {
private const val TAG = "ShortcutsReceiver"
}
diff --git a/app/src/main/java/com/bintianqi/owndroid/activity/CheckPolicyComplianceActivity.kt b/app/src/main/java/com/bintianqi/owndroid/activity/CheckPolicyComplianceActivity.kt
new file mode 100644
index 0000000..a19710c
--- /dev/null
+++ b/app/src/main/java/com/bintianqi/owndroid/activity/CheckPolicyComplianceActivity.kt
@@ -0,0 +1,41 @@
+package com.bintianqi.owndroid.activity
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.res.stringResource
+import com.bintianqi.owndroid.MyApplication
+import com.bintianqi.owndroid.R
+import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
+
+class CheckPolicyComplianceActivity: ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+ val myApp = application as MyApplication
+ setContent {
+ val theme by myApp.container.themeState.collectAsState()
+ OwnDroidTheme(theme) {
+ AlertDialog(
+ text = {
+ Text(stringResource(R.string.info_personal_apps_suspended))
+ },
+ confirmButton = {
+ TextButton(::finish) {
+ Text(stringResource(R.string.confirm))
+ }
+ },
+ onDismissRequest = {
+ finish()
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt b/app/src/main/java/com/bintianqi/owndroid/activity/ManageSpaceActivity.kt
similarity index 74%
rename from app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt
rename to app/src/main/java/com/bintianqi/owndroid/activity/ManageSpaceActivity.kt
index db671b9..4600e96 100644
--- a/app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt
+++ b/app/src/main/java/com/bintianqi/owndroid/activity/ManageSpaceActivity.kt
@@ -1,10 +1,9 @@
-package com.bintianqi.owndroid
+package com.bintianqi.owndroid.activity
import android.os.Build
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.activity.viewModels
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@@ -16,20 +15,27 @@ import androidx.compose.ui.res.stringResource
import androidx.core.content.edit
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.bintianqi.owndroid.MyApplication
+import com.bintianqi.owndroid.R
+import com.bintianqi.owndroid.ui.screen.AppLockDialog
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
+import com.bintianqi.owndroid.utils.showOperationResultToast
import kotlin.system.exitProcess
class ManageSpaceActivity: FragmentActivity() {
+ val myApp = application as MyApplication
+ val settingsRepo = myApp.container.settingsRepo
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
- val vm by viewModels()
setContent {
- val theme by vm.theme.collectAsStateWithLifecycle()
+ val theme by myApp.container.themeState.collectAsStateWithLifecycle()
OwnDroidTheme(theme) {
- var appLockDialog by remember { mutableStateOf(!SP.lockPasswordHash.isNullOrEmpty()) }
- if(appLockDialog) {
- AppLockDialog({ appLockDialog = false }, ::finish)
+ var appLockDialog by remember {
+ mutableStateOf(settingsRepo.data.appLock.passwordHash.isNotEmpty())
+ }
+ if (appLockDialog) {
+ AppLockDialog(settingsRepo.data.appLock, { appLockDialog = false }, ::finish)
} else {
AlertDialog(
text = {
@@ -57,6 +63,7 @@ class ManageSpaceActivity: FragmentActivity() {
cacheDir.deleteRecursively()
codeCacheDir.deleteRecursively()
if(Build.VERSION.SDK_INT >= 24) {
+ dataDir.resolve("databases").deleteRecursively()
dataDir.resolve("shared_prefs").deleteRecursively()
} else {
val sharedPref = applicationContext.getSharedPreferences("data", MODE_PRIVATE)
@@ -66,4 +73,4 @@ class ManageSpaceActivity: FragmentActivity() {
finish()
exitProcess(0)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/activity/ProvisioningActivity.kt b/app/src/main/java/com/bintianqi/owndroid/activity/ProvisioningActivity.kt
new file mode 100644
index 0000000..92105fa
--- /dev/null
+++ b/app/src/main/java/com/bintianqi/owndroid/activity/ProvisioningActivity.kt
@@ -0,0 +1,32 @@
+package com.bintianqi.owndroid.activity
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import com.bintianqi.owndroid.MyApplication
+import com.bintianqi.owndroid.feature.provisioning.ProvisioningScreen
+import com.bintianqi.owndroid.feature.provisioning.ProvisioningViewModel
+import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
+
+@RequiresApi(29)
+class ProvisioningActivity: ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val myApp = application as MyApplication
+ val vm by viewModels()
+ vm.params = vm.getParamsFromIntent(intent)
+ setContent {
+ val theme by myApp.container.themeState.collectAsState()
+ OwnDroidTheme(theme) {
+ ProvisioningScreen(vm.params) {
+ setResult(RESULT_OK, vm.buildResultIntent(it))
+ finish()
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/activity/ProvisioningPolicyComplianceActivity.kt b/app/src/main/java/com/bintianqi/owndroid/activity/ProvisioningPolicyComplianceActivity.kt
new file mode 100644
index 0000000..e317298
--- /dev/null
+++ b/app/src/main/java/com/bintianqi/owndroid/activity/ProvisioningPolicyComplianceActivity.kt
@@ -0,0 +1,15 @@
+package com.bintianqi.owndroid.activity
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import com.bintianqi.owndroid.R
+import com.bintianqi.owndroid.utils.popToast
+
+class ProvisioningPolicyComplianceActivity: ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setResult(RESULT_OK)
+ popToast(R.string.app_name)
+ finish()
+ }
+}
diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt
deleted file mode 100644
index 1f501dd..0000000
--- a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt
+++ /dev/null
@@ -1,1500 +0,0 @@
-package com.bintianqi.owndroid.dpm
-
-import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
-import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
-import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
-import android.app.admin.PackagePolicy
-import android.content.Intent
-import android.net.Uri
-import android.os.Build.VERSION
-import android.os.Looper
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.RequiresApi
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-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
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyItemScope
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.lazy.itemsIndexed
-import androidx.compose.foundation.lazy.rememberLazyListState
-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.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.List
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Check
-import androidx.compose.material.icons.filled.Clear
-import androidx.compose.material.icons.filled.MoreVert
-import androidx.compose.material.icons.outlined.CheckCircle
-import androidx.compose.material.icons.outlined.Clear
-import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material.icons.outlined.Search
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.AlertDialogDefaults
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.Checkbox
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.FloatingActionButton
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.LargeTopAppBar
-import androidx.compose.material3.LinearProgressIndicator
-import androidx.compose.material3.MaterialTheme.colorScheme
-import androidx.compose.material3.MaterialTheme.typography
-import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.RadioButton
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SegmentedButton
-import androidx.compose.material3.SegmentedButtonDefaults
-import androidx.compose.material3.SingleChoiceSegmentedButtonRow
-import androidx.compose.material3.SnackbarDuration
-import androidx.compose.material3.SnackbarHost
-import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.SnackbarResult
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Switch
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-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.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.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-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.font.FontStyle
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.KeyboardType
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.Dialog
-import androidx.compose.ui.window.DialogProperties
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.bintianqi.owndroid.AppInfo
-import com.bintianqi.owndroid.AppInstallerActivity
-import com.bintianqi.owndroid.BottomPadding
-import com.bintianqi.owndroid.HorizontalPadding
-import com.bintianqi.owndroid.MyViewModel
-import com.bintianqi.owndroid.Privilege
-import com.bintianqi.owndroid.R
-import com.bintianqi.owndroid.adaptiveInsets
-import com.bintianqi.owndroid.parsePackageNames
-import com.bintianqi.owndroid.showOperationResultToast
-import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem
-import com.bintianqi.owndroid.ui.FunctionItem
-import com.bintianqi.owndroid.ui.MyLazyScaffold
-import com.bintianqi.owndroid.ui.MyScaffold
-import com.bintianqi.owndroid.ui.MySmallTitleScaffold
-import com.bintianqi.owndroid.ui.NavIcon
-import com.bintianqi.owndroid.ui.Notes
-import com.bintianqi.owndroid.ui.SwitchItem
-import com.google.accompanist.drawablepainter.rememberDrawablePainter
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
-import kotlinx.serialization.Serializable
-import sh.calvin.reorderable.ReorderableItem
-import sh.calvin.reorderable.rememberReorderableLazyListState
-
-val String.isValidPackageName
- get() = Regex("""^(?:[a-zA-Z]\w*\.)+[a-zA-Z]\w*$""").matches(this)
-
-@Composable
-fun LazyItemScope.ApplicationItem(info: AppInfo, onClear: () -> Unit) {
- Row(
- Modifier
- .fillMaxWidth()
- .padding(horizontal = 8.dp, vertical = 6.dp)
- .animateItem(),
- Arrangement.SpaceBetween, Alignment.CenterVertically
- ) {
- Row(Modifier.weight(1F), verticalAlignment = Alignment.CenterVertically) {
- Image(
- painter = rememberDrawablePainter(info.icon), contentDescription = null,
- modifier = Modifier
- .padding(start = 12.dp, end = 18.dp)
- .size(30.dp)
- )
- Column {
- Text(info.label)
- Text(info.name, Modifier.alpha(0.8F), style = typography.bodyMedium)
- }
- }
- IconButton(onClear) {
- Icon(Icons.Default.Clear, null)
- }
- }
-}
-
-@Composable
-fun PackageNameTextField(
- value: String, onChoosePackage: () -> Unit,
- modifier: Modifier = Modifier, onValueChange: (String) -> Unit
-) {
- val fm = LocalFocusManager.current
- OutlinedTextField(
- value, onValueChange, Modifier
- .fillMaxWidth()
- .then(modifier),
- label = { Text(stringResource(R.string.package_name)) },
- trailingIcon = {
- IconButton(onChoosePackage) {
- Icon(Icons.AutoMirrored.Default.List, null)
- }
- },
- isError = value.isNotEmpty() && !value.isValidPackageName,
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
- keyboardActions = KeyboardActions { fm.clearFocus() }
- )
-}
-
-@Serializable object ApplicationsFeatures
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun ApplicationsFeaturesScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onSwitchView: () -> Unit) {
- val context = LocalContext.current
- val sb = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
- Scaffold(
- Modifier.nestedScroll(sb.nestedScrollConnection),
- topBar = {
- LargeTopAppBar(
- { Text(stringResource(R.string.applications)) },
- navigationIcon = { NavIcon(onNavigateUp) },
- actions = {
- IconButton(onSwitchView) {
- Icon(painterResource(R.drawable.android_fill0), null)
- }
- },
- scrollBehavior = sb
- )
- },
- contentWindowInsets = adaptiveInsets()
- ) { paddingValues ->
- Column(
- Modifier
- .fillMaxSize()
- .padding(paddingValues)
- .verticalScroll(rememberScrollState())
- .padding(bottom = 80.dp)
- ) {
- val privilege by Privilege.status.collectAsStateWithLifecycle()
- if(VERSION.SDK_INT >= 24) FunctionItem(R.string.suspend, icon = R.drawable.block_fill0) { onNavigate(Suspend) }
- FunctionItem(R.string.hide, icon = R.drawable.visibility_off_fill0) { onNavigate(Hide) }
- FunctionItem(R.string.block_uninstall, icon = R.drawable.delete_forever_fill0) { onNavigate(BlockUninstall) }
- if(VERSION.SDK_INT >= 30 && (privilege.device || (VERSION.SDK_INT >= 33 && privilege.profile))) {
- FunctionItem(R.string.disable_user_control, icon = R.drawable.do_not_touch_fill0) { onNavigate(DisableUserControl) }
- }
- FunctionItem(R.string.permissions, icon = R.drawable.shield_fill0) { onNavigate(PermissionsManager()) }
- if(VERSION.SDK_INT >= 28) {
- FunctionItem(R.string.disable_metered_data, icon = R.drawable.money_off_fill0) { onNavigate(DisableMeteredData) }
- }
- if(VERSION.SDK_INT >= 28) {
- FunctionItem(R.string.clear_app_storage, icon = R.drawable.mop_fill0) { onNavigate(ClearAppStorage) }
- }
- FunctionItem(R.string.install_app, icon = R.drawable.install_mobile_fill0) {
- context.startActivity(Intent(context, AppInstallerActivity::class.java))
- }
- FunctionItem(R.string.uninstall_app, icon = R.drawable.delete_fill0) { onNavigate(UninstallApp) }
- if(VERSION.SDK_INT >= 28 && privilege.device) {
- FunctionItem(R.string.keep_uninstalled_packages, icon = R.drawable.delete_fill0) { onNavigate(KeepUninstalledPackages) }
- }
- if (VERSION.SDK_INT >= 28 && (privilege.device || (privilege.profile && privilege.affiliated))) {
- FunctionItem(R.string.install_existing_app, icon = R.drawable.install_mobile_fill0) {
- onNavigate(InstallExistingApp)
- }
- }
- if(VERSION.SDK_INT >= 30 && privilege.work) {
- FunctionItem(R.string.cross_profile_apps, icon = R.drawable.work_fill0) { onNavigate(CrossProfilePackages) }
- }
- if(privilege.work) {
- FunctionItem(R.string.cross_profile_widget, icon = R.drawable.widgets_fill0) { onNavigate(CrossProfileWidgetProviders) }
- }
- if(VERSION.SDK_INT >= 34 && privilege.device) {
- FunctionItem(R.string.credential_manager_policy, icon = R.drawable.license_fill0) { onNavigate(CredentialManagerPolicy) }
- }
- FunctionItem(R.string.permitted_accessibility_services, icon = R.drawable.settings_accessibility_fill0) {
- onNavigate(PermittedAccessibilityServices)
- }
- FunctionItem(R.string.permitted_ime, icon = R.drawable.keyboard_fill0) { onNavigate(PermittedInputMethods) }
- FunctionItem(R.string.enable_system_app, icon = R.drawable.enable_fill0) { onNavigate(EnableSystemApp) }
- if(VERSION.SDK_INT >= 34 && (privilege.device || privilege.work)) {
- FunctionItem(R.string.set_default_dialer, icon = R.drawable.call_fill0) { onNavigate(SetDefaultDialer) }
- }
- }
- }
-}
-
-@Serializable data class ApplicationDetails(val packageName: String)
-
-data class AppStatus(
- val suspend: Boolean,
- val hide: Boolean,
- val uninstallBlocked: Boolean,
- val userControlDisabled: Boolean,
- val meteredDataDisabled: Boolean,
- val keepUninstalled: Boolean
-)
-
-@Composable
-fun ApplicationDetailsScreen(
- param: ApplicationDetails, vm: MyViewModel, onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit
-) {
- val packageName = param.packageName
- val privilege by Privilege.status.collectAsStateWithLifecycle()
- var dialog by rememberSaveable { mutableIntStateOf(0) } // 1: clear storage, 2: uninstall
- val info = vm.getAppInfo(packageName)
- val status by vm.appStatus.collectAsStateWithLifecycle()
- val appRestrictions by vm.appRestrictions.collectAsStateWithLifecycle()
- LaunchedEffect(Unit) {
- vm.getAppStatus(packageName)
- vm.getAppRestrictions(packageName)
- }
- MySmallTitleScaffold(R.string.place_holder, onNavigateUp, 0.dp) {
- Column(Modifier
- .align(Alignment.CenterHorizontally)
- .padding(top = 16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
- Image(rememberDrawablePainter(info.icon), null, Modifier.size(50.dp))
- Text(info.label, Modifier.padding(top = 4.dp))
- Text(info.name, Modifier
- .alpha(0.7F)
- .padding(bottom = 8.dp), style = typography.bodyMedium)
- }
- FunctionItem(R.string.permissions, icon = R.drawable.shield_fill0) { onNavigate(PermissionsManager(packageName)) }
- if(VERSION.SDK_INT >= 24) SwitchItem(
- R.string.suspend, icon = R.drawable.block_fill0, state = status.suspend,
- onCheckedChange = { vm.adSetPackageSuspended(packageName, it) }
- )
- SwitchItem(
- R.string.hide, icon = R.drawable.visibility_off_fill0,
- state = status.hide,
- onCheckedChange = { vm.adSetPackageHidden(packageName, it) }
- )
- SwitchItem(
- R.string.block_uninstall, icon = R.drawable.delete_forever_fill0,
- state = status.uninstallBlocked,
- onCheckedChange = { vm.adSetPackageUb(packageName, it) }
- )
- if(VERSION.SDK_INT >= 30) SwitchItem(
- R.string.disable_user_control, icon = R.drawable.do_not_touch_fill0,
- state = status.userControlDisabled,
- onCheckedChange = { vm.adSetPackageUcd(packageName, it) }
- )
- if(VERSION.SDK_INT >= 28) SwitchItem(
- R.string.disable_metered_data, icon = R.drawable.money_off_fill0,
- state = status.meteredDataDisabled,
- onCheckedChange = { vm.adSetPackageMdd(packageName, it) }
- )
- if(privilege.device && VERSION.SDK_INT >= 28) SwitchItem(
- R.string.keep_after_uninstall, icon = R.drawable.delete_fill0,
- state = status.keepUninstalled,
- onCheckedChange = { vm.adSetPackageKu(packageName, it) }
- )
- if (appRestrictions.isNotEmpty()) {
- FunctionItem(R.string.managed_configuration, icon = R.drawable.description_fill0) {
- onNavigate(ManagedConfiguration(packageName))
- }
- }
- if(VERSION.SDK_INT >= 28) FunctionItem(R.string.clear_app_storage, icon = R.drawable.mop_fill0) { dialog = 1 }
- FunctionItem(R.string.uninstall, icon = R.drawable.delete_fill0) { dialog = 2 }
- Spacer(Modifier.height(BottomPadding))
- }
- if(dialog == 1 && VERSION.SDK_INT >= 28)
- ClearAppStorageDialog(packageName, vm::clearAppData) { dialog = 0 }
- if(dialog == 2) UninstallAppDialog(packageName, vm::uninstallPackage) {
- dialog = 0
- if (it) onNavigateUp()
- }
-}
-
-@Serializable object Suspend
-
-@Serializable object Hide
-
-@Serializable object BlockUninstall
-
-@Serializable object DisableUserControl
-
-@Serializable data class PermissionsManager(val packageName: String? = null)
-
-@Composable
-fun PermissionsManagerScreen(
- packagePermissions: MutableStateFlow