├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── LICENSE.md ├── Readme-en.md ├── Readme-ja.md ├── Readme.md ├── app ├── build.gradle.kts ├── proguard-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── aidl │ │ └── com │ │ │ └── bintianqi │ │ │ └── owndroid │ │ │ └── IUserService.aidl │ │ ├── java │ │ ├── android │ │ │ ├── app │ │ │ │ └── admin │ │ │ │ │ └── IDevicePolicyManager.java │ │ │ └── content │ │ │ │ └── pm │ │ │ │ └── IPackageInstaller.java │ │ └── com │ │ │ └── bintianqi │ │ │ └── owndroid │ │ │ ├── ApiReceiver.kt │ │ │ ├── AppInstallerActivity.kt │ │ │ ├── AppLock.kt │ │ │ ├── DhizukuServer.kt │ │ │ ├── MainActivity.kt │ │ │ ├── ManageSpaceActivity.kt │ │ │ ├── MyViewModel.kt │ │ │ ├── NotificationUtils.kt │ │ │ ├── PackageChooser.kt │ │ │ ├── Privilege.kt │ │ │ ├── Receiver.kt │ │ │ ├── Settings.kt │ │ │ ├── SharedPrefs.kt │ │ │ ├── ShizukuService.kt │ │ │ ├── ShortcutsReceiverActivity.kt │ │ │ ├── Utils.kt │ │ │ ├── dpm │ │ │ ├── Applications.kt │ │ │ ├── DPM.kt │ │ │ ├── Network.kt │ │ │ ├── Password.kt │ │ │ ├── Permissions.kt │ │ │ ├── System.kt │ │ │ ├── UserRestriction.kt │ │ │ ├── Users.kt │ │ │ └── WorkProfile.kt │ │ │ └── ui │ │ │ ├── Animations.kt │ │ │ ├── Components.kt │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ └── Theme.kt │ │ └── res │ │ ├── drawable │ │ ├── account_circle_fill0.xml │ │ ├── adb_fill0.xml │ │ ├── admin_panel_settings_fill0.xml │ │ ├── airplanemode_active_fill0.xml │ │ ├── android_fill0.xml │ │ ├── apps_fill0.xml │ │ ├── backup_fill0.xml │ │ ├── block_fill0.xml │ │ ├── bluetooth_fill0.xml │ │ ├── bluetooth_searching_fill0.xml │ │ ├── brightness_5_fill0.xml │ │ ├── bug_report_fill0.xml │ │ ├── calendar_month_fill0.xml │ │ ├── call_fill0.xml │ │ ├── call_log_fill0.xml │ │ ├── cameraswitch_fill0.xml │ │ ├── casino_fill0.xml │ │ ├── cell_tower_fill0.xml │ │ ├── chat_fill0.xml │ │ ├── check_circle_fill0.xml │ │ ├── check_circle_fill1.xml │ │ ├── check_fill0.xml │ │ ├── close_fill0.xml │ │ ├── code_fill0.xml │ │ ├── contacts_fill0.xml │ │ ├── content_copy_fill0.xml │ │ ├── content_paste_fill0.xml │ │ ├── corporate_fare_fill0.xml │ │ ├── delete_fill0.xml │ │ ├── delete_forever_fill0.xml │ │ ├── description_fill0.xml │ │ ├── device_reset_fill0.xml │ │ ├── devices_other_fill0.xml │ │ ├── dhizuku_icon.xml │ │ ├── dns_fill0.xml │ │ ├── do_not_touch_fill0.xml │ │ ├── edit_fill0.xml │ │ ├── enable_fill0.xml │ │ ├── filter_alt_fill0.xml │ │ ├── fingerprint_fill0.xml │ │ ├── fingerprint_off_fill0.xml │ │ ├── folder_fill0.xml │ │ ├── format_paint_fill0.xml │ │ ├── globe_fill0.xml │ │ ├── history_fill0.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── id_card_fill0.xml │ │ ├── image_fill0.xml │ │ ├── info_fill0.xml │ │ ├── install_mobile_fill0.xml │ │ ├── key_fill0.xml │ │ ├── key_vertical_fill0.xml │ │ ├── keyboard_fill0.xml │ │ ├── language_fill0.xml │ │ ├── license_fill0.xml │ │ ├── list_fill0.xml │ │ ├── location_on_fill0.xml │ │ ├── lock_clock_fill0.xml │ │ ├── lock_fill0.xml │ │ ├── lock_reset_fill0.xml │ │ ├── logout_fill0.xml │ │ ├── manage_accounts_fill0.xml │ │ ├── memory_fill0.xml │ │ ├── mic_fill0.xml │ │ ├── mobile_phone_fill0.xml │ │ ├── money_off_fill0.xml │ │ ├── mop_fill0.xml │ │ ├── more_horiz_fill0.xml │ │ ├── movie_fill0.xml │ │ ├── music_note_fill0.xml │ │ ├── network_cell_fill0.xml │ │ ├── nfc_fill0.xml │ │ ├── no_encryption_fill0.xml │ │ ├── no_photography_fill0.xml │ │ ├── notifications_fill0.xml │ │ ├── open_in_new.xml │ │ ├── password_fill0.xml │ │ ├── perm_device_information_fill0.xml │ │ ├── person_add_fill0.xml │ │ ├── person_fill0.xml │ │ ├── person_off.xml │ │ ├── person_remove_fill0.xml │ │ ├── phone_forwarded_fill0.xml │ │ ├── photo_camera_fill0.xml │ │ ├── print_fill0.xml │ │ ├── query_stats_fill0.xml │ │ ├── refresh_fill0.xml │ │ ├── reset_wrench_fill0.xml │ │ ├── restart_alt_fill0.xml │ │ ├── router_fill0.xml │ │ ├── save_fill0.xml │ │ ├── schedule_fill0.xml │ │ ├── screen_lock_portrait_fill0.xml │ │ ├── screenshot_fill0.xml │ │ ├── sd_card_fill0.xml │ │ ├── search_fill0.xml │ │ ├── security_fill0.xml │ │ ├── sensors_fill0.xml │ │ ├── settings_accessibility_fill0.xml │ │ ├── settings_fill0.xml │ │ ├── share_fill0.xml │ │ ├── shield_fill0.xml │ │ ├── signal_cellular_alt_fill0.xml │ │ ├── sim_card_download_fill0.xml │ │ ├── sms_fill0.xml │ │ ├── stadia_controller_fill0.xml │ │ ├── swap_horiz_fill0.xml │ │ ├── sync_alt_fill0.xml │ │ ├── system_update_fill0.xml │ │ ├── tune_fill0.xml │ │ ├── usb_fill0.xml │ │ ├── visibility_off_fill0.xml │ │ ├── volume_off_fill0.xml │ │ ├── volume_up_fill0.xml │ │ ├── vpn_key_fill0.xml │ │ ├── wallpaper_fill0.xml │ │ ├── warning_fill0.xml │ │ ├── web_asset.xml │ │ ├── widgets_fill0.xml │ │ ├── wifi_fill0.xml │ │ ├── wifi_password_fill0.xml │ │ ├── wifi_tethering_fill0.xml │ │ └── work_fill0.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── resources.properties │ │ ├── values-night-v31 │ │ └── themes.xml │ │ ├── values-ru │ │ └── strings.xml │ │ ├── values-tr │ │ └── strings.xml │ │ ├── values-v31 │ │ └── themes.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ └── device_admin.xml └── testkey.jks ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: ["dev"] 7 | paths-ignore: 8 | - '**.md' 9 | tags-ignore: 10 | - '**' 11 | 12 | concurrency: 13 | group: "main" 14 | cancel-in-progress: false 15 | 16 | jobs: 17 | build: 18 | name: Build 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Check out repository 22 | uses: actions/checkout@v4 23 | 24 | - name: Set up JDK 21 25 | uses: actions/setup-java@v4 26 | with: 27 | distribution: 'temurin' 28 | java-version: '21' 29 | 30 | - name: Get short commit SHA 31 | run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV 32 | 33 | - name: Build APK (testkey) 34 | run: ./gradlew build 35 | 36 | - name: Upload Debug APK (testkey) 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: OwnDroid-CI-${{ env.SHORT_SHA }}-debug-testkey 40 | path: app/build/outputs/apk/debug/app-debug.apk 41 | 42 | - name: Upload Release APK (testkey) 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: OwnDroid-CI-${{ env.SHORT_SHA }}-release-testkey 46 | path: app/build/outputs/apk/release/app-release.apk 47 | 48 | - name: Build APK 49 | run: | 50 | echo "${{ secrets.KEY_BASE64 }}" | base64 --decode - > app/release.jks 51 | ./gradlew build -PStoreFile="$(pwd)/app/release.jks" -PStorePassword="${{ secrets.KEYSTORE_PASSWORD }}" -PKeyPassword="${{ secrets.KEY_PASSWORD }}" -PKeyAlias="${{ secrets.KEY_ALIAS }}" 52 | 53 | - name: Upload Debug APK 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: OwnDroid-CI-${{ env.SHORT_SHA }}-debug-signed 57 | path: app/build/outputs/apk/debug/app-debug.apk 58 | 59 | - name: Upload Release APK 60 | uses: actions/upload-artifact@v4 61 | with: 62 | name: OwnDroid-CI-${{ env.SHORT_SHA }}-release-signed 63 | path: app/build/outputs/apk/release/app-release.apk 64 | 65 | upload-telegram: 66 | name: Upload Builds 67 | runs-on: ubuntu-latest 68 | needs: build 69 | steps: 70 | - name: Download Artifacts 71 | uses: actions/download-artifact@v4 72 | with: 73 | path: artifacts 74 | 75 | - name: Download telegram-bot-api 76 | run: | 77 | mkdir ./binaries 78 | wget "https://github.com/jakbin/telegram-bot-api-binary/releases/download/latest/telegram-bot-api" -O ./binaries/telegram-bot-api 79 | chmod +x ./binaries/telegram-bot-api 80 | 81 | - name: Start API Server & Upload 82 | env: 83 | COMMIT_MESSAGE: |+ 84 |
${{ github.event.head_commit.message }}
85 | See commit details here 86 | COMMIT_URL: ${{ github.event.head_commit.url }} 87 | run: | 88 | ESCAPED=`python3 -c 'import json,os,urllib.parse; msg = json.dumps(os.environ["COMMIT_MESSAGE"]); print(urllib.parse.quote(msg if len(msg) <= 1024 else json.dumps(os.environ["COMMIT_URL"])))'` 89 | cd artifacts 90 | export RELEASE_TEST_PWD=$(find . -name "*release-testkey*") 91 | mv ./$RELEASE_TEST_PWD/app-release.apk ./$RELEASE_TEST_PWD.apk && rm -rf ./$RELEASE_TEST_PWD 92 | export RELEASE_SIGNED_PWD=$(find . -name "*release-signed*") 93 | mv ./$RELEASE_SIGNED_PWD/app-release.apk ./$RELEASE_SIGNED_PWD.apk && rm -rf ./$RELEASE_SIGNED_PWD 94 | ../binaries/telegram-bot-api --api-id=${{ secrets.TELEGRAM_API_APP_ID }} --api-hash=${{ secrets.TELEGRAM_API_HASH }} --local 2>&1 > /dev/null & 95 | export token=${{ secrets.TELEGRAM_BOT_KEY }} 96 | 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" \ 97 | -F releaseTest="@$RELEASE_TEST_PWD.apk" \ 98 | -F releaseSigned="@$RELEASE_SIGNED_PWD.apk" 99 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up JDK 21 18 | uses: actions/setup-java@v4 19 | with: 20 | distribution: 'temurin' 21 | java-version: '21' 22 | 23 | - name: Build APK 24 | run: | 25 | echo "${{ secrets.KEY_BASE64 }}" | base64 --decode - > app/release.jks 26 | ./gradlew clean assembleRelease --no-build-cache --no-configuration-cache --no-daemon -PStoreFile="$(pwd)/app/release.jks" -PStorePassword="${{ secrets.KEYSTORE_PASSWORD }}" -PKeyPassword="${{ secrets.KEY_PASSWORD }}" -PKeyAlias="${{ secrets.KEY_ALIAS }}" 27 | 28 | - name: Create release 29 | run: gh release create ${{ github.ref_name }} app/build/outputs/apk/release/app-release.apk#OwnDroid-${{ github.ref_name }}.apk -d --notes-from-tag 30 | env: 31 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: Build APK (testkey) 34 | run: ./gradlew build 35 | 36 | - name: Upload testkey APK 37 | run: gh release upload ${{ github.ref_name }} app/build/outputs/apk/release/app-release.apk#OwnDroid-${{ github.ref_name }}-testkey.apk 38 | env: 39 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | 41 | - name: Generate and submit dependency graph 42 | uses: gradle/actions/dependency-submission@v4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .idea 5 | .DS_Store 6 | build 7 | captures 8 | .externalNativeBuild 9 | .cxx 10 | app/build 11 | app/release 12 | app/debug 13 | .androidide 14 | -------------------------------------------------------------------------------- /Readme-en.md: -------------------------------------------------------------------------------- 1 | [日本語](Readme-ja.md) | [简体中文](Readme.md) 2 | 3 | # OwnDroid 4 | 5 | Use Android's DevicePolicyManager API to manage your device. 6 | 7 | ## Download 8 | 9 | - [IzzyOnDroid F-Droid Repository](https://apt.izzysoft.de/fdroid/index/apk/com.bintianqi.owndroid) 10 | - [Releases on GitHub](https://github.com/BinTianqi/OwnDroid/releases) 11 | 12 | > [!NOTE] 13 | > ColorOS users should download testkey version from releases on GitHub 14 | 15 | ## Features 16 | 17 | - System: Disable camera, disable screenshot, master volume mute, disable USB signal, Lock task mode, Manage CA certificates, Wipe data... 18 | - Network: Add/modify/delete Wi-Fi, Network stats, Minimum Wi-Fi security level, Always-on VPN, Network logging... 19 | - Applications: Suspend/hide app, Block app uninstallation, Grant/revoke permissions, Clear app storage, Install/uninstall app... 20 | - User restriction: Disable SMS, disable outgoing call, disable bluetooth, disable NFC, disable USB file transfer, disable app installing, disable app uninstalling... 21 | - User manager: User information, Start/switch/stop/delete user, Create user... 22 | - Password and keyguard: Reset password, Require password complexity, Set screen timeout... 23 | 24 | ## Working modes 25 | 26 | - Device owner (recommended) 27 | 28 | Activating methods: 29 | - Shizuku 30 | - Dhizuku 31 | - Root 32 | - ADB shell command `dpm set-device-owner com.bintianqi.owndroid/.Receiver` 33 | - [Dhizuku](https://github.com/iamr0s/Dhizuku) 34 | - Work profile 35 | 36 | ## FAQ 37 | 38 | ### Already some accounts on the device 39 | 40 | ```text 41 | java.lang.IllegalStateException: Not allowed to set the device owner because there are already some accounts on the device 42 | ``` 43 | 44 | Solutions: 45 | - Freeze apps who hold those accounts. 46 | - Delete these accounts. 47 | 48 | ### Already several users on the device 49 | 50 | ```text 51 | java.lang.IllegalStateException: Not allowed to set the device owner because there are already several users on the device 52 | ``` 53 | 54 | Solutions: 55 | - Delete secondary users. 56 | 57 | > [!NOTE] 58 | > Some systems have features such as app cloning and children space, which are usually users. 59 | 60 | ### MIUI 61 | 62 | ```text 63 | java.lang.SecurityException: Neither user 2000 nor current process has android.permission.MANAGE_DEVICE_ADMINS. 64 | ``` 65 | 66 | Solutions: 67 | - Enable `USB debugging (Security setting)` in developer options. 68 | - Execute activating command in root shell. 69 | 70 | ### ColorOS 71 | 72 | ```text 73 | java.lang.IllegalStateException: Unexpected @ProvisioningPreCondition 74 | ``` 75 | 76 | Solution: Use OwnDroid testkey version 77 | 78 | ## API 79 | 80 | | ID | Extras | Minimum Android version | 81 | |------------------------|---------------|:-----------------------:| 82 | | HIDE | `package` | | 83 | | UNHIDE | `package` | | 84 | | SUSPEND | `package` | 7 | 85 | | UNSUSPEND | `package` | 7 | 86 | | ADD_USER_RESTRICTION | `restriction` | | 87 | | CLEAR_USER_RESTRICTION | `restriction` | | 88 | | LOCK | | | 89 | | REBOOT | | 7 | 90 | 91 | [Available user restrictions](https://developer.android.com/reference/android/os/UserManager#constants_1) 92 | 93 | ```shell 94 | # An example of hiding app in ADB shell 95 | am broadcast -a com.bintianqi.owndroid.action.HIDE -n com.bintianqi.owndroid/.ApiReceiver --es key abcdefg --es package com.example.app 96 | ``` 97 | 98 | ```kotlin 99 | // An example of hiding app in Kotlin 100 | val intent = Intent("com.bintianqi.owndroid.action.HIDE") 101 | .setComponent(ComponentName("com.bintianqi.owndroid", "com.bintianqi.owndroid.ApiReceiver")) 102 | .putExtra("key", "abcdefg") 103 | .putExtra("package", "com.example.app") 104 | context.sendBroadcast(intent) 105 | ``` 106 | 107 | ## Build 108 | 109 | You can use Gradle in command line to build OwnDroid. 110 | ```shell 111 | # Use testkey for signing (default) 112 | ./gradlew build 113 | # Use your custom .jks key for signing 114 | ./gradlew build -PStoreFile="/path/to/your/jks/file" -PStorePassword="YOUR_KEYSTORE_PASSWORD" -PKeyPassword="YOUR_KEY_PASSWORD" -PKeyAlias="YOUR_KEY_ALIAS" 115 | ``` 116 | (Use `./gradlew.bat` instead on Windows) 117 | 118 | ## License 119 | 120 | [License.md](LICENSE.md) 121 | 122 | > Copyright (C) 2024 BinTianqi 123 | > 124 | > This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 125 | > 126 | > This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 127 | > 128 | > You should have received a copy of the GNU General Public License along with this program. If not, see . 129 | -------------------------------------------------------------------------------- /Readme-ja.md: -------------------------------------------------------------------------------- 1 | [English](Readme-en.md) | [简体中文](Readme.md) 2 | 3 | # OwnDroid 4 | 5 | AndroidのDevice owner特権を使用してデバイスを管理します。 6 | 7 | ## ダウンロード 8 | 9 | - [IzzyOnDroid F-Droid Repository](https://apt.izzysoft.de/fdroid/index/apk/com.bintianqi.owndroid) 10 | - [Releases on GitHub](https://github.com/BinTianqi/OwnDroid/releases) 11 | 12 | > [!NOTE] 13 | > ColorOSユーザーはGitHubのリリースからtestkeyバージョンをダウンロードしてください 14 | 15 | ## 機能 16 | 17 | - システム 18 | - オプション:カメラの無効化、スクリーンショットの無効化、マスターボリュームのミュート、USB信号の無効化... 19 | - 権限ポリシー 20 | - CA証明書の管理 21 | - _データの消去_ 22 | - ... 23 | - ネットワーク 24 | - Wi-Fiの追加/変更/削除 25 | - ネットワーク統計 26 | - 最小Wi-Fiセキュリティレベル 27 | - 常時オンVPN 28 | - ネットワークログ 29 | - ... 30 | - アプリケーション 31 | - アプリの一時停止/非表示 32 | - アンインストールのブロック 33 | - アプリのインストール/アンインストール 34 | - ... 35 | - ユーザー制限 36 | - ネットワーク:モバイルネットワークの設定禁止、Wi-Fiの設定禁止、SMSの無効化、発信通話の禁止... 37 | - 接続:Bluetoothの無効化、位置情報の設定禁止、USBファイル転送の無効化、印刷の無効化... 38 | - アプリケーション:アプリのインストール/アンインストールの禁止... 39 | - ユーザー:ユーザーの追加/削除/切り替えの禁止... 40 | - メディア:明るさの調整禁止、音量の調整禁止... 41 | - その他:アカウントの変更禁止、言語の設定禁止、工場出荷時設定へのリセット禁止、デバッグ機能の無効化... 42 | - ユーザー管理 43 | - ユーザー情報 44 | - ユーザーの起動/切り替え/停止/削除 45 | - ユーザーの作成 46 | - ... 47 | - パスワードとキーロック 48 | - _パスワードのリセット_ 49 | - パスワードの複雑さの要求 50 | - スクリーンタイムアウトの設定 51 | - ... 52 | 53 | ## アクティベート 54 | 55 | - Shizuku (推奨) 56 | - Dhizuku 57 | - Root 58 | - adbシェルでコマンドを実行: `dpm set-device-owner com.bintianqi.owndroid/.Receiver` 59 | 60 | ## FAQ 61 | 62 | ### デバイスに既にアカウントが存在する場合 63 | 64 | ```text 65 | java.lang.IllegalStateException: Not allowed to set the device owner because there are already some accounts on the device 66 | ``` 67 | 68 | 解決策: 69 | - これらのアカウントを保持しているアプリを凍結します。 70 | - これらのアカウントを削除します。 71 | 72 | ### デバイスに既に複数のユーザーが存在する場合 73 | 74 | ```text 75 | java.lang.IllegalStateException: Not allowed to set the device owner because there are already several users on the device 76 | ``` 77 | 78 | 解決策: 79 | - セカンダリユーザーを削除します。 80 | 81 | > [!NOTE] 82 | > 一部のシステムにはアプリのクローンや子供用スペースなどの機能があり、通常はユーザーとして扱われます。 83 | 84 | ### MIUI 85 | 86 | ```text 87 | java.lang.SecurityException: Neither user 2000 nor current process has android.permission.MANAGE_DEVICE_ADMINS. 88 | ``` 89 | 90 | 解決策: 91 | - 開発者オプションで `USBデバッグ(セキュリティ設定)` を有効にします。 92 | - ルートシェルでアクティベートコマンドを実行します。 93 | 94 | ### ColorOS 95 | 96 | ```text 97 | java.lang.IllegalStateException: Unexpected @ProvisioningPreCondition 98 | ``` 99 | 100 | 解決策:OwnDroid testkeyバージョンを使用します 101 | 102 | ## API 103 | 104 | | ID | Extras | 最小Androidバージョン | 105 | |------------------------|---------------|:--------------:| 106 | | HIDE | `package` | | 107 | | UNHIDE | `package` | | 108 | | SUSPEND | `package` | 7 | 109 | | UNSUSPEND | `package` | 7 | 110 | | ADD_USER_RESTRICTION | `restriction` | | 111 | | CLEAR_USER_RESTRICTION | `restriction` | | 112 | | LOCK | | | 113 | | REBOOT | | 7 | 114 | 115 | [利用可能なユーザー制限](https://developer.android.com/reference/android/os/UserManager#constants_1) 116 | 117 | ```shell 118 | # ADBシェルでアプリを非表示にする例 119 | am broadcast -a com.bintianqi.owndroid.action.HIDE -n com.bintianqi.owndroid/.ApiReceiver --es key abcdefg --es package com.example.app 120 | ``` 121 | 122 | ```kotlin 123 | // Kotlinでアプリを非表示にする例 124 | val intent = Intent("com.bintianqi.owndroid.action.HIDE") 125 | .setComponent(ComponentName("com.bintianqi.owndroid", "com.bintianqi.owndroid.ApiReceiver")) 126 | .putExtra("key", "abcdefg") 127 | .putExtra("package", "com.example.app") 128 | context.sendBroadcast(intent) 129 | ``` 130 | 131 | ## ビルド 132 | 133 | コマンドラインでGradleを使用してOwnDroidをビルドできます。 134 | ```shell 135 | # testkeyで署名(デフォルト) 136 | ./gradlew build 137 | # カスタム.jksキーで署名 138 | ./gradlew build -PStoreFile="/path/to/your/jks/file" -PStorePassword="YOUR_KEYSTORE_PASSWORD" -PKeyPassword="YOUR_KEY_PASSWORD" -PKeyAlias="YOUR_KEY_ALIAS" 139 | ``` 140 | (Windowsでは `./gradlew.bat` を使用してください) 141 | 142 | ## ライセンス 143 | 144 | [License.md](LICENSE.md) 145 | 146 | > Copyright (C) 2024 BinTianqi 147 | > 148 | > This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 149 | > 150 | > This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 151 | > 152 | > You should have received a copy of the GNU General Public License along with this program. If not, see . 153 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | [English](Readme-en.md) | [日本語](Readme-ja.md) 2 | 3 | # OwnDroid 4 | 5 | 使用安卓的设备策略管理器API管理你的设备。 6 | 7 | ## 下载 8 | 9 | - [IzzyOnDroid F-Droid Repository](https://apt.izzysoft.de/fdroid/index/apk/com.bintianqi.owndroid) 10 | - [Releases on GitHub](https://github.com/BinTianqi/OwnDroid/releases) 11 | 12 | > [!NOTE] 13 | > ColorOS用户应在GitHub上的releases下载testkey版本 14 | 15 | ## 功能 16 | 17 | - 系统:禁用摄像头、禁止截屏、全局静音、禁用USB信号、锁定任务模式、管理CA证书、清除数据... 18 | - 网络:添加/修改/删除 Wi-Fi、网络统计、最小Wi-Fi安全等级、VPN保持打开、网络日志... 19 | - 应用:挂起/隐藏应用、阻止应用卸载、授予/撤销权限、清除应用存储、安装/卸载应用... 20 | - 用户限制:禁止发送短信、禁止拨出电话、禁用蓝牙、禁用NFC、禁用USB文件传输、禁止安装应用、禁止卸载应用... 21 | - 用户:用户信息、启动/切换/停止/删除用户、创建用户... 22 | - 密码与锁屏:重置密码、要求密码复杂度、设置屏幕超时... 23 | 24 | ## 工作模式 25 | 26 | - Device owner(推荐) 27 | 28 | 激活方式: 29 | - Shizuku 30 | - Dhizuku 31 | - Root 32 | - ADB shell命令 `dpm set-device-owner com.bintianqi.owndroid/.Receiver` 33 | - [Dhizuku](https://github.com/iamr0s/Dhizuku) 34 | - 工作资料 35 | 36 | ## FAQ 37 | 38 | ### 设备上有账号 39 | 40 | ```text 41 | java.lang.IllegalStateException: Not allowed to set the device owner because there are already some accounts on the device 42 | ``` 43 | 44 | 解决办法: 45 | - 冻结持有这些账号的app。 46 | - 删除这些账号。 47 | 48 | ### 设备上有多个用户 49 | 50 | ```text 51 | java.lang.IllegalStateException: Not allowed to set the device owner because there are already several users on the device 52 | ``` 53 | 54 | 解决办法: 55 | - 删除次级用户。 56 | 57 | > [!NOTE] 58 | > 一些系统有应用克隆、儿童空间等功能,它们通常是用户。 59 | 60 | ### MIUI 61 | 62 | ```text 63 | java.lang.SecurityException: Neither user 2000 nor current process has android.permission.MANAGE_DEVICE_ADMINS. 64 | ``` 65 | 66 | 解决办法: 67 | - 在开发者设置中打开`USB调试(安全设置)`。 68 | - 在root命令行中执行激活命令 69 | 70 | ### ColorOS 71 | 72 | ```text 73 | java.lang.IllegalStateException: Unexpected @ProvisioningPreCondition 74 | ``` 75 | 76 | 解决办法:使用 OwnDroid testkey 版本 77 | 78 | ## API 79 | 80 | | ID | Extra | 最小安卓版本 | 81 | |------------------------|---------------|:------:| 82 | | HIDE | `package` | | 83 | | UNHIDE | `package` | | 84 | | SUSPEND | `package` | 7 | 85 | | UNSUSPEND | `package` | 7 | 86 | | ADD_USER_RESTRICTION | `restriction` | | 87 | | CLEAR_USER_RESTRICTION | `restriction` | | 88 | | LOCK | | | 89 | | REBOOT | | 7 | 90 | 91 | [可用的用户限制](https://developer.android.google.cn/reference/android/os/UserManager#constants_1) 92 | 93 | ```shell 94 | # 一个在ADB shell中隐藏app的示例 95 | am broadcast -a com.bintianqi.owndroid.action.HIDE -n com.bintianqi.owndroid/.ApiReceiver --es key abcdefg --es package com.example.app 96 | ``` 97 | 98 | ```kotlin 99 | // 一个在Kotlin中隐藏app的示例 100 | val intent = Intent("com.bintianqi.owndroid.action.HIDE") 101 | .setComponent(ComponentName("com.bintianqi.owndroid", "com.bintianqi.owndroid.ApiReceiver")) 102 | .putExtra("key", "abcdefg") 103 | .putExtra("package", "com.example.app") 104 | context.sendBroadcast(intent) 105 | ``` 106 | 107 | ## 构建 108 | 109 | 你可以在命令行中使用Gradle以构建OwnDroid 110 | ```shell 111 | # 使用testkey签名(默认) 112 | ./gradlew build 113 | # 使用你的jks密钥签名 114 | ./gradlew build -PStoreFile="/path/to/your/jks/file" -PStorePassword="YOUR_KEYSTORE_PASSWORD" -PKeyPassword="YOUR_KEY_PASSWORD" -PKeyAlias="YOUR_KEY_ALIAS" 115 | ``` 116 | (在Windows系统中应使用`./gradlew.bat`) 117 | 118 | ## 许可证 119 | 120 | [License.md](LICENSE.md) 121 | 122 | > Copyright (C) 2024 BinTianqi 123 | > 124 | > This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 125 | > 126 | > This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 127 | > 128 | > You should have received a copy of the GNU General Public License along with this program. If not, see . 129 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.compose) 5 | kotlin("plugin.serialization") version "2.1.20" 6 | } 7 | 8 | android { 9 | signingConfigs { 10 | create("defaultSignature") { 11 | storeFile = file(project.findProperty("StoreFile") ?: "testkey.jks") 12 | storePassword = (project.findProperty("StorePassword") as String?) ?: "testkey" 13 | keyPassword = (project.findProperty("KeyPassword") as String?) ?: "testkey" 14 | keyAlias = (project.findProperty("KeyAlias") as String?) ?: "testkey" 15 | } 16 | } 17 | namespace = "com.bintianqi.owndroid" 18 | compileSdk = 36 19 | 20 | lint.checkReleaseBuilds = false 21 | lint.disable += "All" 22 | 23 | defaultConfig { 24 | applicationId = "com.bintianqi.owndroid" 25 | minSdk = 21 26 | targetSdk = 36 27 | versionCode = 39 28 | versionName = "7.0" 29 | multiDexEnabled = false 30 | } 31 | 32 | buildTypes { 33 | release { 34 | isMinifyEnabled = true 35 | isShrinkResources = true 36 | proguardFiles( 37 | getDefaultProguardFile("proguard-android-optimize.txt"), 38 | "proguard-rules.pro" 39 | ) 40 | signingConfig = signingConfigs.getByName("defaultSignature") 41 | } 42 | debug { 43 | signingConfig = signingConfigs.getByName("defaultSignature") 44 | } 45 | } 46 | compileOptions { 47 | sourceCompatibility = JavaVersion.VERSION_20 48 | targetCompatibility = JavaVersion.VERSION_20 49 | } 50 | kotlinOptions { 51 | jvmTarget = "20" 52 | } 53 | buildFeatures { 54 | compose = true 55 | aidl = true 56 | } 57 | androidResources { 58 | generateLocaleConfig = true 59 | } 60 | dependenciesInfo { 61 | includeInApk = false 62 | } 63 | composeCompiler { 64 | includeSourceInformation = false 65 | includeTraceMarkers = false 66 | } 67 | } 68 | 69 | kotlin { 70 | sourceSets { 71 | all { 72 | languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi") 73 | } 74 | } 75 | } 76 | 77 | gradle.taskGraph.whenReady { 78 | project.tasks.findByPath(":app:test")?.enabled = false 79 | project.tasks.findByPath(":app:lint")?.enabled = false 80 | project.tasks.findByPath(":app:lintAnalyzeDebug")?.enabled = false 81 | } 82 | 83 | dependencies { 84 | implementation(libs.androidx.activity.compose) 85 | implementation(platform(libs.androidx.compose.bom)) 86 | implementation(libs.androidx.compose.ui.tooling.preview) 87 | debugImplementation(libs.androidx.compose.ui.tooling) 88 | implementation(libs.accompanist.drawablepainter) 89 | implementation(libs.accompanist.permissions) 90 | implementation(libs.androidx.material3) 91 | implementation(libs.androidx.navigation.compose) 92 | implementation(libs.shizuku.provider) 93 | implementation(libs.shizuku.api) 94 | implementation(libs.dhizuku.api) 95 | implementation(libs.dhizuku.server.api) 96 | implementation(libs.androidx.fragment) 97 | implementation(libs.hiddenApiBypass) 98 | implementation(libs.libsu) 99 | implementation(libs.serialization) 100 | implementation(kotlin("reflect")) 101 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # -dontobfuscate 9 | # If your project uses WebView with JS, uncomment the following 10 | # and specify the fully qualified class name to the JavaScript interface 11 | # class: 12 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 13 | # public *; 14 | #} 15 | 16 | # Uncomment this to preserve the line number information for 17 | # debugging stack traces. 18 | -keepattributes SourceFile,LineNumberTable 19 | 20 | # If you keep the line number information, uncomment this to 21 | # hide the original source file name. 22 | # -renamesourcefileattribute SourceFile 23 | 24 | -dontwarn android.app.ActivityThread 25 | -dontwarn android.app.ContextImpl 26 | -dontwarn android.app.LoadedApk 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 67 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 88 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 104 | 105 | 112 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /app/src/main/aidl/com/bintianqi/owndroid/IUserService.aidl: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid; 2 | 3 | import android.os.Bundle; 4 | 5 | interface IUserService { 6 | Bundle execute(String command) = 1; 7 | void destroy() = 16777114; 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/android/app/admin/IDevicePolicyManager.java: -------------------------------------------------------------------------------- 1 | package android.app.admin; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | 7 | import androidx.annotation.Keep; 8 | 9 | @Keep 10 | public interface IDevicePolicyManager extends IInterface { 11 | @Keep 12 | abstract class Stub extends Binder implements IDevicePolicyManager { 13 | public static IDevicePolicyManager asInterface(IBinder obj) { 14 | throw new UnsupportedOperationException(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/android/content/pm/IPackageInstaller.java: -------------------------------------------------------------------------------- 1 | package android.content.pm; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | 7 | import androidx.annotation.Keep; 8 | 9 | @Keep 10 | public interface IPackageInstaller extends IInterface { 11 | @Keep 12 | abstract class Stub extends Binder implements IPackageInstaller { 13 | public static IPackageInstaller asInterface(IBinder obj) { 14 | throw new UnsupportedOperationException(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/ApiReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.util.Log 7 | import com.bintianqi.owndroid.dpm.getDPM 8 | import com.bintianqi.owndroid.dpm.getReceiver 9 | 10 | class ApiReceiver: BroadcastReceiver() { 11 | override fun onReceive(context: Context, intent: Intent) { 12 | val requestKey = intent.getStringExtra("key") 13 | var log = "OwnDroid API request received. action: ${intent.action}\nkey: $requestKey" 14 | val sp = SharedPrefs(context) 15 | if(!sp.isApiEnabled) return 16 | val key = sp.apiKey 17 | if(!key.isNullOrEmpty() && key == requestKey) { 18 | val dpm = context.getDPM() 19 | val receiver = context.getReceiver() 20 | val app = intent.getStringExtra("package") 21 | val restriction = intent.getStringExtra("restriction") 22 | if(!app.isNullOrEmpty()) log += "\npackage: $app" 23 | try { 24 | @SuppressWarnings("NewApi") 25 | val ok = when(intent.action?.removePrefix("com.bintianqi.owndroid.action.")) { 26 | "HIDE" -> dpm.setApplicationHidden(receiver, app, true) 27 | "UNHIDE" -> dpm.setApplicationHidden(receiver, app, false) 28 | "SUSPEND" -> dpm.setPackagesSuspended(receiver, arrayOf(app), true).isEmpty() 29 | "UNSUSPEND" -> dpm.setPackagesSuspended(receiver, arrayOf(app), false).isEmpty() 30 | "ADD_USER_RESTRICTION" -> { dpm.addUserRestriction(receiver, restriction); true } 31 | "CLEAR_USER_RESTRICTION" -> { dpm.clearUserRestriction(receiver, restriction); true } 32 | "LOCK" -> { dpm.lockNow(); true } 33 | "REBOOT" -> { dpm.reboot(receiver); true } 34 | else -> { 35 | log += "\nInvalid action" 36 | false 37 | } 38 | } 39 | log += "\nsuccess: $ok" 40 | } catch(e: Exception) { 41 | e.printStackTrace() 42 | val message = (e::class.qualifiedName ?: "Exception") + ": " + (e.message ?: "") 43 | log += "\n$message" 44 | } 45 | } else { 46 | log += "\nUnauthorized" 47 | } 48 | Log.d(TAG, log) 49 | } 50 | companion object { 51 | private const val TAG = "API" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/AppLock.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.content.Context 4 | import android.hardware.biometrics.BiometricPrompt 5 | import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback 6 | import android.os.Build 7 | import android.os.CancellationSignal 8 | import androidx.activity.compose.BackHandler 9 | import androidx.annotation.RequiresApi 10 | import androidx.compose.foundation.gestures.detectTapGestures 11 | import androidx.compose.foundation.layout.Column 12 | import androidx.compose.foundation.layout.Row 13 | import androidx.compose.foundation.layout.padding 14 | import androidx.compose.foundation.layout.width 15 | import androidx.compose.foundation.shape.RoundedCornerShape 16 | import androidx.compose.foundation.text.KeyboardActions 17 | import androidx.compose.foundation.text.KeyboardOptions 18 | import androidx.compose.material3.Button 19 | import androidx.compose.material3.Card 20 | import androidx.compose.material3.FilledTonalIconButton 21 | import androidx.compose.material3.Icon 22 | import androidx.compose.material3.OutlinedTextField 23 | import androidx.compose.material3.Text 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.runtime.LaunchedEffect 26 | import androidx.compose.runtime.getValue 27 | import androidx.compose.runtime.mutableStateOf 28 | import androidx.compose.runtime.remember 29 | import androidx.compose.runtime.setValue 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.focus.FocusRequester 33 | import androidx.compose.ui.focus.focusRequester 34 | import androidx.compose.ui.input.pointer.pointerInput 35 | import androidx.compose.ui.platform.LocalContext 36 | import androidx.compose.ui.platform.LocalFocusManager 37 | import androidx.compose.ui.res.painterResource 38 | import androidx.compose.ui.res.stringResource 39 | import androidx.compose.ui.text.input.ImeAction 40 | import androidx.compose.ui.text.input.KeyboardType 41 | import androidx.compose.ui.unit.dp 42 | import androidx.compose.ui.window.Dialog 43 | import androidx.compose.ui.window.DialogProperties 44 | 45 | @Composable 46 | fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismiss, DialogProperties(true, false)) { 47 | val context = LocalContext.current 48 | val fm = LocalFocusManager.current 49 | val fr = FocusRequester() 50 | val sp = SharedPrefs(context) 51 | var input by remember { mutableStateOf("") } 52 | var isError by remember { mutableStateOf(false) } 53 | fun unlock() { 54 | if(input.hash() == sp.lockPasswordHash) { 55 | fm.clearFocus() 56 | onSucceed() 57 | } else { 58 | isError = true 59 | } 60 | } 61 | LaunchedEffect(Unit) { 62 | fr.requestFocus() 63 | } 64 | BackHandler(onBack = onDismiss) 65 | Card(Modifier.pointerInput(Unit) { detectTapGestures(onTap = { fm.clearFocus() }) }, shape = RoundedCornerShape(16.dp)) { 66 | Column(Modifier.padding(12.dp)) { 67 | Row(verticalAlignment = Alignment.CenterVertically) { 68 | OutlinedTextField( 69 | input, { input = it; isError = false }, Modifier.width(200.dp).focusRequester(fr), 70 | label = { Text(stringResource(R.string.password)) }, isError = isError, 71 | keyboardOptions = KeyboardOptions( 72 | keyboardType = KeyboardType.Password, imeAction = if(input.length >= 4) ImeAction.Go else ImeAction.Done 73 | ), 74 | keyboardActions = KeyboardActions({ fm.clearFocus() }, { unlock() }) 75 | ) 76 | if(Build.VERSION.SDK_INT >= 28 && sp.biometricsUnlock) { 77 | FilledTonalIconButton({ startBiometricsUnlock(context, onSucceed) }, Modifier.padding(start = 4.dp)) { 78 | Icon(painterResource(R.drawable.fingerprint_fill0), null) 79 | } 80 | } 81 | } 82 | Button(::unlock, Modifier.align(Alignment.End).padding(top = 8.dp), input.length >= 4) { 83 | Text(stringResource(R.string.unlock)) 84 | } 85 | } 86 | } 87 | } 88 | 89 | @RequiresApi(28) 90 | fun startBiometricsUnlock(context: Context, onSucceed: () -> Unit) { 91 | val callback = object : AuthenticationCallback() { 92 | override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) { 93 | super.onAuthenticationSucceeded(result) 94 | onSucceed() 95 | } 96 | override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { 97 | super.onAuthenticationError(errorCode, errString) 98 | if(errorCode != BiometricPrompt.BIOMETRIC_ERROR_CANCELED) context.showOperationResultToast(false) 99 | } 100 | } 101 | val cancel = CancellationSignal() 102 | BiometricPrompt.Builder(context) 103 | .setTitle(context.getText(R.string.unlock)) 104 | .setNegativeButton(context.getString(R.string.cancel), context.mainExecutor) { _, _ -> cancel.cancel() } 105 | .build() 106 | .authenticate(cancel, context.mainExecutor, callback) 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.content.ComponentName 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.os.Build 7 | import android.os.Bundle 8 | import android.util.Log 9 | import androidx.activity.ComponentActivity 10 | import androidx.activity.compose.setContent 11 | import androidx.activity.enableEdgeToEdge 12 | import androidx.activity.viewModels 13 | import androidx.compose.foundation.Image 14 | import androidx.compose.foundation.layout.size 15 | import androidx.compose.material3.AlertDialog 16 | import androidx.compose.material3.Text 17 | import androidx.compose.material3.TextButton 18 | import androidx.compose.runtime.LaunchedEffect 19 | import androidx.compose.runtime.getValue 20 | import androidx.compose.runtime.mutableIntStateOf 21 | import androidx.compose.runtime.mutableStateOf 22 | import androidx.compose.runtime.remember 23 | import androidx.compose.runtime.setValue 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.res.stringResource 26 | import androidx.compose.ui.unit.dp 27 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 28 | import com.bintianqi.owndroid.ui.theme.OwnDroidTheme 29 | import com.google.accompanist.drawablepainter.rememberDrawablePainter 30 | import com.rosan.dhizuku.aidl.IDhizukuClient 31 | import com.rosan.dhizuku.aidl.IDhizukuRequestPermissionListener 32 | import com.rosan.dhizuku.server_api.DhizukuProvider 33 | import com.rosan.dhizuku.server_api.DhizukuService 34 | import com.rosan.dhizuku.shared.DhizukuVariables 35 | import kotlinx.coroutines.delay 36 | import kotlinx.serialization.Serializable 37 | import kotlinx.serialization.encodeToString 38 | import kotlinx.serialization.json.Json 39 | 40 | private const val TAG = "DhizukuServer" 41 | 42 | const val DHIZUKU_CLIENTS_FILE = "dhizuku_clients.json" 43 | 44 | class MyDhizukuProvider(): DhizukuProvider() { 45 | override fun onCreateService(client: IDhizukuClient): DhizukuService? { 46 | Log.d(TAG, "Creating MyDhizukuService") 47 | return if (SharedPrefs(context!!).dhizukuServer) MyDhizukuService(context!!, MyAdminComponent, client) else null 48 | } 49 | } 50 | 51 | class MyDhizukuService(context: Context, admin: ComponentName, client: IDhizukuClient) : 52 | DhizukuService(context, admin, client) { 53 | override fun checkCallingPermission(func: String?, callingUid: Int, callingPid: Int): Boolean { 54 | if (!SharedPrefs(mContext).dhizukuServer) return false 55 | val pm = mContext.packageManager 56 | val packageInfo = pm.getPackageInfo( 57 | pm.getNameForUid(callingUid) ?: return false, 58 | if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES else PackageManager.GET_SIGNATURES 59 | ) 60 | val file = mContext.filesDir.resolve(DHIZUKU_CLIENTS_FILE) 61 | val clients = Json.decodeFromString>(file.readText()) 62 | val signature = getPackageSignature(packageInfo) 63 | val hasPermission = DhizukuClientInfo(callingUid, signature, true) in clients 64 | Log.d(TAG, "UID $callingUid, PID $callingPid, has permission: $hasPermission") 65 | return hasPermission 66 | } 67 | 68 | override fun getVersionName() = "1.0" 69 | } 70 | 71 | class DhizukuActivity : ComponentActivity() { 72 | @OptIn(ExperimentalStdlibApi::class) 73 | override fun onCreate(savedInstanceState: Bundle?) { 74 | super.onCreate(savedInstanceState) 75 | if (!SharedPrefs(this).dhizukuServer) { 76 | finish() 77 | return 78 | } 79 | val bundle = intent.extras ?: return 80 | val uid = bundle.getInt(DhizukuVariables.PARAM_CLIENT_UID, -1) 81 | if (uid == -1) return 82 | val binder = bundle.getBinder(DhizukuVariables.PARAM_CLIENT_REQUEST_PERMISSION_BINDER) ?: return 83 | val listener = IDhizukuRequestPermissionListener.Stub.asInterface(binder) 84 | val packageName = packageManager.getPackagesForUid(uid)?.first() ?: return 85 | val packageInfo = packageManager.getPackageInfo( 86 | packageName, 87 | if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES else PackageManager.GET_SIGNATURES 88 | ) 89 | val appInfo = packageManager.getApplicationInfo(packageName, 0) 90 | val icon = appInfo.loadIcon(packageManager) 91 | val label = appInfo.loadLabel(packageManager).toString() 92 | fun close(grantPermission: Boolean) { 93 | val file = filesDir.resolve(DHIZUKU_CLIENTS_FILE) 94 | val clients = Json.decodeFromString>(file.readText()) 95 | val index = clients.indexOfFirst { it.uid == uid } 96 | val clientInfo = DhizukuClientInfo(uid, getPackageSignature(packageInfo), grantPermission) 97 | if (index == -1) clients += clientInfo 98 | else clients[index] = clientInfo 99 | file.writeText(Json.encodeToString(clients)) 100 | finish() 101 | listener.onRequestPermission( 102 | if (grantPermission) PackageManager.PERMISSION_GRANTED else PackageManager.PERMISSION_DENIED 103 | ) 104 | } 105 | val vm by viewModels() 106 | enableEdgeToEdge() 107 | setContent { 108 | var appLockDialog by remember { mutableStateOf(false) } 109 | val theme by vm.theme.collectAsStateWithLifecycle() 110 | OwnDroidTheme(theme) { 111 | if (!appLockDialog) AlertDialog( 112 | icon = { 113 | Image(rememberDrawablePainter(icon), null, Modifier.size(35.dp)) 114 | }, 115 | title = { 116 | Text(stringResource(R.string.request_permission)) 117 | }, 118 | text = { 119 | Text("$label\n($packageName)") 120 | }, 121 | confirmButton = { 122 | var time by remember { mutableIntStateOf(3) } 123 | LaunchedEffect(Unit) { 124 | (1..3).forEach { 125 | delay(1000) 126 | time -= 1 127 | } 128 | } 129 | TextButton({ 130 | if (SharedPrefs(this).lockPasswordHash.isNullOrEmpty()) { 131 | close(true) 132 | } else { 133 | appLockDialog = true 134 | } 135 | }, enabled = time == 0) { 136 | val append = if (time > 0) " (${time}s)" else "" 137 | Text(stringResource(R.string.allow) + append) 138 | } 139 | }, 140 | dismissButton = { 141 | TextButton({ 142 | close(false) 143 | }) { 144 | Text(stringResource(R.string.reject)) 145 | } 146 | }, 147 | onDismissRequest = { close(false) } 148 | ) 149 | else AppLockDialog({ close(true) }) { close(false) } 150 | } 151 | } 152 | } 153 | } 154 | 155 | 156 | @Serializable 157 | data class DhizukuClientInfo( 158 | val uid: Int, 159 | val signature: String?, 160 | val allow: Boolean 161 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/ManageSpaceActivity.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | import androidx.activity.compose.setContent 6 | import androidx.activity.enableEdgeToEdge 7 | import androidx.activity.viewModels 8 | import androidx.compose.material3.AlertDialog 9 | import androidx.compose.material3.Text 10 | import androidx.compose.material3.TextButton 11 | import androidx.compose.runtime.getValue 12 | import androidx.compose.runtime.mutableStateOf 13 | import androidx.compose.runtime.remember 14 | import androidx.compose.runtime.setValue 15 | import androidx.compose.ui.res.stringResource 16 | import androidx.core.content.edit 17 | import androidx.fragment.app.FragmentActivity 18 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 19 | import com.bintianqi.owndroid.ui.theme.OwnDroidTheme 20 | import kotlin.system.exitProcess 21 | 22 | class ManageSpaceActivity: FragmentActivity() { 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | enableEdgeToEdge() 25 | super.onCreate(savedInstanceState) 26 | val vm by viewModels() 27 | setContent { 28 | val theme by vm.theme.collectAsStateWithLifecycle() 29 | OwnDroidTheme(theme) { 30 | var appLockDialog by remember { mutableStateOf(!SharedPrefs(this).lockPasswordHash.isNullOrEmpty()) } 31 | if(appLockDialog) { 32 | AppLockDialog({ appLockDialog = false }, ::finish) 33 | } else { 34 | AlertDialog( 35 | text = { 36 | Text(stringResource(R.string.clear_storage)) 37 | }, 38 | onDismissRequest = ::finish, 39 | dismissButton = { 40 | TextButton(::finish) { 41 | Text(stringResource(R.string.cancel)) 42 | } 43 | }, 44 | confirmButton = { 45 | TextButton(::clearStorage) { 46 | Text(stringResource(R.string.confirm)) 47 | } 48 | } 49 | ) 50 | } 51 | } 52 | } 53 | } 54 | 55 | fun clearStorage() { 56 | filesDir.deleteRecursively() 57 | cacheDir.deleteRecursively() 58 | codeCacheDir.deleteRecursively() 59 | if(Build.VERSION.SDK_INT >= 24) { 60 | dataDir.resolve("shared_prefs").deleteRecursively() 61 | } else { 62 | val sharedPref = applicationContext.getSharedPreferences("data", MODE_PRIVATE) 63 | sharedPref.edit { clear() } 64 | } 65 | this.showOperationResultToast(true) 66 | finish() 67 | exitProcess(0) 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.launch 8 | 9 | class MyViewModel(application: Application): AndroidViewModel(application) { 10 | val theme = MutableStateFlow(ThemeSettings()) 11 | 12 | init { 13 | val sp = SharedPrefs(application) 14 | theme.value = ThemeSettings(sp.materialYou, sp.darkTheme, sp.blackTheme) 15 | viewModelScope.launch { 16 | theme.collect { 17 | sp.materialYou = it.materialYou 18 | sp.darkTheme = it.darkTheme 19 | sp.blackTheme = it.blackTheme 20 | } 21 | } 22 | } 23 | } 24 | 25 | data class ThemeSettings( 26 | val materialYou: Boolean = false, 27 | val darkTheme: Int = -1, 28 | val blackTheme: Boolean = false 29 | ) 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/NotificationUtils.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.Manifest 4 | import android.app.Notification 5 | import android.app.NotificationChannel 6 | import android.app.NotificationManager 7 | import android.content.Context 8 | import android.content.pm.PackageManager 9 | import android.os.Build 10 | 11 | object NotificationUtils { 12 | fun checkPermission(context: Context): Boolean { 13 | return if(Build.VERSION.SDK_INT >= 33) 14 | context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED 15 | else false 16 | } 17 | fun registerChannels(context: Context) { 18 | if(Build.VERSION.SDK_INT < 26) return 19 | val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 20 | val lockTaskMode = NotificationChannel(Channel.LOCK_TASK_MODE, context.getString(R.string.lock_task_mode), NotificationManager.IMPORTANCE_HIGH) 21 | val events = NotificationChannel(Channel.EVENTS, context.getString(R.string.events), NotificationManager.IMPORTANCE_HIGH) 22 | nm.createNotificationChannels(listOf(lockTaskMode, events)) 23 | } 24 | fun notify(context: Context, id: Int, notification: Notification) { 25 | val sp = context.getSharedPreferences("data", Context.MODE_PRIVATE) 26 | if(sp.getBoolean("n_$id", true) && checkPermission(context)) { 27 | registerChannels(context) 28 | val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 29 | nm.notify(id, notification) 30 | } 31 | } 32 | object Channel { 33 | const val LOCK_TASK_MODE = "LockTaskMode" 34 | const val EVENTS = "Events" 35 | } 36 | object ID { 37 | const val LOCK_TASK_MODE = 1 38 | const val PASSWORD_CHANGED = 2 39 | const val USER_ADDED = 3 40 | const val USER_STARTED = 4 41 | const val USER_SWITCHED = 5 42 | const val USER_STOPPED = 6 43 | const val USER_REMOVED = 7 44 | const val BUG_REPORT_SHARED = 8 45 | const val BUG_REPORT_SHARING_DECLINED = 9 46 | const val BUG_REPORT_FAILED = 10 47 | const val SYSTEM_UPDATE_PENDING = 11 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/Privilege.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.content.Context 4 | import android.os.Binder 5 | import android.os.Build 6 | import com.bintianqi.owndroid.dpm.getDPM 7 | import com.bintianqi.owndroid.dpm.getReceiver 8 | import com.bintianqi.owndroid.dpm.isDeviceOwner 9 | import com.bintianqi.owndroid.dpm.isProfileOwner 10 | import kotlinx.coroutines.flow.MutableStateFlow 11 | 12 | class Privilege( 13 | val device: Boolean = false, // Device owner 14 | val profile: Boolean = false, // Profile owner 15 | val dhizuku: Boolean = false, 16 | val work: Boolean = false, // Work profile 17 | val org: Boolean = false, // Organization-owned work profile 18 | val affiliated: Boolean = false 19 | ) { 20 | val primary = Binder.getCallingUid() / 100000 == 0 // Primary user 21 | } 22 | 23 | val myPrivilege = MutableStateFlow(Privilege()) 24 | 25 | fun updatePrivilege(context: Context) { 26 | val dpm = context.getDPM() 27 | val receiver = context.getReceiver() 28 | val profile = context.isProfileOwner 29 | val work = profile && Build.VERSION.SDK_INT >= 24 && dpm.isManagedProfile(receiver) 30 | myPrivilege.value = Privilege( 31 | device = context.isDeviceOwner, 32 | profile = profile, 33 | dhizuku = SharedPrefs(context).dhizuku, 34 | work = work, 35 | org = work && Build.VERSION.SDK_INT >= 30 && dpm.isOrganizationOwnedDeviceWithManagedProfile, 36 | affiliated = Build.VERSION.SDK_INT >= 28 && dpm.isAffiliatedUser 37 | ) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/Receiver.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.app.NotificationManager 4 | import android.app.PendingIntent 5 | import android.app.admin.DeviceAdminReceiver 6 | import android.content.ComponentName 7 | import android.content.Context 8 | import android.content.Intent 9 | import android.os.Build.VERSION 10 | import android.os.PersistableBundle 11 | import android.os.UserHandle 12 | import android.os.UserManager 13 | import android.widget.Toast 14 | import androidx.core.app.NotificationCompat 15 | import com.bintianqi.owndroid.dpm.handleNetworkLogs 16 | import com.bintianqi.owndroid.dpm.handlePrivilegeChange 17 | import com.bintianqi.owndroid.dpm.processSecurityLogs 18 | import kotlinx.coroutines.CoroutineScope 19 | import kotlinx.coroutines.Dispatchers 20 | import kotlinx.coroutines.launch 21 | import java.text.SimpleDateFormat 22 | import java.util.Date 23 | import java.util.Locale 24 | 25 | class Receiver : DeviceAdminReceiver() { 26 | override fun onReceive(context: Context, intent: Intent) { 27 | super.onReceive(context, intent) 28 | if(VERSION.SDK_INT >= 26 && intent.action == "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE") { 29 | val dpm = getManager(context) 30 | val receiver = ComponentName(context, this::class.java) 31 | val packages = dpm.getLockTaskPackages(receiver) 32 | dpm.setLockTaskPackages(receiver, arrayOf()) 33 | dpm.setLockTaskPackages(receiver, packages) 34 | } 35 | } 36 | 37 | override fun onEnabled(context: Context, intent: Intent) { 38 | super.onEnabled(context, intent) 39 | updatePrivilege(context) 40 | handlePrivilegeChange(context) 41 | } 42 | 43 | override fun onDisabled(context: Context, intent: Intent) { 44 | super.onDisabled(context, intent) 45 | updatePrivilege(context) 46 | handlePrivilegeChange(context) 47 | } 48 | 49 | override fun onProfileProvisioningComplete(context: Context, intent: Intent) { 50 | super.onProfileProvisioningComplete(context, intent) 51 | Toast.makeText(context, R.string.create_work_profile_success, Toast.LENGTH_SHORT).show() 52 | } 53 | 54 | override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) { 55 | super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount) 56 | if(VERSION.SDK_INT >= 26) { 57 | CoroutineScope(Dispatchers.IO).launch { 58 | handleNetworkLogs(context, batchToken) 59 | } 60 | } 61 | } 62 | 63 | override fun onSecurityLogsAvailable(context: Context, intent: Intent) { 64 | super.onSecurityLogsAvailable(context, intent) 65 | if(VERSION.SDK_INT >= 24) { 66 | CoroutineScope(Dispatchers.IO).launch { 67 | val events = getManager(context).retrieveSecurityLogs(MyAdminComponent) ?: return@launch 68 | val file = context.filesDir.resolve("SecurityLogs.json") 69 | val fileExists = file.exists() 70 | file.outputStream().use { 71 | if(fileExists) it.write(",".encodeToByteArray()) 72 | processSecurityLogs(events, it) 73 | } 74 | } 75 | } 76 | } 77 | 78 | override fun onTransferOwnershipComplete(context: Context, bundle: PersistableBundle?) { 79 | super.onTransferOwnershipComplete(context, bundle) 80 | SharedPrefs(context).dhizuku = false 81 | } 82 | 83 | override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) { 84 | super.onLockTaskModeEntering(context, intent, pkg) 85 | if(!NotificationUtils.checkPermission(context)) return 86 | NotificationUtils.registerChannels(context) 87 | val intent = Intent(context, this::class.java).setAction("com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE") 88 | val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) 89 | val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.LOCK_TASK_MODE) 90 | .setContentTitle(context.getText(R.string.lock_task_mode)) 91 | .setSmallIcon(R.drawable.lock_fill0) 92 | .addAction(NotificationCompat.Action.Builder(null, context.getString(R.string.stop), pendingIntent).build()) 93 | NotificationUtils.notify(context, NotificationUtils.ID.LOCK_TASK_MODE, builder.build()) 94 | } 95 | 96 | override fun onLockTaskModeExiting(context: Context, intent: Intent) { 97 | super.onLockTaskModeExiting(context, intent) 98 | val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 99 | nm.cancel(NotificationUtils.ID.LOCK_TASK_MODE) 100 | } 101 | 102 | override fun onPasswordChanged(context: Context, intent: Intent, userHandle: UserHandle) { 103 | super.onPasswordChanged(context, intent, userHandle) 104 | sendUserRelatedNotification(context, userHandle, NotificationUtils.ID.PASSWORD_CHANGED, R.string.password_changed, R.drawable.password_fill0) 105 | } 106 | 107 | override fun onUserAdded(context: Context, intent: Intent, addedUser: UserHandle) { 108 | super.onUserAdded(context, intent, addedUser) 109 | sendUserRelatedNotification(context, addedUser, NotificationUtils.ID.USER_ADDED, R.string.user_added, R.drawable.person_add_fill0) 110 | } 111 | 112 | override fun onUserStarted(context: Context, intent: Intent, startedUser: UserHandle) { 113 | super.onUserStarted(context, intent, startedUser) 114 | sendUserRelatedNotification(context, startedUser, NotificationUtils.ID.USER_STARTED, R.string.user_started, R.drawable.person_fill0) 115 | } 116 | 117 | override fun onUserSwitched(context: Context, intent: Intent, switchedUser: UserHandle) { 118 | super.onUserSwitched(context, intent, switchedUser) 119 | sendUserRelatedNotification(context, switchedUser, NotificationUtils.ID.USER_SWITCHED, R.string.user_switched, R.drawable.person_fill0) 120 | } 121 | 122 | override fun onUserStopped(context: Context, intent: Intent, stoppedUser: UserHandle) { 123 | super.onUserStopped(context, intent, stoppedUser) 124 | sendUserRelatedNotification(context, stoppedUser, NotificationUtils.ID.USER_STOPPED, R.string.user_stopped, R.drawable.person_fill0) 125 | } 126 | 127 | override fun onUserRemoved(context: Context, intent: Intent, removedUser: UserHandle) { 128 | super.onUserRemoved(context, intent, removedUser) 129 | sendUserRelatedNotification(context, removedUser, NotificationUtils.ID.USER_REMOVED, R.string.user_removed, R.drawable.person_remove_fill0) 130 | } 131 | 132 | override fun onBugreportShared(context: Context, intent: Intent, hash: String) { 133 | super.onBugreportShared(context, intent, hash) 134 | val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS) 135 | .setContentTitle(context.getString(R.string.bug_report_shared)) 136 | .setContentText("SHA-256 hash: $hash") 137 | .setSmallIcon(R.drawable.bug_report_fill0) 138 | NotificationUtils.notify(context, NotificationUtils.ID.BUG_REPORT_SHARED, builder.build()) 139 | } 140 | 141 | override fun onBugreportSharingDeclined(context: Context, intent: Intent) { 142 | super.onBugreportSharingDeclined(context, intent) 143 | val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS) 144 | .setContentTitle(context.getString(R.string.bug_report_sharing_declined)) 145 | .setSmallIcon(R.drawable.bug_report_fill0) 146 | NotificationUtils.notify(context, NotificationUtils.ID.BUG_REPORT_SHARING_DECLINED, builder.build()) 147 | } 148 | 149 | override fun onBugreportFailed(context: Context, intent: Intent, failureCode: Int) { 150 | super.onBugreportFailed(context, intent, failureCode) 151 | val message = when(failureCode) { 152 | BUGREPORT_FAILURE_FAILED_COMPLETING -> R.string.bug_report_failure_failed_completing 153 | BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE -> R.string.bug_report_failure_no_longer_available 154 | else -> R.string.place_holder 155 | } 156 | val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS) 157 | .setContentTitle(context.getString(R.string.bug_report_failed)) 158 | .setContentText(context.getString(message)) 159 | .setSmallIcon(R.drawable.bug_report_fill0) 160 | NotificationUtils.notify(context, NotificationUtils.ID.BUG_REPORT_FAILED, builder.build()) 161 | } 162 | 163 | override fun onSystemUpdatePending(context: Context, intent: Intent, receivedTime: Long) { 164 | super.onSystemUpdatePending(context, intent, receivedTime) 165 | val time = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(Date(receivedTime)) 166 | val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS) 167 | .setContentTitle(context.getString(R.string.system_update_pending)) 168 | .setContentText(context.getString(R.string.received_time) + ": $time") 169 | .setSmallIcon(R.drawable.system_update_fill0) 170 | NotificationUtils.notify(context, NotificationUtils.ID.SYSTEM_UPDATE_PENDING, builder.build()) 171 | } 172 | 173 | private fun sendUserRelatedNotification(context: Context, userHandle: UserHandle, id: Int, title: Int, icon: Int) { 174 | val um = context.getSystemService(Context.USER_SERVICE) as UserManager 175 | val serial = um.getSerialNumberForUser(userHandle) 176 | val builder = NotificationCompat.Builder(context, NotificationUtils.Channel.EVENTS) 177 | .setContentTitle(context.getString(title)) 178 | .setContentText(context.getString(R.string.serial_number) + ": $serial") 179 | .setSmallIcon(icon) 180 | NotificationUtils.notify(context, id, builder.build()) 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/SharedPrefs.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import android.os.Build 6 | import androidx.core.content.edit 7 | import kotlin.properties.ReadWriteProperty 8 | import kotlin.reflect.KProperty 9 | 10 | class SharedPrefs(context: Context) { 11 | val sharedPrefs: SharedPreferences = context.getSharedPreferences("data", Context.MODE_PRIVATE) 12 | var managedProfileActivated by BooleanSharedPref("managed_profile_activated") 13 | var dhizuku by BooleanSharedPref("dhizuku_mode") 14 | var isDefaultAffiliationIdSet by BooleanSharedPref("default_affiliation_id_set") 15 | var displayDangerousFeatures by BooleanSharedPref("display_dangerous_features") 16 | var isApiEnabled by BooleanSharedPref("api.enabled") 17 | var apiKey by StringSharedPref("api.key") 18 | var materialYou by BooleanSharedPref("theme.material_you", Build.VERSION.SDK_INT >= 31) 19 | /** -1: follow system, 0: off, 1: on */ 20 | var darkTheme by IntSharedPref("theme.dark", -1) 21 | var blackTheme by BooleanSharedPref("theme.black") 22 | var lockPasswordHash by StringSharedPref("lock.password.sha256") 23 | var biometricsUnlock by BooleanSharedPref("lock.biometrics") 24 | var lockWhenLeaving by BooleanSharedPref("lock.onleave") 25 | var applicationsListView by BooleanSharedPref("applications.list_view", true) 26 | var shortcuts by BooleanSharedPref("shortcuts") 27 | var dhizukuServer by BooleanSharedPref("dhizuku_server") 28 | } 29 | 30 | private class BooleanSharedPref(val key: String, val defValue: Boolean = false): ReadWriteProperty { 31 | override fun getValue(thisRef: SharedPrefs, property: KProperty<*>): Boolean = 32 | thisRef.sharedPrefs.getBoolean(key, defValue) 33 | override fun setValue(thisRef: SharedPrefs, property: KProperty<*>, value: Boolean) = 34 | thisRef.sharedPrefs.edit { putBoolean(key, value) } 35 | } 36 | 37 | private class StringSharedPref(val key: String): ReadWriteProperty { 38 | override fun getValue(thisRef: SharedPrefs, property: KProperty<*>): String? = 39 | thisRef.sharedPrefs.getString(key, null) 40 | override fun setValue(thisRef: SharedPrefs, property: KProperty<*>, value: String?) = 41 | thisRef.sharedPrefs.edit { putString(key, value) } 42 | } 43 | 44 | private class IntSharedPref(val key: String, val defValue: Int = 0): ReadWriteProperty { 45 | override fun getValue(thisRef: SharedPrefs, property: KProperty<*>): Int = 46 | thisRef.sharedPrefs.getInt(key, defValue) 47 | override fun setValue(thisRef: SharedPrefs, property: KProperty<*>, value: Int) = 48 | thisRef.sharedPrefs.edit { putInt(key, value) } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/ShizukuService.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.content.ComponentName 4 | import android.content.Context 5 | import android.content.ServiceConnection 6 | import android.content.pm.PackageManager 7 | import android.os.Bundle 8 | import android.os.IBinder 9 | import android.widget.Toast 10 | import androidx.annotation.Keep 11 | import rikka.shizuku.Shizuku 12 | import rikka.sui.Sui 13 | import kotlin.system.exitProcess 14 | 15 | @Keep 16 | class ShizukuService: IUserService.Stub() { 17 | override fun execute(command: String): Bundle? { 18 | try { 19 | val bundle = Bundle() 20 | val process = Runtime.getRuntime().exec(command) 21 | val exitCode = process.waitFor() 22 | bundle.putInt("code", exitCode) 23 | bundle.putString("output", process.inputStream.readBytes().decodeToString()) 24 | bundle.putString("error", process.errorStream.readBytes().decodeToString()) 25 | return bundle 26 | } catch(e: Exception) { 27 | e.printStackTrace() 28 | return null 29 | } 30 | } 31 | 32 | override fun destroy() { 33 | exitProcess(0) 34 | } 35 | } 36 | 37 | fun getShizukuArgs(context: Context): Shizuku.UserServiceArgs { 38 | return Shizuku.UserServiceArgs(ComponentName(context, ShizukuService::class.java)) 39 | .daemon(false) 40 | .processNameSuffix("shizuku-service") 41 | .debuggable(false) 42 | .version(1) 43 | } 44 | 45 | fun useShizuku(context: Context, action: (IBinder?) -> Unit) { 46 | val connection = object : ServiceConnection { 47 | override fun onServiceConnected(name: ComponentName?, service: IBinder?) { 48 | action(service) 49 | Shizuku.unbindUserService(getShizukuArgs(context), this, true) 50 | } 51 | override fun onServiceDisconnected(name: ComponentName?) {} 52 | } 53 | try { 54 | if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { 55 | Shizuku.bindUserService(getShizukuArgs(context), connection) 56 | } else if(Shizuku.shouldShowRequestPermissionRationale()) { 57 | Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_SHORT).show() 58 | } else { 59 | Sui.init(context.packageName) 60 | fun requestPermissionResultListener(requestCode: Int, grantResult: Int) { 61 | if(grantResult != PackageManager.PERMISSION_GRANTED) { 62 | Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_SHORT).show() 63 | } 64 | Shizuku.removeRequestPermissionResultListener(::requestPermissionResultListener) 65 | } 66 | Shizuku.addRequestPermissionResultListener(::requestPermissionResultListener) 67 | Shizuku.requestPermission(0) 68 | } 69 | } catch (e: Exception) { 70 | e.printStackTrace() 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/ShortcutsReceiverActivity.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import androidx.core.content.pm.ShortcutInfoCompat 8 | import androidx.core.content.pm.ShortcutManagerCompat 9 | import androidx.core.graphics.drawable.IconCompat 10 | import com.bintianqi.owndroid.dpm.getDPM 11 | import com.bintianqi.owndroid.dpm.getReceiver 12 | 13 | class ShortcutsReceiverActivity : Activity() { 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | try { 17 | val action = intent.action?.removePrefix("com.bintianqi.owndroid.action.") 18 | if (action != null && SharedPrefs(this).shortcuts) { 19 | val dpm = getDPM() 20 | val receiver = getReceiver() 21 | when (action) { 22 | "LOCK" -> dpm.lockNow() 23 | "DISABLE_CAMERA" -> { 24 | dpm.setCameraDisabled(receiver, !dpm.getCameraDisabled(receiver)) 25 | createShortcuts(this) 26 | } 27 | "MUTE" -> { 28 | dpm.setMasterVolumeMuted(receiver, !dpm.isMasterVolumeMuted(receiver)) 29 | createShortcuts(this) 30 | } 31 | } 32 | } 33 | } finally { 34 | finish() 35 | } 36 | } 37 | } 38 | 39 | fun createShortcuts(context: Context) { 40 | if (!SharedPrefs(context).shortcuts) return 41 | val action = "com.bintianqi.owndroid.action" 42 | val baseIntent = Intent(context, ShortcutsReceiverActivity::class.java) 43 | val dpm = context.getDPM() 44 | val receiver = context.getReceiver() 45 | val cameraDisabled = dpm.getCameraDisabled(receiver) 46 | val muted = dpm.isMasterVolumeMuted(receiver) 47 | val list = listOf( 48 | ShortcutInfoCompat.Builder(context, "LOCK") 49 | .setIcon(IconCompat.createWithResource(context, R.drawable.screen_lock_portrait_fill0)) 50 | .setShortLabel(context.getString(R.string.lock_screen)) 51 | .setIntent(Intent(baseIntent).setAction("$action.LOCK")), 52 | ShortcutInfoCompat.Builder(context, "DISABLE_CAMERA") 53 | .setIcon( 54 | IconCompat.createWithResource( 55 | context, 56 | if (cameraDisabled) R.drawable.photo_camera_fill0 else R.drawable.no_photography_fill0 57 | ) 58 | ) 59 | .setShortLabel(context.getString(if (cameraDisabled) R.string.enable_camera else R.string.disable_cam)) 60 | .setIntent(Intent(baseIntent).setAction("$action.DISABLE_CAMERA")), 61 | ShortcutInfoCompat.Builder(context, "MUTE") 62 | .setIcon( 63 | IconCompat.createWithResource( 64 | context, 65 | if (muted) R.drawable.volume_up_fill0 else R.drawable.volume_off_fill0 66 | ) 67 | ) 68 | .setShortLabel(context.getString(if (muted) R.string.unmute else R.string.mute)) 69 | .setIntent(Intent(baseIntent).setAction("$action.MUTE")) 70 | ) 71 | ShortcutManagerCompat.setDynamicShortcuts(context, list.map { it.build() }) 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.ComponentName 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.pm.PackageInfo 9 | import android.net.Uri 10 | import android.os.Build 11 | import android.os.Bundle 12 | import android.widget.Toast 13 | import androidx.activity.result.contract.ActivityResultContract 14 | import androidx.annotation.RequiresApi 15 | import androidx.annotation.StringRes 16 | import androidx.compose.ui.unit.dp 17 | import androidx.navigation.NavHostController 18 | import androidx.navigation.NavType 19 | import kotlinx.serialization.encodeToString 20 | import kotlinx.serialization.json.Json 21 | import java.io.FileNotFoundException 22 | import java.io.IOException 23 | import java.io.InputStream 24 | import java.security.MessageDigest 25 | import java.text.SimpleDateFormat 26 | import java.time.Instant 27 | import java.time.ZoneId 28 | import java.time.format.DateTimeFormatter 29 | import java.util.Date 30 | import java.util.Locale 31 | import java.util.concurrent.TimeUnit 32 | import kotlin.reflect.typeOf 33 | 34 | var zhCN = true 35 | 36 | fun uriToStream( 37 | context: Context, 38 | uri: Uri, 39 | operation: (stream: InputStream)->Unit 40 | ){ 41 | try { 42 | context.contentResolver.openInputStream(uri)?.use { 43 | operation(it) 44 | } 45 | } 46 | catch(_: FileNotFoundException) { Toast.makeText(context, R.string.file_not_exist, Toast.LENGTH_SHORT).show() } 47 | catch(_: IOException) { Toast.makeText(context, R.string.io_exception, Toast.LENGTH_SHORT).show() } 48 | } 49 | 50 | fun writeClipBoard(context: Context, string: String):Boolean{ 51 | val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 52 | try { 53 | clipboardManager.setPrimaryClip(ClipData.newPlainText("", string)) 54 | } catch(_:Exception) { 55 | return false 56 | } 57 | return true 58 | } 59 | 60 | fun formatFileSize(bytes: Long): String { 61 | val kb = 1024 62 | val mb = kb * 1024 63 | val gb = mb * 1024 64 | return when { 65 | bytes >= gb -> String.format(Locale.US, "%.2f GB", bytes / gb.toDouble()) 66 | bytes >= mb -> String.format(Locale.US, "%.2f MB", bytes / mb.toDouble()) 67 | bytes >= kb -> String.format(Locale.US, "%.2f KB", bytes / kb.toDouble()) 68 | else -> "$bytes bytes" 69 | } 70 | } 71 | 72 | val Boolean.yesOrNo 73 | @StringRes get() = if(this) R.string.yes else R.string.no 74 | 75 | @RequiresApi(26) 76 | fun parseTimestamp(timestamp: Long): String { 77 | val instant = Instant.ofEpochMilli(timestamp) 78 | val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()) 79 | return formatter.format(instant) 80 | } 81 | 82 | fun parseDate(date: Date): String = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(date) 83 | 84 | val Long.humanReadableDate: String 85 | get() = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(Date(this)) 86 | 87 | fun formatDate(pattern: String, value: Long): String 88 | = SimpleDateFormat(pattern, Locale.getDefault()).format(Date(value)) 89 | 90 | fun Context.showOperationResultToast(success: Boolean) { 91 | Toast.makeText(this, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show() 92 | } 93 | 94 | const val APK_MIME = "application/vnd.android.package-archive" 95 | 96 | inline fun serializableNavTypePair() = 97 | typeOf() to object : NavType(false) { 98 | override fun get(bundle: Bundle, key: String): T? = 99 | bundle.getString(key)?.let { parseValue(it) } 100 | override fun put(bundle: Bundle, key: String, value: T) = 101 | bundle.putString(key, serializeAsValue(value)) 102 | override fun parseValue(value: String): T = 103 | Json.decodeFromString(value) 104 | override fun serializeAsValue(value: T): String = 105 | Json.encodeToString(value) 106 | } 107 | 108 | class ChoosePackageContract: ActivityResultContract() { 109 | override fun createIntent(context: Context, input: Nothing?): Intent = 110 | Intent(context, PackageChooserActivity::class.java) 111 | override fun parseResult(resultCode: Int, intent: Intent?): String? = 112 | intent?.getStringExtra("package") 113 | } 114 | 115 | fun exportLogs(context: Context, uri: Uri) { 116 | context.contentResolver.openOutputStream(uri)?.use { output -> 117 | val proc = Runtime.getRuntime().exec("logcat -d") 118 | proc.inputStream.copyTo(output) 119 | if(Build.VERSION.SDK_INT >= 26) proc.waitFor(2L, TimeUnit.SECONDS) 120 | else proc.waitFor() 121 | context.showOperationResultToast(proc.exitValue() == 0) 122 | } 123 | } 124 | 125 | fun NavHostController.navigate(route: T, args: Bundle) { 126 | navigate(graph.findNode(route)!!.id, args) 127 | } 128 | 129 | val HorizontalPadding = 16.dp 130 | 131 | @OptIn(ExperimentalStdlibApi::class) 132 | fun String.hash(): String { 133 | val md = MessageDigest.getInstance("SHA-256") 134 | return md.digest(this.encodeToByteArray()).toHexString() 135 | } 136 | 137 | val MyAdminComponent = ComponentName.unflattenFromString("com.bintianqi.owndroid/.Receiver")!! 138 | 139 | 140 | @OptIn(ExperimentalStdlibApi::class) 141 | fun getPackageSignature(info: PackageInfo): String? { 142 | val signatures = if (Build.VERSION.SDK_INT >= 28) info.signingInfo?.apkContentsSigners else info.signatures 143 | return signatures?.firstOrNull()?.toByteArray() 144 | ?.let { MessageDigest.getInstance("SHA-256").digest(it) }?.toHexString() 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/ui/Animations.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid.ui 2 | 3 | import androidx.compose.animation.* 4 | import androidx.compose.animation.core.* 5 | import androidx.compose.ui.unit.IntOffset 6 | import androidx.navigation.NavBackStackEntry 7 | 8 | object Animations { 9 | private const val INITIAL_OFFSET_VALUE = 96 10 | private const val TARGET_OFFSET_VALUE = 96 11 | 12 | private val bezier = CubicBezierEasing(0.20f, 0.85f, 0.0f, 1f) 13 | 14 | private val tween: FiniteAnimationSpec = tween(durationMillis = 550, easing = bezier, delayMillis = 50) 15 | 16 | val navHostEnterTransition: AnimatedContentTransitionScope.() -> EnterTransition = { 17 | fadeIn(tween(100, easing = LinearEasing)) + 18 | slideIntoContainer( 19 | animationSpec = tween, 20 | towards = AnimatedContentTransitionScope.SlideDirection.End, 21 | initialOffset = { INITIAL_OFFSET_VALUE } 22 | ) 23 | } 24 | 25 | val navHostExitTransition: AnimatedContentTransitionScope.() -> ExitTransition = { 26 | fadeOut(tween(100, easing = LinearEasing)) + 27 | slideOutOfContainer( 28 | animationSpec = tween, 29 | towards = AnimatedContentTransitionScope.SlideDirection.Start, 30 | targetOffset = { -TARGET_OFFSET_VALUE } 31 | ) 32 | } 33 | 34 | val navHostPopEnterTransition: AnimatedContentTransitionScope.() -> EnterTransition = { 35 | fadeIn(tween(100, easing = LinearEasing)) + 36 | slideIntoContainer( 37 | animationSpec = tween, 38 | towards = AnimatedContentTransitionScope.SlideDirection.End, 39 | initialOffset = { -INITIAL_OFFSET_VALUE } 40 | ) 41 | } 42 | 43 | val navHostPopExitTransition: AnimatedContentTransitionScope.() -> ExitTransition = { 44 | fadeOut(tween(100, easing = LinearEasing)) + 45 | slideOutOfContainer( 46 | animationSpec = tween, 47 | towards = AnimatedContentTransitionScope.SlideDirection.Start, 48 | targetOffset = { TARGET_OFFSET_VALUE } 49 | ) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val primaryLight = Color(0xFF4C662B) 6 | val onPrimaryLight = Color(0xFFFFFFFF) 7 | val primaryContainerLight = Color(0xFFCDEDA3) 8 | val onPrimaryContainerLight = Color(0xFF102000) 9 | val secondaryLight = Color(0xFF586249) 10 | val onSecondaryLight = Color(0xFFFFFFFF) 11 | val secondaryContainerLight = Color(0xFFDCE7C8) 12 | val onSecondaryContainerLight = Color(0xFF151E0B) 13 | val tertiaryLight = Color(0xFF386663) 14 | val onTertiaryLight = Color(0xFFFFFFFF) 15 | val tertiaryContainerLight = Color(0xFFBCECE7) 16 | val onTertiaryContainerLight = Color(0xFF00201E) 17 | val errorLight = Color(0xFFBA1A1A) 18 | val onErrorLight = Color(0xFFFFFFFF) 19 | val errorContainerLight = Color(0xFFFFDAD6) 20 | val onErrorContainerLight = Color(0xFF410002) 21 | val backgroundLight = Color(0xFFF9FAEF) 22 | val onBackgroundLight = Color(0xFF1A1C16) 23 | val surfaceLight = Color(0xFFF9FAEF) 24 | val onSurfaceLight = Color(0xFF1A1C16) 25 | val surfaceVariantLight = Color(0xFFE1E4D5) 26 | val onSurfaceVariantLight = Color(0xFF44483D) 27 | val outlineLight = Color(0xFF75796C) 28 | val outlineVariantLight = Color(0xFFC5C8BA) 29 | val scrimLight = Color(0xFF000000) 30 | val inverseSurfaceLight = Color(0xFF2F312A) 31 | val inverseOnSurfaceLight = Color(0xFFF1F2E6) 32 | val inversePrimaryLight = Color(0xFFB1D18A) 33 | val surfaceDimLight = Color(0xFFDADBD0) 34 | val surfaceBrightLight = Color(0xFFF9FAEF) 35 | val surfaceContainerLowestLight = Color(0xFFFFFFFF) 36 | val surfaceContainerLowLight = Color(0xFFF3F4E9) 37 | val surfaceContainerLight = Color(0xFFEEEFE3) 38 | val surfaceContainerHighLight = Color(0xFFE8E9DE) 39 | val surfaceContainerHighestLight = Color(0xFFE2E3D8) 40 | 41 | 42 | val primaryDark = Color(0xFFB1D18A) 43 | val onPrimaryDark = Color(0xFF1F3701) 44 | val primaryContainerDark = Color(0xFF354E16) 45 | val onPrimaryContainerDark = Color(0xFFCDEDA3) 46 | val secondaryDark = Color(0xFFBFCBAD) 47 | val onSecondaryDark = Color(0xFF2A331E) 48 | val secondaryContainerDark = Color(0xFF404A33) 49 | val onSecondaryContainerDark = Color(0xFFDCE7C8) 50 | val tertiaryDark = Color(0xFFA0D0CB) 51 | val onTertiaryDark = Color(0xFF003735) 52 | val tertiaryContainerDark = Color(0xFF1F4E4B) 53 | val onTertiaryContainerDark = Color(0xFFBCECE7) 54 | val errorDark = Color(0xFFFFB4AB) 55 | val onErrorDark = Color(0xFF690005) 56 | val errorContainerDark = Color(0xFF93000A) 57 | val onErrorContainerDark = Color(0xFFFFDAD6) 58 | val backgroundDark = Color(0xFF12140E) 59 | val onBackgroundDark = Color(0xFFE2E3D8) 60 | val surfaceDark = Color(0xFF12140E) 61 | val onSurfaceDark = Color(0xFFE2E3D8) 62 | val surfaceVariantDark = Color(0xFF44483D) 63 | val onSurfaceVariantDark = Color(0xFFC5C8BA) 64 | val outlineDark = Color(0xFF8F9285) 65 | val outlineVariantDark = Color(0xFF44483D) 66 | val scrimDark = Color(0xFF000000) 67 | val inverseSurfaceDark = Color(0xFFE2E3D8) 68 | val inverseOnSurfaceDark = Color(0xFF2F312A) 69 | val inversePrimaryDark = Color(0xFF4C662B) 70 | val surfaceDimDark = Color(0xFF12140E) 71 | val surfaceBrightDark = Color(0xFF383A32) 72 | val surfaceContainerLowestDark = Color(0xFF0C0F09) 73 | val surfaceContainerLowDark = Color(0xFF1A1C16) 74 | val surfaceContainerDark = Color(0xFF1E201A) 75 | val surfaceContainerHighDark = Color(0xFF282B24) 76 | val surfaceContainerHighestDark = Color(0xFF33362E) 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/bintianqi/owndroid/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.bintianqi.owndroid.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build.VERSION 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.SideEffect 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.platform.LocalContext 15 | import androidx.compose.ui.platform.LocalView 16 | import androidx.core.view.WindowCompat 17 | import com.bintianqi.owndroid.ThemeSettings 18 | 19 | private val lightScheme = lightColorScheme( 20 | primary = primaryLight, 21 | onPrimary = onPrimaryLight, 22 | primaryContainer = primaryContainerLight, 23 | onPrimaryContainer = onPrimaryContainerLight, 24 | secondary = secondaryLight, 25 | onSecondary = onSecondaryLight, 26 | secondaryContainer = secondaryContainerLight, 27 | onSecondaryContainer = onSecondaryContainerLight, 28 | tertiary = tertiaryLight, 29 | onTertiary = onTertiaryLight, 30 | tertiaryContainer = tertiaryContainerLight, 31 | onTertiaryContainer = onTertiaryContainerLight, 32 | error = errorLight, 33 | onError = onErrorLight, 34 | errorContainer = errorContainerLight, 35 | onErrorContainer = onErrorContainerLight, 36 | background = backgroundLight, 37 | onBackground = onBackgroundLight, 38 | surface = surfaceLight, 39 | onSurface = onSurfaceLight, 40 | surfaceVariant = surfaceVariantLight, 41 | onSurfaceVariant = onSurfaceVariantLight, 42 | outline = outlineLight, 43 | outlineVariant = outlineVariantLight, 44 | scrim = scrimLight, 45 | inverseSurface = inverseSurfaceLight, 46 | inverseOnSurface = inverseOnSurfaceLight, 47 | inversePrimary = inversePrimaryLight, 48 | surfaceDim = surfaceDimLight, 49 | surfaceBright = surfaceBrightLight, 50 | surfaceContainerLowest = surfaceContainerLowestLight, 51 | surfaceContainerLow = surfaceContainerLowLight, 52 | surfaceContainer = surfaceContainerLight, 53 | surfaceContainerHigh = surfaceContainerHighLight, 54 | surfaceContainerHighest = surfaceContainerHighestLight, 55 | ) 56 | 57 | private val darkScheme = darkColorScheme( 58 | primary = primaryDark, 59 | onPrimary = onPrimaryDark, 60 | primaryContainer = primaryContainerDark, 61 | onPrimaryContainer = onPrimaryContainerDark, 62 | secondary = secondaryDark, 63 | onSecondary = onSecondaryDark, 64 | secondaryContainer = secondaryContainerDark, 65 | onSecondaryContainer = onSecondaryContainerDark, 66 | tertiary = tertiaryDark, 67 | onTertiary = onTertiaryDark, 68 | tertiaryContainer = tertiaryContainerDark, 69 | onTertiaryContainer = onTertiaryContainerDark, 70 | error = errorDark, 71 | onError = onErrorDark, 72 | errorContainer = errorContainerDark, 73 | onErrorContainer = onErrorContainerDark, 74 | background = backgroundDark, 75 | onBackground = onBackgroundDark, 76 | surface = surfaceDark, 77 | onSurface = onSurfaceDark, 78 | surfaceVariant = surfaceVariantDark, 79 | onSurfaceVariant = onSurfaceVariantDark, 80 | outline = outlineDark, 81 | outlineVariant = outlineVariantDark, 82 | scrim = scrimDark, 83 | inverseSurface = inverseSurfaceDark, 84 | inverseOnSurface = inverseOnSurfaceDark, 85 | inversePrimary = inversePrimaryDark, 86 | surfaceDim = surfaceDimDark, 87 | surfaceBright = surfaceBrightDark, 88 | surfaceContainerLowest = surfaceContainerLowestDark, 89 | surfaceContainerLow = surfaceContainerLowDark, 90 | surfaceContainer = surfaceContainerDark, 91 | surfaceContainerHigh = surfaceContainerHighDark, 92 | surfaceContainerHighest = surfaceContainerHighestDark, 93 | ) 94 | 95 | @Composable 96 | fun OwnDroidTheme( 97 | theme: ThemeSettings, 98 | content: @Composable () -> Unit 99 | ) { 100 | val darkTheme = theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme()) 101 | val context = LocalContext.current 102 | val colorScheme = when { 103 | theme.materialYou && VERSION.SDK_INT >= 31 -> { 104 | if(darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 105 | } 106 | darkTheme -> darkScheme 107 | else -> lightScheme 108 | }.let { 109 | if(darkTheme && theme.blackTheme) it.copy(background = Color.Black, surface = Color.Black) else it 110 | } 111 | val view = LocalView.current 112 | SideEffect { 113 | val window = (context as Activity).window 114 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme 115 | } 116 | MaterialTheme( 117 | colorScheme = colorScheme, 118 | content = content 119 | ) 120 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/account_circle_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/adb_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/admin_panel_settings_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/airplanemode_active_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/android_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/apps_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/backup_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/block_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bluetooth_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bluetooth_searching_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/brightness_5_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bug_report_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/calendar_month_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/call_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/call_log_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cameraswitch_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/casino_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cell_tower_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/chat_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/check_circle_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/check_circle_fill1.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/check_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/close_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/code_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/contacts_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/content_copy_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/content_paste_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/corporate_fare_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/delete_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/delete_forever_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/description_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/device_reset_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/devices_other_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dhizuku_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dns_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/do_not_touch_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/enable_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/filter_alt_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fingerprint_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fingerprint_off_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/folder_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/format_paint_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/globe_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/history_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/id_card_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/image_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/info_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/install_mobile_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/key_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/key_vertical_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/keyboard_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/language_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/license_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/location_on_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/lock_clock_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/lock_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/lock_reset_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/logout_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/manage_accounts_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/memory_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mic_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mobile_phone_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/money_off_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mop_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/more_horiz_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/movie_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/music_note_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/network_cell_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/nfc_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/no_encryption_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/no_photography_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/notifications_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/open_in_new.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/password_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/perm_device_information_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/person_add_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/person_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/person_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/person_remove_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/phone_forwarded_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/photo_camera_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/print_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/query_stats_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/refresh_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/reset_wrench_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/restart_alt_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/router_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/save_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/schedule_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/screen_lock_portrait_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/screenshot_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sd_card_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/search_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/security_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sensors_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/settings_accessibility_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/settings_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/share_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shield_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/signal_cellular_alt_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sim_card_download_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sms_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/stadia_controller_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/swap_horiz_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sync_alt_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/system_update_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tune_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/usb_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/visibility_off_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/volume_off_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/volume_up_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vpn_key_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/wallpaper_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/warning_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/web_asset.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/widgets_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/wifi_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/wifi_password_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/wifi_tethering_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/work_fill0.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/resources.properties: -------------------------------------------------------------------------------- 1 | unqualifiedResLocale=en-US -------------------------------------------------------------------------------- /app/src/main/res/values-night-v31/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #191919 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/xml/device_admin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/testkey.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/app/testkey.jks -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | alias(libs.plugins.android.application) apply false 4 | alias(libs.plugins.kotlin.android) apply false 5 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## For more details on how to configure your build environment visit 2 | # http://www.gradle.org/docs/current/userguide/build_environment.html 3 | 4 | android.nonTransitiveRClass=true 5 | android.useAndroidX=true 6 | kotlin.code.style=official 7 | org.gradle.parallel=true 8 | org.gradle.caching=true 9 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx1536M" 10 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.10.1" 3 | kotlin = "2.1.20" 4 | 5 | navigation-compose = "2.9.0" 6 | composeBom = "2025.05.01" 7 | accompanist-drawablepainter = "0.35.0-alpha" 8 | accompanist-permissions = "0.37.0" 9 | shizuku = "13.1.5" 10 | fragment = "1.8.7" 11 | dhizuku = "2.5.3" 12 | dhizuku-server = "0.0.5" 13 | hiddenApiBypass = "4.3" 14 | libsu = "6.0.0" 15 | serialization = "1.7.3" 16 | 17 | [libraries] 18 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } 19 | androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } 20 | androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } 21 | androidx-activity-compose = { module = "androidx.activity:activity-compose" } 22 | androidx-material3 = { module = "androidx.compose.material3:material3" } 23 | androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" } 24 | androidx-fragment = { group = "androidx.fragment", name = "fragment", version.ref = "fragment" } 25 | 26 | accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanist-drawablepainter" } 27 | accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist-permissions" } 28 | 29 | shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku" } 30 | shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" } 31 | dhizuku-api = { module = "io.github.iamr0s:Dhizuku-API", version.ref = "dhizuku" } 32 | dhizuku-server-api = { group = "io.github.iamr0s", name = "Dhizuku-SERVER_API", version.ref = "dhizuku-server" } 33 | hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" } 34 | libsu = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } 35 | 36 | serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } 37 | 38 | [plugins] 39 | android-application = { id = "com.android.application", version.ref = "agp" } 40 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 41 | compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 42 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BinTianqi/OwnDroid/ba2e4f3a388f0b32efab0dbe32ee9033a7462007/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jan 12 20:22:20 CST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://downloads.gradle.org/distributions/gradle-8.14.1-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven("https://jitpack.io") 14 | } 15 | } 16 | 17 | rootProject.name = "OwnDroid" 18 | include(":app") 19 | --------------------------------------------------------------------------------