├── .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 |
--------------------------------------------------------------------------------