Showing preview only (923K chars total). Download the full file or copy to clipboard to get everything.
Repository: RikkaApps/Shizuku
Branch: master
Commit: b844bc491f17
Files: 287
Total size: 841.3 KB
Directory structure:
gitextract_t0twfram/
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ └── config.yml
│ └── workflows/
│ └── app.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── build.gradle
├── common/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── moe/
│ └── shizuku/
│ └── common/
│ └── util/
│ ├── BuildUtils.java
│ └── OsUtils.java
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── manager/
│ ├── .gitignore
│ ├── aapt2-resources.cfg
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── .gitignore
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ └── rish
│ ├── java/
│ │ └── moe/
│ │ └── shizuku/
│ │ └── manager/
│ │ ├── AppConstants.java
│ │ ├── Helps.java
│ │ ├── MainActivity.java
│ │ ├── Manifest.java
│ │ ├── ShizukuApplication.kt
│ │ ├── ShizukuManagerProvider.kt
│ │ ├── ShizukuSettings.java
│ │ ├── adb/
│ │ │ ├── AdbClient.kt
│ │ │ ├── AdbException.kt
│ │ │ ├── AdbKey.kt
│ │ │ ├── AdbMdns.kt
│ │ │ ├── AdbMessage.kt
│ │ │ ├── AdbPairingClient.kt
│ │ │ ├── AdbPairingService.kt
│ │ │ ├── AdbPairingTutorialActivity.kt
│ │ │ └── AdbProtocol.kt
│ │ ├── app/
│ │ │ ├── AppActivity.kt
│ │ │ ├── AppBarActivity.kt
│ │ │ └── ThemeHelper.java
│ │ ├── authorization/
│ │ │ ├── AuthorizationManager.kt
│ │ │ └── RequestPermissionActivity.kt
│ │ ├── home/
│ │ │ ├── AdbDialogFragment.kt
│ │ │ ├── AdbPairDialogFragment.kt
│ │ │ ├── AdbPermissionLimitedViewHolder.kt
│ │ │ ├── HomeActivity.kt
│ │ │ ├── HomeAdapter.kt
│ │ │ ├── HomeViewModel.kt
│ │ │ ├── LearnMoreViewHolder.kt
│ │ │ ├── ManageAppsViewHolder.kt
│ │ │ ├── ServerStatusViewHolder.kt
│ │ │ ├── StartAdbViewHolder.kt
│ │ │ ├── StartRootViewHolder.kt
│ │ │ ├── StartWirelessAdbViewHolder.kt
│ │ │ ├── TerminalViewHolder.kt
│ │ │ └── WadbNotEnabledDialogFragment.kt
│ │ ├── ktx/
│ │ │ ├── Context.kt
│ │ │ ├── Log.kt
│ │ │ ├── PackageManager.kt
│ │ │ ├── RecyclerView.kt
│ │ │ └── String.kt
│ │ ├── legacy/
│ │ │ ├── LegacyIsNotSupportedActivity.kt
│ │ │ └── ShellRequestHandlerActivity.kt
│ │ ├── management/
│ │ │ ├── AppViewHolder.kt
│ │ │ ├── ApplicationManagementActivity.kt
│ │ │ ├── AppsAdapter.java
│ │ │ ├── AppsViewModel.kt
│ │ │ └── EmptyViewHolder.kt
│ │ ├── model/
│ │ │ └── ServiceStatus.kt
│ │ ├── receiver/
│ │ │ ├── BootCompleteReceiver.kt
│ │ │ └── ShizukuReceiver.kt
│ │ ├── settings/
│ │ │ ├── IntegerSimpleMenuPreference.java
│ │ │ ├── SettingsActivity.kt
│ │ │ └── SettingsFragment.kt
│ │ ├── shell/
│ │ │ ├── Shell.java
│ │ │ ├── ShellBinderRequestHandler.kt
│ │ │ └── ShellTutorialActivity.kt
│ │ ├── starter/
│ │ │ ├── Starter.kt
│ │ │ └── StarterActivity.kt
│ │ ├── utils/
│ │ │ ├── AppIconCache.kt
│ │ │ ├── CustomTabsHelper.java
│ │ │ ├── EmptySharedPreferencesImpl.java
│ │ │ ├── EnvironmentUtils.kt
│ │ │ ├── Logger.java
│ │ │ ├── MultiLocaleEntity.java
│ │ │ ├── ShizukuSystemApis.kt
│ │ │ ├── UserHandleCompat.java
│ │ │ └── UserInfoCompat.java
│ │ └── widget/
│ │ ├── CheckedImageView.java
│ │ └── VerticalPaddingDecoration.java
│ ├── jni/
│ │ ├── CMakeLists.txt
│ │ ├── adb_pairing.cpp
│ │ ├── adb_pairing.h
│ │ ├── android.cpp
│ │ ├── android.h
│ │ ├── cgroup.cpp
│ │ ├── cgroup.h
│ │ ├── helper.cpp
│ │ ├── logging.h
│ │ ├── misc.cpp
│ │ ├── misc.h
│ │ ├── selinux.cpp
│ │ ├── selinux.h
│ │ └── starter.cpp
│ └── res/
│ ├── animator/
│ │ └── alpha_animator.xml
│ ├── color/
│ │ ├── grant_permissions_button_ripple_color_selector.xml
│ │ ├── home_card_background_color.xml
│ │ └── home_card_foreground_color.xml
│ ├── color-night/
│ │ ├── home_card_background_color.xml
│ │ └── home_card_foreground_color.xml
│ ├── drawable/
│ │ ├── card_btn_background.xml
│ │ ├── grant_permissions_buttons_bottom.xml
│ │ ├── grant_permissions_buttons_top.xml
│ │ ├── home_card_foreground.xml
│ │ ├── ic_action_about_24dp.xml
│ │ ├── ic_action_settings_24dp.xml
│ │ ├── ic_adb_24dp.xml
│ │ ├── ic_baseline_link_24.xml
│ │ ├── ic_close_24.xml
│ │ ├── ic_code_24dp.xml
│ │ ├── ic_default_app_icon_background.xml
│ │ ├── ic_help_outline_24dp.xml
│ │ ├── ic_launcher.xml
│ │ ├── ic_learn_more_24dp.xml
│ │ ├── ic_monochrome.xml
│ │ ├── ic_numeric_1_circle_outline_24.xml
│ │ ├── ic_numeric_2_circle_outline_24.xml
│ │ ├── ic_numeric_3_circle_outline_24.xml
│ │ ├── ic_outline_arrow_upward_24.xml
│ │ ├── ic_outline_dark_mode_24.xml
│ │ ├── ic_outline_info_24.xml
│ │ ├── ic_outline_notifications_active_24.xml
│ │ ├── ic_outline_open_in_new_24.xml
│ │ ├── ic_outline_play_arrow_24.xml
│ │ ├── ic_outline_translate_24.xml
│ │ ├── ic_root_24dp.xml
│ │ ├── ic_server_error_24dp.xml
│ │ ├── ic_server_ok_24dp.xml
│ │ ├── ic_server_restart.xml
│ │ ├── ic_server_start_24dp.xml
│ │ ├── ic_settings_outline_24dp.xml
│ │ ├── ic_system_icon.xml
│ │ ├── ic_terminal_24.xml
│ │ ├── ic_wadb_24.xml
│ │ ├── ic_warning_24.xml
│ │ └── shape_circle_icon_background.xml
│ ├── drawable-v24/
│ │ └── ic_default_app_icon_foreground.xml
│ ├── drawable-v26/
│ │ ├── ic_default_app_icon.xml
│ │ └── ic_launcher.xml
│ ├── layout/
│ │ ├── about_dialog.xml
│ │ ├── adb_dialog.xml
│ │ ├── adb_pair_dialog.xml
│ │ ├── adb_pairing_tutorial_activity.xml
│ │ ├── app_list_empty.xml
│ │ ├── app_list_item.xml
│ │ ├── appbar.xml
│ │ ├── appbar_activity.xml
│ │ ├── appbar_fragment_activity.xml
│ │ ├── apps_activity.xml
│ │ ├── confirmation_dialog.xml
│ │ ├── home_activity.xml
│ │ ├── home_extra_step_required.xml
│ │ ├── home_item_container.xml
│ │ ├── home_learn_more.xml
│ │ ├── home_manage_apps_item.xml
│ │ ├── home_server_status.xml
│ │ ├── home_start_adb.xml
│ │ ├── home_start_root.xml
│ │ ├── home_start_wireless_adb.xml
│ │ ├── home_terminal.xml
│ │ ├── preference_recyclerview.xml
│ │ ├── shell_dialog.xml
│ │ ├── starter_activity.xml
│ │ └── terminal_tutorial_activity.xml
│ ├── menu/
│ │ └── main.xml
│ ├── values/
│ │ ├── arrays.xml
│ │ ├── attrs.xml
│ │ ├── bools.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ ├── strings_untranslatable.xml
│ │ ├── styles.xml
│ │ ├── themes.xml
│ │ ├── themes_overlay.xml
│ │ └── values.xml
│ ├── values-ang/
│ │ └── strings.xml
│ ├── values-ar/
│ │ └── strings.xml
│ ├── values-ars/
│ │ └── strings.xml
│ ├── values-az/
│ │ └── strings.xml
│ ├── values-b+es+419/
│ │ └── strings.xml
│ ├── values-bg/
│ │ └── strings.xml
│ ├── values-bn/
│ │ └── strings.xml
│ ├── values-ca/
│ │ └── strings.xml
│ ├── values-ckb/
│ │ └── strings.xml
│ ├── values-cs/
│ │ └── strings.xml
│ ├── values-da/
│ │ └── strings.xml
│ ├── values-de/
│ │ └── strings.xml
│ ├── values-el/
│ │ └── strings.xml
│ ├── values-enm/
│ │ └── strings.xml
│ ├── values-eo/
│ │ └── strings.xml
│ ├── values-es/
│ │ └── strings.xml
│ ├── values-es-rCL/
│ │ └── strings.xml
│ ├── values-et/
│ │ └── strings.xml
│ ├── values-fa/
│ │ └── strings.xml
│ ├── values-fi/
│ │ └── strings.xml
│ ├── values-fil/
│ │ └── strings.xml
│ ├── values-fr/
│ │ └── strings.xml
│ ├── values-he/
│ │ └── strings.xml
│ ├── values-hi/
│ │ └── strings.xml
│ ├── values-hr/
│ │ └── strings.xml
│ ├── values-hu/
│ │ └── strings.xml
│ ├── values-hy/
│ │ └── strings.xml
│ ├── values-id/
│ │ └── strings.xml
│ ├── values-it/
│ │ └── strings.xml
│ ├── values-ja/
│ │ └── strings.xml
│ ├── values-ka/
│ │ └── strings.xml
│ ├── values-kk/
│ │ └── strings.xml
│ ├── values-km/
│ │ └── strings.xml
│ ├── values-kn/
│ │ └── strings.xml
│ ├── values-ko/
│ │ └── strings.xml
│ ├── values-lb/
│ │ └── strings.xml
│ ├── values-lv/
│ │ └── strings.xml
│ ├── values-mk/
│ │ └── strings.xml
│ ├── values-ml/
│ │ └── strings.xml
│ ├── values-ms/
│ │ └── strings.xml
│ ├── values-my/
│ │ └── strings.xml
│ ├── values-night/
│ │ └── styles.xml
│ ├── values-nl/
│ │ └── strings.xml
│ ├── values-or/
│ │ └── strings.xml
│ ├── values-pl/
│ │ └── strings.xml
│ ├── values-pt/
│ │ └── strings.xml
│ ├── values-pt-rBR/
│ │ └── strings.xml
│ ├── values-ro/
│ │ └── strings.xml
│ ├── values-ru/
│ │ └── strings.xml
│ ├── values-sk/
│ │ └── strings.xml
│ ├── values-sl/
│ │ └── strings.xml
│ ├── values-sr/
│ │ └── strings.xml
│ ├── values-sv/
│ │ └── strings.xml
│ ├── values-sw600dp/
│ │ ├── bools.xml
│ │ ├── dimens.xml
│ │ └── values.xml
│ ├── values-ta/
│ │ └── strings.xml
│ ├── values-te/
│ │ └── strings.xml
│ ├── values-th/
│ │ └── strings.xml
│ ├── values-tr/
│ │ └── strings.xml
│ ├── values-ug/
│ │ └── strings.xml
│ ├── values-uk/
│ │ └── strings.xml
│ ├── values-ur/
│ │ └── strings.xml
│ ├── values-v21/
│ │ └── themes_override.xml
│ ├── values-v31/
│ │ └── themes_overlay.xml
│ ├── values-vi/
│ │ └── strings.xml
│ ├── values-zh-rCN/
│ │ └── strings.xml
│ ├── values-zh-rTW/
│ │ └── strings.xml
│ └── xml/
│ ├── backup_descriptor.xml
│ ├── data_extraction_rules.xml
│ └── settings.xml
├── server/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── rikka/
│ └── shizuku/
│ └── server/
│ ├── ApkChangedObservers.kt
│ ├── BinderSender.java
│ ├── ServerConstants.java
│ ├── ShizukuClientManager.java
│ ├── ShizukuConfig.java
│ ├── ShizukuConfigManager.java
│ ├── ShizukuService.java
│ ├── ShizukuUserServiceManager.java
│ ├── api/
│ │ └── IContentProviderUtils.java
│ └── ktx/
│ └── Handler.kt
├── settings.gradle
├── shell/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── rikka/
│ └── shizuku/
│ └── shell/
│ └── ShizukuShellLoader.java
├── signing.gradle
└── starter/
├── .gitignore
├── build.gradle
└── src/
└── main/
├── AndroidManifest.xml
└── java/
└── moe/
└── shizuku/
└── starter/
├── ServiceStarter.java
└── util/
└── IContentProviderCompat.java
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
* text=auto eol=lf
*.bat text eol=crlf
*.jar binary
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Report a bug of Shizuku
body:
- type: checkboxes
id: requirements
attributes:
label: Requirements
options:
- label: Shizuku is downloaded from official channels (GitHub release or Google Play)
- label: Shizuku is not running in a virtual environment or broken ROM (GrapheneOS)
- label: (Root users) No Xposed installed / Xposed is not enabled for Shizuku
- type: input
id: version
attributes:
label: Latest Shizuku version
validations:
required: true
- type: input
id: shizuku_version
attributes:
label: Version in use
validations:
required: true
- type: dropdown
id: mode
attributes:
label: Mode
options:
- adb
- root
- manual
validations:
required: true
- type: input
id: android_version
attributes:
label: Android version
validations:
required: true
- type: input
id: device
attributes:
label: Device
validations:
required: true
- type: textarea
id: reproducer
attributes:
label: What did you do
- type: textarea
id: logs
attributes:
label: What happened
description: You can upload log and screenshot
- type: textarea
id: expected-behaviour
attributes:
label: What do you expect
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: GitHub Community Support
url: https://github.com/RikkaApps/Shizuku/discussions
about: Please ask and answer questions here.
================================================
FILE: .github/workflows/app.yml
================================================
name: App
on:
push:
paths-ignore:
- '.github/ISSUE_TEMPLATE'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Write key
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
run: |
echo KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }} > signing.properties
echo KEYSTORE_ALIAS=${{ secrets.KEYSTORE_ALIAS }} >> signing.properties
echo KEYSTORE_ALIAS_PASSWORD='${{ secrets.KEYSTORE_ALIAS_PASSWORD }}' >> signing.properties
echo KEYSTORE_FILE=../key.jks >> signing.properties
echo ${{ secrets.KEYSTORE }} | base64 --decode > key.jks
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
dependency-graph: generate-and-submit
build-scan-publish: true
build-scan-terms-of-use-url: "https://gradle.com/terms-of-service"
build-scan-terms-of-use-agree: "yes"
- name: Build with Gradle
id: buildWithGradle
run: |
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null || true
echo 'android.sdk.channel=3' >> gradle.properties
echo 'android.native.buildOutput=verbose' >> gradle.properties
echo 'org.gradle.caching=true' >> gradle.properties
echo 'org.gradle.parallel=true' >> gradle.properties
./gradlew :manager:assemble
releaseName=`ls manager/build/outputs/apk/release/shizuku*-v*-release.apk | awk -F '(/|-release.apk)' '{print $6}'` && echo "releaseName=$releaseName" >> $GITHUB_OUTPUT
- name: Upload release
if: success()
uses: actions/upload-artifact@v4
with:
name: ${{ steps.buildWithGradle.outputs.releaseName }}
path: "manager/build/outputs"
compression-level: 9
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild
/.idea
/manager/signing.properties
/out
/signing.properties
================================================
FILE: .gitmodules
================================================
[submodule "api"]
path = api
url = git@github.com:RikkaApps/Shizuku-API.git
branch = master
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# Shizuku
## Background
When developing apps that requires root, the most common method is to run some commands in the su shell. For example, there is an app that uses the `pm enable/disable` command to enable/disable components.
This method has very big disadvantages:
1. **Extremely slow** (Multiple process creation)
2. Needs to process texts (**Super unreliable**)
3. The possibility is limited to available commands
4. Even if ADB has sufficient permissions, the app requires root privileges to run
Shizuku uses a completely different way. See detailed description below.
## User guide & Download
<https://shizuku.rikka.app/>
## How does Shizuku work?
First, we need to talk about how app use system APIs. For example, if the app wants to get installed apps, we all know we should use `PackageManager#getInstalledPackages()`. This is actually an interprocess communication (IPC) process of the app process and system server process, just the Android framework did the inner works for us.
Android uses `binder` to do this type of IPC. `Binder` allows the server-side to learn the uid and pid of the client-side, so that the system server can check if the app has the permission to do the operation.
Usually, if there is a "manager" (e.g., `PackageManager`) for apps to use, there should be a "service" (e.g., `PackageManagerService`) in the system server process. We can simply think if the app holds the `binder` of the "service", it can communicate with the "service". The app process will receive binders of system services on start.
Shizuku guides users to run a process, Shizuku server, with root or ADB first. When the app starts, the `binder` to Shizuku server will also be sent to the app.
The most important feature Shizuku provides is something like be a middle man to receive requests from the app, sent them to the system server, and send back the results. You can see the `transactRemote` method in `rikka.shizuku.server.ShizukuService` class, and `moe.shizuku.api.ShizukuBinderWrapper` class for the detail.
So, we reached our goal, to use system APIs with higher permission. And to the app, it is almost identical to the use of system APIs directly.
## Developer guide
### API & sample
https://github.com/RikkaApps/Shizuku-API
### Migrating from pre-v11
> Existing applications still works, of course.
https://github.com/RikkaApps/Shizuku-API#migration-guide-for-existing-applications-use-shizuku-pre-v11
### Attention
1. ADB permissions are limited
ADB has limited permissions and different on various system versions. You can see permissions granted to ADB [here](https://github.com/aosp-mirror/platform_frameworks_base/blob/master/packages/Shell/AndroidManifest.xml).
Before calling the API, you can use `ShizukuService#getUid` to check if Shizuku is running user ADB, or use `ShizukuService#checkPermission` to check if the server has sufficient permissions.
2. Hidden API limitation from Android 9
As of Android 9, the usage of the hidden APIs is limited for normal apps. Please use other methods (such as <https://github.com/LSPosed/AndroidHiddenApiBypass>).
3. Android 8.0 & ADB
At present, the way Shizuku service gets the app process is to combine `IActivityManager#registerProcessObserver` and `IActivityManager#registerUidObserver` (26+) to ensure that the app process will be sent when the app starts. However, on API 26, ADB lacks permissions to use `registerUidObserver`, so if you need to use Shizuku in a process that might not be started by an Activity, it is recommended to trigger the send binder by starting a transparent activity.
4. Direct use of `transactRemote` requires attention
* The API may be different under different Android versions, please be sure to check it carefully. Also, the `android.app.IActivityManager` has the aidl form in API 26 and later, and `android.app.IActivityManager$Stub` exists only on API 26.
* `SystemServiceHelper.getTransactionCode` may not get the correct transaction code, such as `android.content.pm.IPackageManager$Stub.TRANSACTION_getInstalledPackages` does not exist on API 25 and there is `android.content.pm.IPackageManager$Stub.TRANSACTION_getInstalledPackages_47` (this situation has been dealt with, but it is not excluded that there may be other circumstances). This problem is not encountered with the `ShizukuBinderWrapper` method.
## Developing Shizuku itself
### Build
- Clone with `git clone --recurse-submodules`
- Run gradle task `:manager:assembleDebug` or `:manager:assembleRelease`
The `:manager:assembleDebug` task generates a debuggable server. You can attach a debugger to `shizuku_server` to debug the server. Be aware that, in Android Studio, "Run/Debug configurations" - "Always install with package manager" should be checked, so that the server will use the latest code.
## License
All code files in this project are licensed under Apache 2.0
Under Apache 2.0 section 6, specifically:
* You are **FORBIDDEN** to use `manager/src/main/res/mipmap*/ic_launcher*.png` image files, unless for displaying Shizuku itself.
* You are **FORBIDDEN** to use `Shizuku` as app name or use `moe.shizuku.privileged.api` as application id or declare `moe.shizuku.manager.permission.*` permission.
================================================
FILE: build.gradle
================================================
plugins {
id("idea")
}
idea.module {
excludeDirs += file('out')
}
subprojects {
plugins.withId("com.android.base") {
android {
compileSdk = 36
buildToolsVersion = "36.0.0"
ndkVersion = "29.0.13113456"
defaultConfig {
minSdk = 24
targetSdk = 36
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
}
}
}
apply from: 'api/manifest.gradle'
def gitCommitId = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()
def gitCommitCount = Integer.parseInt('git rev-list --count HEAD'.execute([], project.rootDir).text.trim())
def baseVersionName = "${api_version_major}.6.0"
ext {
versionCode = gitCommitCount
versionName = "${baseVersionName}.r${gitCommitCount}.${gitCommitId}"
}
================================================
FILE: common/.gitignore
================================================
/build
================================================
FILE: common/build.gradle
================================================
plugins {
id 'com.android.library'
}
android {
namespace 'rikka.shizuku.common'
buildFeatures {
buildConfig = false
}
}
dependencies {
compileOnly libs.hidden.stub
}
================================================
FILE: common/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest />
================================================
FILE: common/src/main/java/moe/shizuku/common/util/BuildUtils.java
================================================
package moe.shizuku.common.util;
import android.os.Build;
/**
* TODO: Replace it with {@link rikka.core.util.BuildUtils}.
*/
public class BuildUtils {
private static final int SDK = Build.VERSION.SDK_INT;
private static final int PREVIEW_SDK = SDK >= 23 ? Build.VERSION.PREVIEW_SDK_INT : 0;
public static boolean atLeast31() {
return SDK >= 31 || SDK == 30 && PREVIEW_SDK > 0;
}
public static boolean atLeast30() {
return SDK >= 30;
}
public static boolean atLeast29() {
return SDK >= 29;
}
public static boolean atLeast28() {
return SDK >= 28;
}
public static boolean atLeast26() {
return SDK >= 26;
}
public static boolean atLeast24() {
return SDK >= 24;
}
public static boolean atLeast23() {
return SDK >= 23;
}
}
================================================
FILE: common/src/main/java/moe/shizuku/common/util/OsUtils.java
================================================
package moe.shizuku.common.util;
import android.os.SELinux;
public class OsUtils {
private static final int UID = android.system.Os.getuid();
private static final int PID = android.system.Os.getpid();
private static final String SELINUX_CONTEXT;
static {
String context;
try {
context = SELinux.getContext();
} catch (Throwable tr) {
context = null;
}
SELINUX_CONTEXT = context;
}
public static int getUid() {
return UID;
}
public static int getPid() {
return PID;
}
public static String getSELinuxContext() {
return SELINUX_CONTEXT;
}
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: gradle.properties
================================================
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
================================================
FILE: gradlew
================================================
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: manager/.gitignore
================================================
/build
/signing.properties
/.cxx
================================================
FILE: manager/aapt2-resources.cfg
================================================
color/material_orange_50#no_obfuscate
color/material_indigo_50#no_obfuscate
color/material_blue_grey_50#no_obfuscate
color/material_teal_50#no_obfuscate
color/material_cyan_50#no_obfuscate
color/material_blue_50#no_obfuscate
color/material_blue_grey_50#no_obfuscate
color/material_deep_purple_50#no_obfuscate
color/material_red_50#no_obfuscate
color/material_orange_100#no_obfuscate
color/material_indigo_100#no_obfuscate
color/material_blue_grey_100#no_obfuscate
color/material_teal_100#no_obfuscate
color/material_cyan_100#no_obfuscate
color/material_blue_100#no_obfuscate
color/material_blue_grey_100#no_obfuscate
color/material_deep_purple_100#no_obfuscate
color/material_red_100#no_obfuscate
color/material_orange_600#no_obfuscate
color/material_indigo_600#no_obfuscate
color/material_blue_grey_600#no_obfuscate
color/material_teal_600#no_obfuscate
color/material_cyan_600#no_obfuscate
color/material_blue_600#no_obfuscate
color/material_blue_grey_600#no_obfuscate
color/material_deep_purple_600#no_obfuscate
color/material_red_600#no_obfuscate
================================================
FILE: manager/build.gradle
================================================
import java.nio.file.Paths
import com.android.build.gradle.internal.tasks.CompileArtProfileTask
plugins {
id('com.android.application')
id('org.jetbrains.kotlin.android')
id('dev.rikka.tools.refine')
id('dev.rikka.tools.autoresconfig')
id('dev.rikka.tools.materialthemebuilder')
}
android {
namespace 'moe.shizuku.manager'
defaultConfig {
applicationId "moe.shizuku.privileged.api"
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=none'
}
}
}
buildFeatures {
buildConfig true
viewBinding true
prefab true
}
signingConfigs {
sign
}
buildTypes {
debug {
signingConfig signingConfigs.sign
}
release {
signingConfig signingConfigs.sign
minifyEnabled true
shrinkResources true
vcsInfo.include false
proguardFiles 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path 'src/main/jni/CMakeLists.txt'
version = "3.31.0+"
}
}
kotlinOptions {
jvmTarget = "21"
}
packagingOptions {
jniLibs {
useLegacyPackaging true
}
resources {
excludes += ['**']
}
}
dependenciesInfo {
includeInApk false
}
lint {
checkReleaseBuilds false
}
}
autoResConfig {
generatedClassFullName = "rikka.shizuku.manager.ShizukuLocales"
generateRes = false
generatedArrayFirstItem = "SYSTEM"
generateLocaleConfig = true
}
materialThemeBuilder {
themes {
shizuku {
primaryColor = "#3F51B5"
lightThemeFormat = "Theme.Material3.Light.%s"
lightThemeParent = "Theme.Material3.Light.Rikka"
darkThemeFormat = "Theme.Material3.Dark.%s"
darkThemeParent = "Theme.Material3.Dark.Rikka"
}
}
generatePalette = true
generateTextColors = true
}
def collapseReleaseResourceNames = task('collapseReleaseResourceNames').doLast {
def aapt2 = Paths.get(project.android.sdkDirectory.path, 'build-tools', project.android.buildToolsVersion, 'aapt2')
def zip = Paths.get(project.buildDir.path, 'intermediates',
'optimized_processed_res', 'release', 'optimizeReleaseResources', 'resources-release-optimize.ap_')
def optimized = new File("${zip}.opt")
def cmd = exec {
commandLine aapt2, 'optimize', '--collapse-resource-names',
'--resources-config-path', 'aapt2-resources.cfg',
'-o', optimized, zip
ignoreExitValue false
}
if (cmd.exitValue == 0) {
delete(zip)
optimized.renameTo("$zip")
}
}
afterEvaluate {
tasks.getByName('optimizeReleaseResources').finalizedBy(collapseReleaseResourceNames)
tasks.getByName('preReleaseBuild').dependsOn(':shell:assembleRelease')
tasks.getByName('preDebugBuild').dependsOn(':shell:assembleDebug')
}
android.applicationVariants.configureEach { variant ->
variant.outputs.configureEach {
outputFileName = "shizuku-v${variant.versionName}-${variant.name}.apk"
variant.assembleProvider.get().doLast {
def outDir = new File(rootDir, "out")
def mappingDir = new File(outDir, "mapping").absolutePath
def apkDir = new File(outDir, "apk").absolutePath
if (variant.getBuildType().isMinifyEnabled()) {
copy {
from variant.mappingFileProvider.get()
into mappingDir
rename { String fileName -> "mapping-${variant.versionName}.txt" }
}
copy {
from outputFile
into apkDir
}
}
}
}
}
tasks.withType(CompileArtProfileTask.class).configureEach {
enabled = false
}
configurations.configureEach {
exclude group: 'androidx.appcompat', module: 'appcompat'
exclude group: 'androidx.profileinstaller', module: 'profileinstaller'
}
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2'
implementation project(':server')
implementation project(':rish')
implementation project(':starter')
implementation project(':api')
implementation project(':provider')
implementation libs.hidden.compat
compileOnly libs.hidden.stub
implementation 'androidx.browser:browser:1.8.0'
implementation 'androidx.core:core-ktx:1.16.0'
implementation 'androidx.fragment:fragment-ktx:1.8.7'
implementation 'androidx.recyclerview:recyclerview:1.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.9.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'com.github.topjohnwu.libsu:core:6.0.0'
implementation 'dev.rikka.rikkax.appcompat:appcompat:1.6.1'
implementation 'dev.rikka.rikkax.compatibility:compatibility:2.0.0'
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
implementation 'dev.rikka.rikkax.material:material:2.7.2'
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
implementation 'dev.rikka.rikkax.html:html-ktx:1.1.2'
implementation 'dev.rikka.rikkax.recyclerview:recyclerview-adapter:1.3.0'
implementation 'dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.2'
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0'
implementation 'dev.rikka.rikkax.widget:borderview:1.1.0'
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
implementation 'dev.rikka.rikkax.lifecycle:lifecycle-resource-livedata:1.0.1'
implementation 'dev.rikka.rikkax.lifecycle:lifecycle-shared-viewmodel:1.0.1'
implementation 'dev.rikka.rikkax.lifecycle:lifecycle-viewmodel-lazy:2.0.0'
implementation 'io.github.vvb2060.ndk:boringssl:20250114'
implementation 'org.lsposed.libcxx:libcxx:27.0.12077973'
implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:6.1'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.80'
implementation 'me.zhanghai.android.appiconloader:appiconloader:1.5.0'
}
apply from: rootProject.file('signing.gradle')
================================================
FILE: manager/proguard-rules.pro
================================================
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
}
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void check*(...);
public static void throw*(...);
}
-assumenosideeffects class java.util.Objects{
** requireNonNull(...);
}
-keepnames class moe.shizuku.api.BinderContainer
# Missing class android.app.IProcessObserver$Stub
# Missing class android.app.IUidObserver$Stub
-keepclassmembers class rikka.hidden.compat.adapter.ProcessObserverAdapter {
<methods>;
}
-keepclassmembers class rikka.hidden.compat.adapter.UidObserverAdapter {
<methods>;
}
# Entrance of Shizuku service
-keep class rikka.shizuku.server.ShizukuService {
public static void main(java.lang.String[]);
}
# Entrance of user service starter
-keep class moe.shizuku.starter.ServiceStarter {
public static void main(java.lang.String[]);
}
# Entrance of shell
-keep class moe.shizuku.manager.shell.Shell {
public static void main(java.lang.String[], java.lang.String, android.os.IBinder, android.os.Handler);
}
-assumenosideeffects class android.util.Log {
public static *** d(...);
}
-assumenosideeffects class moe.shizuku.manager.utils.Logger {
public *** d(...);
}
#noinspection ShrinkerUnresolvedReference
-assumenosideeffects class rikka.shizuku.server.util.Logger {
public *** d(...);
}
-allowaccessmodification
-repackageclasses rikka.shizuku
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
================================================
FILE: manager/src/main/.gitignore
================================================
/assets/*.dex
================================================
FILE: manager/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.WRITE_SECURE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<permission-group
android:name="moe.shizuku.manager.permission-group.API"
android:description="@string/permission_group_description"
android:icon="@drawable/ic_system_icon"
android:label="@string/permission_group_label" />
<permission
android:name="moe.shizuku.manager.permission.MANAGER"
android:protectionLevel="signature" />
<uses-permission android:name="moe.shizuku.manager.permission.MANAGER" />
<permission
android:name="moe.shizuku.manager.permission.API_V23"
android:description="@string/permission_description"
android:icon="@drawable/ic_system_icon"
android:label="@string/permission_label"
android:permissionGroup="moe.shizuku.manager.permission-group.API"
android:protectionLevel="dangerous" />
<uses-permission
android:name="moe.shizuku.manager.permission.API_V23"
tools:node="remove" />
<permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="signature"
tools:node="remove" />
<uses-permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
tools:node="remove" />
<application
android:name=".ShizukuApplication"
android:allowBackup="true"
android:autoRevokePermissions="allowed"
android:banner="@drawable/ic_launcher"
android:dataExtractionRules="@xml/data_extraction_rules"
android:directBootAware="true"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning"
tools:remove="android:appComponentFactory"
tools:targetApi="33">
<activity
android:name=".MainActivity"
android:banner="@mipmap/banner"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!-- 2023.08.03: Google play enforces that apps with LEANBACK_LAUNCHER Activity must be uploaded with App Bundle format, -->
<!-- and possibly additional review steps. To avoid problems, remove it temporarily. -->
<!--<category android:name="android.intent.category.LEANBACK_LAUNCHER" />-->
</intent-filter>
</activity>
<activity
android:name=".management.ApplicationManagementActivity"
android:label="@string/home_app_management_title" />
<activity
android:name=".adb.AdbPairingTutorialActivity"
android:label="@string/adb_pairing" />
<activity
android:name=".shell.ShellTutorialActivity"
android:label="@string/home_terminal_title" />
<activity
android:name=".settings.SettingsActivity"
android:exported="false"
android:label="@string/settings_title">
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
</intent-filter>
</activity>
<activity
android:name=".starter.StarterActivity"
android:label="@string/starter" />
<activity
android:name=".authorization.RequestPermissionActivity"
android:directBootAware="true"
android:excludeFromRecents="true"
android:exported="true"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL"
android:theme="@style/GrantPermissions">
<intent-filter>
<action android:name="${applicationId}.intent.action.REQUEST_PERMISSION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".legacy.ShellRequestHandlerActivity"
android:directBootAware="true"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@style/GrantPermissions">
<intent-filter>
<action android:name="rikka.shizuku.intent.action.REQUEST_BINDER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".legacy.LegacyIsNotSupportedActivity"
android:excludeFromRecents="true"
android:exported="true"
android:permission="moe.shizuku.manager.permission.API"
android:theme="@style/GrantPermissions">
<intent-filter>
<action android:name="${applicationId}.intent.action.REQUEST_AUTHORIZATION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service
android:name=".adb.AdbPairingService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="shortService" />
<receiver
android:name=".receiver.BootCompleteReceiver"
android:directBootAware="true"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name=".receiver.ShizukuReceiver"
android:directBootAware="true"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="rikka.shizuku.intent.action.REQUEST_BINDER" />
</intent-filter>
</receiver>
<provider
android:name=".ShizukuManagerProvider"
android:authorities="${applicationId}.shizuku"
android:directBootAware="true"
android:enabled="true"
android:exported="true"
android:multiprocess="false"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
</application>
</manifest>
================================================
FILE: manager/src/main/assets/rish
================================================
#!/system/bin/sh
BASEDIR=$(dirname "$0")
DEX="$BASEDIR"/rish_shizuku.dex
if [ ! -f "$DEX" ]; then
echo "Cannot find $DEX, please check the tutorial in Shizuku app"
exit 1
fi
if [ $(getprop ro.build.version.sdk) -ge 34 ]; then
if [ -w $DEX ]; then
echo "On Android 14+, app_process cannot load writable dex."
echo "Attempting to remove the write permission..."
chmod 400 $DEX
fi
if [ -w $DEX ]; then
echo "Cannot remove the write permission of $DEX."
echo "You can copy to file to terminal app's private directory (/data/data/<package>, so that remove write permission is possible"
exit 1
fi
fi
# Replace "PKG" with the application id of your terminal app
[ -z "$RISH_APPLICATION_ID" ] && export RISH_APPLICATION_ID="PKG"
/system/bin/app_process -Djava.class.path="$DEX" /system/bin --nice-name=rish rikka.shizuku.shell.ShizukuShellLoader "$@"
================================================
FILE: manager/src/main/java/moe/shizuku/manager/AppConstants.java
================================================
package moe.shizuku.manager;
public class AppConstants {
public static final String TAG = "ShizukuManager";
public static final String NOTIFICATION_CHANNEL_STATUS = "starter";
public static final String NOTIFICATION_CHANNEL_WORK = "work";
public static final int NOTIFICATION_ID_STATUS = 1;
public static final int NOTIFICATION_ID_WORK = 2;
private static final String PACKAGE = "moe.shizuku.manager";
public static final String EXTRA = PACKAGE + ".extra";
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/Helps.java
================================================
package moe.shizuku.manager;
import moe.shizuku.manager.utils.MultiLocaleEntity;
public class Helps {
public static final MultiLocaleEntity ADB = new MultiLocaleEntity();
public static final MultiLocaleEntity ADB_ANDROID11 = new MultiLocaleEntity();
public static final MultiLocaleEntity APPS = new MultiLocaleEntity();
public static final MultiLocaleEntity HOME = new MultiLocaleEntity();
public static final MultiLocaleEntity DOWNLOAD = new MultiLocaleEntity();
public static final MultiLocaleEntity SUI = new MultiLocaleEntity();
public static final MultiLocaleEntity RISH = new MultiLocaleEntity();
public static final MultiLocaleEntity ADB_PERMISSION = new MultiLocaleEntity();
static {
ADB.put("zh-CN", "https://shizuku.rikka.app/zh-hans/guide/setup/");
ADB.put("zh-TW", "https://shizuku.rikka.app/zh-hant/guide/setup/");
ADB.put("en", "https://shizuku.rikka.app/guide/setup/");
ADB_ANDROID11.put("zh-CN", "https://shizuku.rikka.app/zh-hans/guide/setup/");
ADB_ANDROID11.put("zh-TW", "https://shizuku.rikka.app/zh-hant/guide/setup/");
ADB_ANDROID11.put("en", "https://shizuku.rikka.app/guide/setup/");
APPS.put("zh-CN", "https://shizuku.rikka.app/zh-hans/apps/");
APPS.put("zh-TW", "https://shizuku.rikka.app/zh-hant/apps/");
APPS.put("en", "https://shizuku.rikka.app/apps/");
HOME.put("zh-CN", "https://shizuku.rikka.app/zh-hans/");
HOME.put("zh-TW", "https://shizuku.rikka.app/zh-hant/");
HOME.put("en", "https://shizuku.rikka.app/");
DOWNLOAD.put("zh-CN", "https://shizuku.rikka.app/zh-hans/download/");
DOWNLOAD.put("zh-TW", "https://shizuku.rikka.app/zh-hant/download/");
DOWNLOAD.put("en", "https://shizuku.rikka.app/download/");
ADB_PERMISSION.put("zh-CN", "https://shizuku.rikka.app/zh-hans/guide/setup/#%E9%80%9A%E8%BF%87%E6%97%A0%E7%BA%BF%E8%B0%83%E8%AF%95%E5%90%AF%E5%8A%A8-%E9%80%9A%E8%BF%87%E8%BF%9E%E6%8E%A5%E7%94%B5%E8%84%91%E5%90%AF%E5%8A%A8-adb-%E6%9D%83%E9%99%90%E5%8F%97%E9%99%90");
ADB_PERMISSION.put("zh-TW", "https://shizuku.rikka.app/zh-hant/guide/setup/#%E9%80%8F%E9%81%8E%E7%84%A1%E7%B7%9A%E9%99%A4%E9%8C%AF%E5%95%9F%E5%8B%95-%E9%80%8F%E9%81%8E%E9%80%A3%E7%B7%9A%E9%9B%BB%E8%85%A6%E5%95%9F%E5%8B%95-adb-%E6%AC%8A%E9%99%90%E5%8F%97%E9%99%90");
ADB_PERMISSION.put("en", "https://shizuku.rikka.app/guide/setup/#start-via-wireless-debugging-start-by-connecting-to-a-computer-the-permission-of-adb-is-limited");
SUI.put("en", "https://github.com/RikkaApps/Sui");
RISH.put("en", "https://github.com/RikkaApps/Shizuku-API/tree/master/rish");
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/MainActivity.java
================================================
package moe.shizuku.manager;
import moe.shizuku.manager.home.HomeActivity;
public class MainActivity extends HomeActivity {
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/Manifest.java
================================================
package moe.shizuku.manager;
public class Manifest {
public static class permission {
public static final String API_V23 = "moe.shizuku.manager.permission.API_V23";
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/ShizukuApplication.kt
================================================
package moe.shizuku.manager
import android.app.Application
import android.content.Context
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate
import com.topjohnwu.superuser.Shell
import moe.shizuku.manager.ktx.logd
import org.lsposed.hiddenapibypass.HiddenApiBypass
import rikka.core.util.BuildUtils.atLeast30
import rikka.material.app.LocaleDelegate
lateinit var application: ShizukuApplication
class ShizukuApplication : Application() {
companion object {
init {
logd("ShizukuApplication", "init")
Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_REDIRECT_STDERR))
if (Build.VERSION.SDK_INT >= 28) {
HiddenApiBypass.setHiddenApiExemptions("")
}
if (atLeast30) {
System.loadLibrary("adb")
}
}
}
private fun init(context: Context?) {
ShizukuSettings.initialize(context)
LocaleDelegate.defaultLocale = ShizukuSettings.getLocale()
AppCompatDelegate.setDefaultNightMode(ShizukuSettings.getNightMode())
}
override fun onCreate() {
super.onCreate()
application = this
init(this)
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/ShizukuManagerProvider.kt
================================================
package moe.shizuku.manager
import android.os.Bundle
import androidx.core.os.bundleOf
import moe.shizuku.api.BinderContainer
import moe.shizuku.manager.utils.Logger.LOGGER
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuApiConstants.USER_SERVICE_ARG_TOKEN
import rikka.shizuku.ShizukuProvider
import rikka.shizuku.server.ktx.workerHandler
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
class ShizukuManagerProvider : ShizukuProvider() {
companion object {
private const val EXTRA_BINDER = "moe.shizuku.privileged.api.intent.extra.BINDER"
private const val METHOD_SEND_USER_SERVICE = "sendUserService"
}
override fun onCreate(): Boolean {
disableAutomaticSuiInitialization()
return super.onCreate()
}
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
if (extras == null) return null
return if (method == METHOD_SEND_USER_SERVICE) {
try {
extras.classLoader = BinderContainer::class.java.classLoader
val token = extras.getString(USER_SERVICE_ARG_TOKEN) ?: return null
val binder = extras.getParcelable<BinderContainer>(EXTRA_BINDER)?.binder ?: return null
val countDownLatch = CountDownLatch(1)
var reply: Bundle? = Bundle()
val listener = object : Shizuku.OnBinderReceivedListener {
override fun onBinderReceived() {
try {
Shizuku.attachUserService(binder, bundleOf(
USER_SERVICE_ARG_TOKEN to token
))
reply!!.putParcelable(EXTRA_BINDER, BinderContainer(Shizuku.getBinder()))
} catch (e: Throwable) {
LOGGER.e(e, "attachUserService $token")
reply = null
}
Shizuku.removeBinderReceivedListener(this)
countDownLatch.countDown()
}
}
Shizuku.addBinderReceivedListenerSticky(listener, workerHandler)
return try {
countDownLatch.await(5, TimeUnit.SECONDS)
reply
} catch (e: TimeoutException) {
LOGGER.e(e, "Binder not received in 5s")
null
}
} catch (e: Throwable) {
LOGGER.e(e, "sendUserService")
null
}
} else {
super.call(method, arg, extras)
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/ShizukuSettings.java
================================================
package moe.shizuku.manager;
import android.app.ActivityThread;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.SharedPreferences;
import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatDelegate;
import java.lang.annotation.Retention;
import java.util.Locale;
import moe.shizuku.manager.utils.EmptySharedPreferencesImpl;
import moe.shizuku.manager.utils.EnvironmentUtils;
import static java.lang.annotation.RetentionPolicy.SOURCE;
public class ShizukuSettings {
public static final String NAME = "settings";
public static final String NIGHT_MODE = "night_mode";
public static final String LANGUAGE = "language";
public static final String KEEP_START_ON_BOOT = "start_on_boot";
private static SharedPreferences sPreferences;
public static SharedPreferences getPreferences() {
return sPreferences;
}
@NonNull
private static Context getSettingsStorageContext(@NonNull Context context) {
Context storageContext;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
storageContext = context.createDeviceProtectedStorageContext();
} else {
storageContext = context;
}
storageContext = new ContextWrapper(storageContext) {
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
try {
return super.getSharedPreferences(name, mode);
} catch (IllegalStateException e) {
// SharedPreferences in credential encrypted storage are not available until after user is unlocked
return new EmptySharedPreferencesImpl();
}
}
};
return storageContext;
}
public static void initialize(Context context) {
if (sPreferences == null) {
sPreferences = getSettingsStorageContext(context)
.getSharedPreferences(NAME, Context.MODE_PRIVATE);
}
}
@IntDef({
LaunchMethod.UNKNOWN,
LaunchMethod.ROOT,
LaunchMethod.ADB,
})
@Retention(SOURCE)
public @interface LaunchMethod {
int UNKNOWN = -1;
int ROOT = 0;
int ADB = 1;
}
@LaunchMethod
public static int getLastLaunchMode() {
return getPreferences().getInt("mode", LaunchMethod.UNKNOWN);
}
public static void setLastLaunchMode(@LaunchMethod int method) {
getPreferences().edit().putInt("mode", method).apply();
}
@AppCompatDelegate.NightMode
public static int getNightMode() {
int defValue = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
if (EnvironmentUtils.isWatch(ActivityThread.currentActivityThread().getApplication())) {
defValue = AppCompatDelegate.MODE_NIGHT_YES;
}
return getPreferences().getInt(NIGHT_MODE, defValue);
}
public static Locale getLocale() {
String tag = getPreferences().getString(LANGUAGE, null);
if (TextUtils.isEmpty(tag) || "SYSTEM".equals(tag)) {
return Locale.getDefault();
}
return Locale.forLanguageTag(tag);
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/adb/AdbClient.kt
================================================
package moe.shizuku.manager.adb
import android.util.Log
import moe.shizuku.manager.adb.AdbProtocol.ADB_AUTH_RSAPUBLICKEY
import moe.shizuku.manager.adb.AdbProtocol.ADB_AUTH_SIGNATURE
import moe.shizuku.manager.adb.AdbProtocol.ADB_AUTH_TOKEN
import moe.shizuku.manager.adb.AdbProtocol.A_AUTH
import moe.shizuku.manager.adb.AdbProtocol.A_CLSE
import moe.shizuku.manager.adb.AdbProtocol.A_CNXN
import moe.shizuku.manager.adb.AdbProtocol.A_MAXDATA
import moe.shizuku.manager.adb.AdbProtocol.A_OKAY
import moe.shizuku.manager.adb.AdbProtocol.A_OPEN
import moe.shizuku.manager.adb.AdbProtocol.A_STLS
import moe.shizuku.manager.adb.AdbProtocol.A_STLS_VERSION
import moe.shizuku.manager.adb.AdbProtocol.A_VERSION
import moe.shizuku.manager.adb.AdbProtocol.A_WRTE
import moe.shizuku.manager.ktx.logd
import rikka.core.util.BuildUtils
import java.io.Closeable
import java.io.DataInputStream
import java.io.DataOutputStream
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.ByteOrder
import javax.net.ssl.SSLSocket
private const val TAG = "AdbClient"
class AdbClient(private val host: String, private val port: Int, private val key: AdbKey) : Closeable {
private lateinit var socket: Socket
private lateinit var plainInputStream: DataInputStream
private lateinit var plainOutputStream: DataOutputStream
private var useTls = false
private lateinit var tlsSocket: SSLSocket
private lateinit var tlsInputStream: DataInputStream
private lateinit var tlsOutputStream: DataOutputStream
private val inputStream get() = if (useTls) tlsInputStream else plainInputStream
private val outputStream get() = if (useTls) tlsOutputStream else plainOutputStream
fun connect() {
socket = Socket(host, port)
socket.tcpNoDelay = true
plainInputStream = DataInputStream(socket.getInputStream())
plainOutputStream = DataOutputStream(socket.getOutputStream())
write(A_CNXN, A_VERSION, A_MAXDATA, "host::")
var message = read()
if (message.command == A_STLS) {
if (!BuildUtils.atLeast29) {
error("Connect to adb with TLS is not supported before Android 9")
}
write(A_STLS, A_STLS_VERSION, 0)
val sslContext = key.sslContext
tlsSocket = sslContext.socketFactory.createSocket(socket, host, port, true) as SSLSocket
tlsSocket.startHandshake()
Log.d(TAG, "Handshake succeeded.")
tlsInputStream = DataInputStream(tlsSocket.inputStream)
tlsOutputStream = DataOutputStream(tlsSocket.outputStream)
useTls = true
message = read()
} else if (message.command == A_AUTH) {
if (message.command != A_AUTH && message.arg0 != ADB_AUTH_TOKEN) error("not A_AUTH ADB_AUTH_TOKEN")
write(A_AUTH, ADB_AUTH_SIGNATURE, 0, key.sign(message.data))
message = read()
if (message.command != A_CNXN) {
write(A_AUTH, ADB_AUTH_RSAPUBLICKEY, 0, key.adbPublicKey)
message = read()
}
}
if (message.command != A_CNXN) error("not A_CNXN")
}
fun shellCommand(command: String, listener: ((ByteArray) -> Unit)?) {
val localId = 1
write(A_OPEN, localId, 0, "shell:$command")
var message = read()
when (message.command) {
A_OKAY -> {
while (true) {
message = read()
val remoteId = message.arg0
if (message.command == A_WRTE) {
if (message.data_length > 0) {
listener?.invoke(message.data!!)
}
write(A_OKAY, localId, remoteId)
} else if (message.command == A_CLSE) {
write(A_CLSE, localId, remoteId)
break
} else {
error("not A_WRTE or A_CLSE")
}
}
}
A_CLSE -> {
val remoteId = message.arg0
write(A_CLSE, localId, remoteId)
}
else -> {
error("not A_OKAY or A_CLSE")
}
}
}
private fun write(command: Int, arg0: Int, arg1: Int, data: ByteArray? = null) = write(AdbMessage(command, arg0, arg1, data))
private fun write(command: Int, arg0: Int, arg1: Int, data: String) = write(AdbMessage(command, arg0, arg1, data))
private fun write(message: AdbMessage) {
outputStream.write(message.toByteArray())
outputStream.flush()
Log.d(TAG, "write ${message.toStringShort()}")
}
private fun read(): AdbMessage {
val buffer = ByteBuffer.allocate(AdbMessage.HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN)
inputStream.readFully(buffer.array(), 0, 24)
val command = buffer.int
val arg0 = buffer.int
val arg1 = buffer.int
val dataLength = buffer.int
val checksum = buffer.int
val magic = buffer.int
val data: ByteArray?
if (dataLength >= 0) {
data = ByteArray(dataLength)
inputStream.readFully(data, 0, dataLength)
} else {
data = null
}
val message = AdbMessage(command, arg0, arg1, dataLength, checksum, magic, data)
message.validateOrThrow()
Log.d(TAG, "read ${message.toStringShort()}")
return message
}
override fun close() {
try {
plainInputStream.close()
} catch (e: Throwable) {
}
try {
plainOutputStream.close()
} catch (e: Throwable) {
}
try {
socket.close()
} catch (e: Exception) {
}
if (useTls) {
try {
tlsInputStream.close()
} catch (e: Throwable) {
}
try {
tlsOutputStream.close()
} catch (e: Throwable) {
}
try {
tlsSocket.close()
} catch (e: Exception) {
}
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/adb/AdbException.kt
================================================
package moe.shizuku.manager.adb
@Suppress("NOTHING_TO_INLINE")
inline fun adbError(message: Any): Nothing = throw AdbException(message.toString())
open class AdbException : Exception {
constructor(message: String, cause: Throwable?) : super(message, cause)
constructor(message: String) : super(message)
constructor(cause: Throwable) : super(cause)
constructor()
}
class AdbInvalidPairingCodeException : AdbException()
class AdbKeyException(cause: Throwable) : AdbException(cause)
================================================
FILE: manager/src/main/java/moe/shizuku/manager/adb/AdbKey.kt
================================================
package moe.shizuku.manager.adb
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.content.edit
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import rikka.core.ktx.unsafeLazy
import java.io.ByteArrayInputStream
import java.math.BigInteger
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.security.*
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.RSAKeyGenParameterSpec
import java.security.spec.RSAPublicKeySpec
import java.util.*
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.spec.GCMParameterSpec
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLEngine
import javax.net.ssl.X509ExtendedKeyManager
import javax.net.ssl.X509ExtendedTrustManager
private const val TAG = "AdbKey"
class AdbKey(private val adbKeyStore: AdbKeyStore, name: String) {
companion object {
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
private const val ENCRYPTION_KEY_ALIAS = "_adbkey_encryption_key_"
private const val TRANSFORMATION = "AES/GCM/NoPadding"
private const val IV_SIZE_IN_BYTES = 12
private const val TAG_SIZE_IN_BYTES = 16
private val PADDING = byteArrayOf(
0x00, 0x01, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0x00,
0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00,
0x04, 0x14)
}
private val encryptionKey: Key
private val privateKey: RSAPrivateKey
private val publicKey: RSAPublicKey
private val certificate: X509Certificate
init {
this.encryptionKey = getOrCreateEncryptionKey() ?: error("Failed to generate encryption key with AndroidKeyManager.")
this.privateKey = getOrCreatePrivateKey()
this.publicKey = KeyFactory.getInstance("RSA").generatePublic(RSAPublicKeySpec(privateKey.modulus, RSAKeyGenParameterSpec.F4)) as RSAPublicKey
val signer = JcaContentSignerBuilder("SHA256withRSA").build(privateKey)
val x509Certificate = X509v3CertificateBuilder(X500Name("CN=00"),
BigInteger.ONE,
Date(0),
Date(2461449600 * 1000),
Locale.ROOT,
X500Name("CN=00"),
SubjectPublicKeyInfo.getInstance(publicKey.encoded)
).build(signer)
this.certificate = CertificateFactory.getInstance("X.509")
.generateCertificate(ByteArrayInputStream(x509Certificate.encoded)) as X509Certificate
Log.d(TAG, privateKey.toString())
}
val adbPublicKey: ByteArray by unsafeLazy {
publicKey.adbEncoded(name)
}
private fun getOrCreateEncryptionKey(): Key? {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null)
return keyStore.getKey(ENCRYPTION_KEY_ALIAS, null) ?: run {
val parameterSpec = KeyGenParameterSpec.Builder(ENCRYPTION_KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE)
keyGenerator.init(parameterSpec)
keyGenerator.generateKey()
}
}
private fun encrypt(plaintext: ByteArray, aad: ByteArray?): ByteArray? {
if (plaintext.size > Int.MAX_VALUE - IV_SIZE_IN_BYTES - TAG_SIZE_IN_BYTES) {
return null
}
val ciphertext = ByteArray(IV_SIZE_IN_BYTES + plaintext.size + TAG_SIZE_IN_BYTES)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey)
cipher.updateAAD(aad)
cipher.doFinal(plaintext, 0, plaintext.size, ciphertext, IV_SIZE_IN_BYTES)
System.arraycopy(cipher.iv, 0, ciphertext, 0, IV_SIZE_IN_BYTES)
return ciphertext
}
private fun decrypt(ciphertext: ByteArray, aad: ByteArray?): ByteArray? {
if (ciphertext.size < IV_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES) {
return null
}
val params = GCMParameterSpec(8 * TAG_SIZE_IN_BYTES, ciphertext, 0, IV_SIZE_IN_BYTES)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, encryptionKey, params)
cipher.updateAAD(aad)
return cipher.doFinal(ciphertext, IV_SIZE_IN_BYTES, ciphertext.size - IV_SIZE_IN_BYTES)
}
private fun getOrCreatePrivateKey(): RSAPrivateKey {
var privateKey: RSAPrivateKey? = null
val aad = ByteArray(16)
"adbkey".toByteArray().copyInto(aad)
var ciphertext = adbKeyStore.get()
if (ciphertext != null) {
try {
val plaintext = decrypt(ciphertext, aad)
val keyFactory = KeyFactory.getInstance("RSA")
privateKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(plaintext)) as RSAPrivateKey
} catch (e: Exception) {
}
}
if (privateKey == null) {
val keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA)
keyPairGenerator.initialize(RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
val keyPair = keyPairGenerator.generateKeyPair()
privateKey = keyPair.private as RSAPrivateKey
ciphertext = encrypt(privateKey.encoded, aad)
if (ciphertext != null) {
adbKeyStore.put(ciphertext)
}
}
return privateKey
}
fun sign(data: ByteArray?): ByteArray {
val cipher = Cipher.getInstance("RSA/ECB/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, privateKey)
cipher.update(PADDING)
return cipher.doFinal(data)
}
private val keyManager
get() = object : X509ExtendedKeyManager() {
private val alias = "key"
override fun chooseClientAlias(keyTypes: Array<out String>, issuers: Array<out Principal>?, socket: Socket?): String? {
Log.d(TAG, "chooseClientAlias: keyType=${keyTypes.contentToString()}, issuers=${issuers?.contentToString()}")
for (keyType in keyTypes) {
if (keyType == "RSA") return alias
}
return null
}
override fun getCertificateChain(alias: String?): Array<X509Certificate>? {
Log.d(TAG, "getCertificateChain: alias=$alias")
return if (alias == this.alias) arrayOf(certificate) else null
}
override fun getPrivateKey(alias: String?): PrivateKey? {
Log.d(TAG, "getPrivateKey: alias=$alias")
return if (alias == this.alias) privateKey else null
}
override fun getClientAliases(keyType: String?, issuers: Array<out Principal>?): Array<String>? {
return null
}
override fun getServerAliases(keyType: String, issuers: Array<out Principal>?): Array<String>? {
return null
}
override fun chooseServerAlias(keyType: String, issuers: Array<out Principal>?, socket: Socket?): String? {
return null
}
}
private val trustManager
get() =
@RequiresApi(Build.VERSION_CODES.R)
object : X509ExtendedTrustManager() {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?, socket: Socket?) {
}
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?, engine: SSLEngine?) {
}
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
}
@SuppressLint("TrustAllX509TrustManager")
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?, socket: Socket?) {
}
@SuppressLint("TrustAllX509TrustManager")
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?, engine: SSLEngine?) {
}
@SuppressLint("TrustAllX509TrustManager")
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return emptyArray()
}
}
@delegate:RequiresApi(Build.VERSION_CODES.R)
val sslContext: SSLContext by unsafeLazy {
val sslContext = SSLContext.getInstance("TLSv1.3")
sslContext.init(arrayOf(keyManager), arrayOf(trustManager), SecureRandom())
sslContext
}
}
interface AdbKeyStore {
fun put(bytes: ByteArray)
fun get(): ByteArray?
}
class PreferenceAdbKeyStore(private val preference: SharedPreferences) : AdbKeyStore {
private val preferenceKey = "adbkey"
override fun put(bytes: ByteArray) {
preference.edit { putString(preferenceKey, String(Base64.encode(bytes, Base64.NO_WRAP))) }
}
override fun get(): ByteArray? {
if (!preference.contains(preferenceKey)) return null
return Base64.decode(preference.getString(preferenceKey, null), Base64.NO_WRAP)
}
}
const val ANDROID_PUBKEY_MODULUS_SIZE = 2048 / 8
const val ANDROID_PUBKEY_MODULUS_SIZE_WORDS = ANDROID_PUBKEY_MODULUS_SIZE / 4
const val RSAPublicKey_Size = 524
private fun BigInteger.toAdbEncoded(): IntArray {
// little-endian integer with padding zeros in the end
val endcoded = IntArray(ANDROID_PUBKEY_MODULUS_SIZE_WORDS)
val r32 = BigInteger.ZERO.setBit(32)
var tmp = this.add(BigInteger.ZERO)
for (i in 0 until ANDROID_PUBKEY_MODULUS_SIZE_WORDS) {
val out = tmp.divideAndRemainder(r32)
tmp = out[0]
endcoded[i] = out[1].toInt()
}
return endcoded
}
private fun RSAPublicKey.adbEncoded(name: String): ByteArray {
// https://cs.android.com/android/platform/superproject/+/android-10.0.0_r30:system/core/libcrypto_utils/android_pubkey.c
/*
typedef struct RSAPublicKey {
uint32_t modulus_size_words; // ANDROID_PUBKEY_MODULUS_SIZE
uint32_t n0inv; // n0inv = -1 / N[0] mod 2^32
uint8_t modulus[ANDROID_PUBKEY_MODULUS_SIZE];
uint8_t rr[ANDROID_PUBKEY_MODULUS_SIZE]; // rr = (2^(rsa_size)) ^ 2 mod N
uint32_t exponent;
} RSAPublicKey;
*/
val r32 = BigInteger.ZERO.setBit(32)
val n0inv = modulus.remainder(r32).modInverse(r32).negate()
val r = BigInteger.ZERO.setBit(ANDROID_PUBKEY_MODULUS_SIZE * 8)
val rr = r.modPow(BigInteger.valueOf(2), modulus)
val buffer = ByteBuffer.allocate(RSAPublicKey_Size).order(ByteOrder.LITTLE_ENDIAN)
buffer.putInt(ANDROID_PUBKEY_MODULUS_SIZE_WORDS)
buffer.putInt(n0inv.toInt())
modulus.toAdbEncoded().forEach { buffer.putInt(it) }
rr.toAdbEncoded().forEach { buffer.putInt(it) }
buffer.putInt(publicExponent.toInt())
val base64Bytes = Base64.encode(buffer.array(), Base64.NO_WRAP)
val nameBytes = " $name\u0000".toByteArray()
val bytes = ByteArray(base64Bytes.size + nameBytes.size)
base64Bytes.copyInto(bytes)
nameBytes.copyInto(bytes, base64Bytes.size)
return bytes
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/adb/AdbMdns.kt
================================================
package moe.shizuku.manager.adb
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.lifecycle.Observer
import java.io.IOException
import java.net.InetSocketAddress
import java.net.NetworkInterface
import java.net.ServerSocket
@RequiresApi(Build.VERSION_CODES.R)
class AdbMdns(
context: Context, private val serviceType: String,
private val observer: Observer<Int>
) {
private var registered = false
private var running = false
private var serviceName: String? = null
private val listener = DiscoveryListener(this)
private val nsdManager: NsdManager = context.getSystemService(NsdManager::class.java)
fun start() {
if (running) return
running = true
if (!registered) {
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener)
}
}
fun stop() {
if (!running) return
running = false
if (registered) {
nsdManager.stopServiceDiscovery(listener)
}
}
private fun onDiscoveryStart() {
registered = true
}
private fun onDiscoveryStop() {
registered = false
}
private fun onServiceFound(info: NsdServiceInfo) {
nsdManager.resolveService(info, ResolveListener(this))
}
private fun onServiceLost(info: NsdServiceInfo) {
if (info.serviceName == serviceName) observer.onChanged(-1)
}
private fun onServiceResolved(resolvedService: NsdServiceInfo) {
if (running && NetworkInterface.getNetworkInterfaces()
.asSequence()
.any { networkInterface ->
networkInterface.inetAddresses
.asSequence()
.any { resolvedService.host.hostAddress == it.hostAddress }
}
&& isPortAvailable(resolvedService.port)
) {
serviceName = resolvedService.serviceName
observer.onChanged(resolvedService.port)
}
}
private fun isPortAvailable(port: Int) = try {
ServerSocket().use {
it.bind(InetSocketAddress("127.0.0.1", port), 1)
false
}
} catch (e: IOException) {
true
}
internal class DiscoveryListener(private val adbMdns: AdbMdns) : NsdManager.DiscoveryListener {
override fun onDiscoveryStarted(serviceType: String) {
Log.v(TAG, "onDiscoveryStarted: $serviceType")
adbMdns.onDiscoveryStart()
}
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
Log.v(TAG, "onStartDiscoveryFailed: $serviceType, $errorCode")
}
override fun onDiscoveryStopped(serviceType: String) {
Log.v(TAG, "onDiscoveryStopped: $serviceType")
adbMdns.onDiscoveryStop()
}
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
Log.v(TAG, "onStopDiscoveryFailed: $serviceType, $errorCode")
}
override fun onServiceFound(serviceInfo: NsdServiceInfo) {
Log.v(TAG, "onServiceFound: ${serviceInfo.serviceName}")
adbMdns.onServiceFound(serviceInfo)
}
override fun onServiceLost(serviceInfo: NsdServiceInfo) {
Log.v(TAG, "onServiceLost: ${serviceInfo.serviceName}")
adbMdns.onServiceLost(serviceInfo)
}
}
internal class ResolveListener(private val adbMdns: AdbMdns) : NsdManager.ResolveListener {
override fun onResolveFailed(nsdServiceInfo: NsdServiceInfo, i: Int) {}
override fun onServiceResolved(nsdServiceInfo: NsdServiceInfo) {
adbMdns.onServiceResolved(nsdServiceInfo)
}
}
companion object {
const val TLS_CONNECT = "_adb-tls-connect._tcp"
const val TLS_PAIRING = "_adb-tls-pairing._tcp"
const val TAG = "AdbMdns"
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/adb/AdbMessage.kt
================================================
package moe.shizuku.manager.adb
import moe.shizuku.manager.adb.AdbProtocol.A_AUTH
import moe.shizuku.manager.adb.AdbProtocol.A_CLSE
import moe.shizuku.manager.adb.AdbProtocol.A_CNXN
import moe.shizuku.manager.adb.AdbProtocol.A_OKAY
import moe.shizuku.manager.adb.AdbProtocol.A_OPEN
import moe.shizuku.manager.adb.AdbProtocol.A_STLS
import moe.shizuku.manager.adb.AdbProtocol.A_SYNC
import moe.shizuku.manager.adb.AdbProtocol.A_WRTE
import java.nio.ByteBuffer
import java.nio.ByteOrder
class AdbMessage(
val command: Int,
val arg0: Int,
val arg1: Int,
val data_length: Int,
val data_crc32: Int,
val magic: Int,
val data: ByteArray?
) {
constructor(command: Int, arg0: Int, arg1: Int, data: String) : this(
command,
arg0,
arg1,
"$data\u0000".toByteArray())
constructor(command: Int, arg0: Int, arg1: Int, data: ByteArray?) : this(
command,
arg0,
arg1,
data?.size ?: 0,
crc32(data),
(command.toLong() xor 0xFFFFFFFF).toInt(),
data)
fun validate(): Boolean {
if (command != magic xor -0x1) return false
if (data_length != 0 && crc32(data) != data_crc32) return false
return true
}
fun validateOrThrow() {
if (!validate()) throw IllegalArgumentException("bad message ${this.toStringShort()}")
}
fun toByteArray(): ByteArray {
val length = HEADER_LENGTH + (data?.size ?: 0)
return ByteBuffer.allocate(length).apply {
order(ByteOrder.LITTLE_ENDIAN)
putInt(command)
putInt(arg0)
putInt(arg1)
putInt(data_length)
putInt(data_crc32)
putInt(magic)
if (data != null) {
put(data)
}
}.array()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AdbMessage
if (command != other.command) return false
if (arg0 != other.arg0) return false
if (arg1 != other.arg1) return false
if (data_length != other.data_length) return false
if (data_crc32 != other.data_crc32) return false
if (magic != other.magic) return false
if (data != null) {
if (other.data == null) return false
if (!data.contentEquals(other.data)) return false
} else if (other.data != null) return false
return true
}
override fun hashCode(): Int {
var result = command
result = 31 * result + arg0
result = 31 * result + arg1
result = 31 * result + data_length
result = 31 * result + data_crc32
result = 31 * result + magic
result = 31 * result + (data?.contentHashCode() ?: 0)
return result
}
override fun toString(): String {
return "AdbMessage(${toStringShort()})"
}
fun toStringShort(): String {
val commandString = when (command) {
A_SYNC -> "A_SYNC"
A_CNXN -> "A_CNXN"
A_AUTH -> "A_AUTH"
A_OPEN -> "A_OPEN"
A_OKAY -> "A_OKAY"
A_CLSE -> "A_CLSE"
A_WRTE -> "A_WRTE"
A_STLS -> "A_STLS"
else -> command.toString()
}
return "command=$commandString, arg0=$arg0, arg1=$arg1, data_length=$data_length, data_crc32=$data_crc32, magic=$magic, data=${data?.contentToString()}"
}
companion object {
const val HEADER_LENGTH = 24
private fun crc32(data: ByteArray?): Int {
if (data == null) return 0
var res = 0
for (b in data) {
if (b >= 0)
res += b
else
res += b + 256
}
return res
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/adb/AdbPairingClient.kt
================================================
package moe.shizuku.manager.adb
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.android.org.conscrypt.Conscrypt
import java.io.Closeable
import java.io.DataInputStream
import java.io.DataOutputStream
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.ByteOrder
import javax.net.ssl.SSLSocket
private const val TAG = "AdbPairClient"
private const val kCurrentKeyHeaderVersion = 1.toByte()
private const val kMinSupportedKeyHeaderVersion = 1.toByte()
private const val kMaxSupportedKeyHeaderVersion = 1.toByte()
private const val kMaxPeerInfoSize = 8192
private const val kMaxPayloadSize = kMaxPeerInfoSize * 2
private const val kExportedKeyLabel = "adb-label\u0000"
private const val kExportedKeySize = 64
private const val kPairingPacketHeaderSize = 6
private class PeerInfo(
val type: Byte,
data: ByteArray) {
val data = ByteArray(kMaxPeerInfoSize - 1)
init {
data.copyInto(this.data, 0, 0, data.size.coerceAtMost(kMaxPeerInfoSize - 1))
}
enum class Type(val value: Byte) {
ADB_RSA_PUB_KEY(0.toByte()),
ADB_DEVICE_GUID(0.toByte()),
}
fun writeTo(buffer: ByteBuffer) {
buffer.run {
put(type)
put(data)
}
Log.d(TAG, "write PeerInfo ${toStringShort()}")
}
override fun toString(): String {
return "PeerInfo(${toStringShort()})"
}
fun toStringShort(): String {
return "type=$type, data=${data.contentToString()}"
}
companion object {
fun readFrom(buffer: ByteBuffer): PeerInfo {
val type = buffer.get()
val data = ByteArray(kMaxPeerInfoSize - 1)
buffer.get(data)
return PeerInfo(type, data)
}
}
}
private class PairingPacketHeader(
val version: Byte,
val type: Byte,
val payload: Int) {
enum class Type(val value: Byte) {
SPAKE2_MSG(0.toByte()),
PEER_INFO(1.toByte())
}
fun writeTo(buffer: ByteBuffer) {
buffer.run {
put(version)
put(type)
putInt(payload)
}
Log.d(TAG, "write PairingPacketHeader ${toStringShort()}")
}
override fun toString(): String {
return "PairingPacketHeader(${toStringShort()})"
}
fun toStringShort(): String {
return "version=${version.toInt()}, type=${type.toInt()}, payload=$payload"
}
companion object {
fun readFrom(buffer: ByteBuffer): PairingPacketHeader? {
val version = buffer.get()
val type = buffer.get()
val payload = buffer.int
if (version < kMinSupportedKeyHeaderVersion || version > kMaxSupportedKeyHeaderVersion) {
Log.e(TAG, "PairingPacketHeader version mismatch (us=$kCurrentKeyHeaderVersion them=${version})")
return null
}
if (type != Type.SPAKE2_MSG.value && type != Type.PEER_INFO.value) {
Log.e(TAG, "Unknown PairingPacket type=${type}")
return null
}
if (payload <= 0 || payload > kMaxPayloadSize) {
Log.e(TAG, "header payload not within a safe payload size (size=${payload})")
return null
}
val header = PairingPacketHeader(version, type, payload)
Log.d(TAG, "read PairingPacketHeader ${header.toStringShort()}")
return header
}
}
}
private class PairingContext private constructor(private val nativePtr: Long) {
val msg: ByteArray
init {
msg = nativeMsg(nativePtr)
}
fun initCipher(theirMsg: ByteArray) = nativeInitCipher(nativePtr, theirMsg)
fun encrypt(`in`: ByteArray) = nativeEncrypt(nativePtr, `in`)
fun decrypt(`in`: ByteArray) = nativeDecrypt(nativePtr, `in`)
fun destroy() = nativeDestroy(nativePtr)
private external fun nativeMsg(nativePtr: Long): ByteArray
private external fun nativeInitCipher(nativePtr: Long, theirMsg: ByteArray): Boolean
private external fun nativeEncrypt(nativePtr: Long, inbuf: ByteArray): ByteArray?
private external fun nativeDecrypt(nativePtr: Long, inbuf: ByteArray): ByteArray?
private external fun nativeDestroy(nativePtr: Long)
companion object {
fun create(password: ByteArray): PairingContext? {
val nativePtr = nativeConstructor(true, password)
return if (nativePtr != 0L) PairingContext(nativePtr) else null
}
@JvmStatic
private external fun nativeConstructor(isClient: Boolean, password: ByteArray): Long
}
}
@RequiresApi(Build.VERSION_CODES.R)
class AdbPairingClient(private val host: String, private val port: Int, private val pairCode: String, private val key: AdbKey) : Closeable {
private enum class State {
Ready,
ExchangingMsgs,
ExchangingPeerInfo,
Stopped
}
private lateinit var socket: Socket
private lateinit var inputStream: DataInputStream
private lateinit var outputStream: DataOutputStream
private val peerInfo: PeerInfo = PeerInfo(PeerInfo.Type.ADB_RSA_PUB_KEY.value, key.adbPublicKey)
private lateinit var pairingContext: PairingContext
private var state: State = State.Ready
fun start(): Boolean {
setupTlsConnection()
state = State.ExchangingMsgs
if (!doExchangeMsgs()) {
state = State.Stopped
return false
}
state = State.ExchangingPeerInfo
if (!doExchangePeerInfo()) {
state = State.Stopped
return false
}
state = State.Stopped
return true
}
private fun setupTlsConnection() {
socket = Socket(host, port)
socket.tcpNoDelay = true
val sslContext = key.sslContext
val sslSocket = sslContext.socketFactory.createSocket(socket, host, port, true) as SSLSocket
sslSocket.startHandshake()
Log.d(TAG, "Handshake succeeded.")
inputStream = DataInputStream(sslSocket.inputStream)
outputStream = DataOutputStream(sslSocket.outputStream)
val pairCodeBytes = pairCode.toByteArray()
val keyMaterial = Conscrypt.exportKeyingMaterial(sslSocket, kExportedKeyLabel, null, kExportedKeySize)
val passwordBytes = ByteArray(pairCode.length + keyMaterial.size)
pairCodeBytes.copyInto(passwordBytes)
keyMaterial.copyInto(passwordBytes, pairCodeBytes.size)
val pairingContext = PairingContext.create(passwordBytes)
checkNotNull(pairingContext) { "Unable to create PairingContext." }
this.pairingContext = pairingContext
}
private fun createHeader(type: PairingPacketHeader.Type, payloadSize: Int): PairingPacketHeader {
return PairingPacketHeader(kCurrentKeyHeaderVersion, type.value, payloadSize)
}
private fun readHeader(): PairingPacketHeader? {
val bytes = ByteArray(kPairingPacketHeaderSize)
inputStream.readFully(bytes)
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN)
return PairingPacketHeader.readFrom(buffer)
}
private fun writeHeader(header: PairingPacketHeader, payload: ByteArray) {
val buffer = ByteBuffer.allocate(kPairingPacketHeaderSize).order(ByteOrder.BIG_ENDIAN)
header.writeTo(buffer)
outputStream.write(buffer.array())
outputStream.write(payload)
Log.d(TAG, "write payload, size=${payload.size}")
}
private fun doExchangeMsgs(): Boolean {
val msg = pairingContext.msg
val size = msg.size
val ourHeader = createHeader(PairingPacketHeader.Type.SPAKE2_MSG, size)
writeHeader(ourHeader, msg)
val theirHeader = readHeader() ?: return false
if (theirHeader.type != PairingPacketHeader.Type.SPAKE2_MSG.value) return false
val theirMessage = ByteArray(theirHeader.payload)
inputStream.readFully(theirMessage)
if (!pairingContext.initCipher(theirMessage)) return false
return true
}
private fun doExchangePeerInfo(): Boolean {
val buf = ByteBuffer.allocate(kMaxPeerInfoSize).order(ByteOrder.BIG_ENDIAN)
peerInfo.writeTo(buf)
val outbuf = pairingContext.encrypt(buf.array()) ?: return false
val ourHeader = createHeader(PairingPacketHeader.Type.PEER_INFO, outbuf.size)
writeHeader(ourHeader, outbuf)
val theirHeader = readHeader() ?: return false
if (theirHeader.type != PairingPacketHeader.Type.PEER_INFO.value) return false
val theirMessage = ByteArray(theirHeader.payload)
inputStream.readFully(theirMessage)
val decrypted = pairingContext.decrypt(theirMessage) ?: throw AdbInvalidPairingCodeException()
if (decrypted.size != kMaxPeerInfoSize) {
Log.e(TAG, "Got size=${decrypted.size} PeerInfo.size=$kMaxPeerInfoSize")
return false
}
val theirPeerInfo = PeerInfo.readFrom(ByteBuffer.wrap(decrypted))
Log.d(TAG, theirPeerInfo.toString())
return true
}
override fun close() {
try {
inputStream.close()
} catch (e: Throwable) {
}
try {
outputStream.close()
} catch (e: Throwable) {
}
try {
socket.close()
} catch (e: Exception) {
}
if (state != State.Ready) {
pairingContext.destroy()
}
}
companion object {
init {
System.loadLibrary("adb")
}
@JvmStatic
external fun available(): Boolean
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/adb/AdbPairingService.kt
================================================
package moe.shizuku.manager.adb
import android.annotation.TargetApi
import android.app.*
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.lifecycle.Observer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import moe.shizuku.manager.R
import moe.shizuku.manager.ShizukuSettings
import rikka.core.ktx.unsafeLazy
import java.net.ConnectException
@TargetApi(Build.VERSION_CODES.R)
class AdbPairingService : Service() {
companion object {
const val notificationChannel = "adb_pairing"
private const val tag = "AdbPairingService"
private const val notificationId = 1
private const val replyRequestId = 1
private const val stopRequestId = 2
private const val retryRequestId = 3
private const val startAction = "start"
private const val stopAction = "stop"
private const val replyAction = "reply"
private const val remoteInputResultKey = "paring_code"
private const val portKey = "paring_code"
fun startIntent(context: Context): Intent {
return Intent(context, AdbPairingService::class.java).setAction(startAction)
}
private fun stopIntent(context: Context): Intent {
return Intent(context, AdbPairingService::class.java).setAction(stopAction)
}
private fun replyIntent(context: Context, port: Int): Intent {
return Intent(context, AdbPairingService::class.java).setAction(replyAction).putExtra(portKey, port)
}
}
private var adbMdns: AdbMdns? = null
private val observer = Observer<Int> { port ->
Log.i(tag, "Pairing service port: $port")
if (port <= 0) return@Observer
// Since the service could be killed before user finishing input,
// we need to put the port into Intent
val notification = createInputNotification(port)
getSystemService(NotificationManager::class.java).notify(notificationId, notification)
}
private var started = false
override fun onCreate() {
super.onCreate()
getSystemService(NotificationManager::class.java).createNotificationChannel(
NotificationChannel(
notificationChannel,
getString(R.string.notification_channel_adb_pairing),
NotificationManager.IMPORTANCE_HIGH
).apply {
setSound(null, null)
setShowBadge(false)
setAllowBubbles(false)
})
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = when (intent?.action) {
startAction -> {
onStart()
}
replyAction -> {
val code = RemoteInput.getResultsFromIntent(intent)?.getCharSequence(remoteInputResultKey) ?: ""
val port = intent.getIntExtra(portKey, -1)
if (port != -1) {
onInput(code.toString(), port)
} else {
onStart()
}
}
stopAction -> {
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
null
}
else -> {
return START_NOT_STICKY
}
}
if (notification != null) {
try {
startForeground(notificationId, notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST)
} catch (e: Throwable) {
Log.e(tag, "startForeground failed", e)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
&& e is ForegroundServiceStartNotAllowedException) {
getSystemService(NotificationManager::class.java).notify(notificationId, notification)
}
}
}
return START_REDELIVER_INTENT
}
private fun startSearch() {
if (started) return
started = true
adbMdns = AdbMdns(this, AdbMdns.TLS_PAIRING, observer).apply { start() }
}
private fun stopSearch() {
if (!started) return
started = false
adbMdns?.stop()
}
override fun onDestroy() {
super.onDestroy()
stopSearch()
}
private fun onStart(): Notification {
startSearch()
return searchingNotification
}
private fun onInput(code: String, port: Int): Notification {
GlobalScope.launch(Dispatchers.IO) {
val host = "127.0.0.1"
val key = try {
AdbKey(PreferenceAdbKeyStore(ShizukuSettings.getPreferences()), "shizuku")
} catch (e: Throwable) {
e.printStackTrace()
return@launch
}
AdbPairingClient(host, port, code, key).runCatching {
start()
}.onFailure {
handleResult(false, it)
}.onSuccess {
handleResult(it, null)
}
}
return workingNotification
}
private fun handleResult(success: Boolean, exception: Throwable?) {
stopForeground(STOP_FOREGROUND_REMOVE)
val title: String
val text: String?
if (success) {
Log.i(tag, "Pair succeed")
title = getString(R.string.notification_adb_pairing_succeed_title)
text = getString(R.string.notification_adb_pairing_succeed_text)
stopSearch()
} else {
title = getString(R.string.notification_adb_pairing_failed_title)
text = when (exception) {
is ConnectException -> {
getString(R.string.cannot_connect_port)
}
is AdbInvalidPairingCodeException -> {
getString(R.string.paring_code_is_wrong)
}
is AdbKeyException -> {
getString(R.string.adb_error_key_store)
}
else -> {
exception?.let { Log.getStackTraceString(it) }
}
}
if (exception != null) {
Log.w(tag, "Pair failed", exception)
} else {
Log.w(tag, "Pair failed")
}
}
getSystemService(NotificationManager::class.java).notify(
notificationId,
Notification.Builder(this, notificationChannel)
.setColor(getColor(R.color.notification))
.setSmallIcon(R.drawable.ic_system_icon)
.setContentTitle(title)
.setContentText(text)
/*.apply {
if (!success) {
addAction(retryNotificationAction)
}
}*/
.build()
)
stopSelf()
}
private val stopNotificationAction by unsafeLazy {
val pendingIntent = PendingIntent.getService(
this,
stopRequestId,
stopIntent(this),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_IMMUTABLE
else
0
)
Notification.Action.Builder(
null,
getString(R.string.notification_adb_pairing_stop_searching),
pendingIntent
)
.build()
}
private val retryNotificationAction by unsafeLazy {
val pendingIntent = PendingIntent.getService(
this,
retryRequestId,
startIntent(this),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_IMMUTABLE
else
0
)
Notification.Action.Builder(
null,
getString(R.string.notification_adb_pairing_retry),
pendingIntent
)
.build()
}
private val replyNotificationAction by unsafeLazy {
val remoteInput = RemoteInput.Builder(remoteInputResultKey).run {
setLabel(getString(R.string.dialog_adb_pairing_paring_code))
build()
}
val pendingIntent = PendingIntent.getForegroundService(
this,
replyRequestId,
replyIntent(this, -1),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
else
PendingIntent.FLAG_UPDATE_CURRENT
)
Notification.Action.Builder(
null,
getString(R.string.notification_adb_pairing_input_paring_code),
pendingIntent
)
.addRemoteInput(remoteInput)
.build()
}
private fun replyNotificationAction(port: Int): Notification.Action {
// Ensure pending intent is created
val action = replyNotificationAction
PendingIntent.getForegroundService(
this,
replyRequestId,
replyIntent(this, port),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
else
PendingIntent.FLAG_UPDATE_CURRENT
)
return action
}
private val searchingNotification by unsafeLazy {
Notification.Builder(this, notificationChannel)
.setColor(getColor(R.color.notification))
.setSmallIcon(R.drawable.ic_system_icon)
.setContentTitle(getString(R.string.notification_adb_pairing_searching_for_service_title))
.addAction(stopNotificationAction)
.build()
}
private fun createInputNotification(port: Int): Notification {
return Notification.Builder(this, notificationChannel)
.setColor(getColor(R.color.notification))
.setContentTitle(getString(R.string.notification_adb_pairing_service_found_title))
.setSmallIcon(R.drawable.ic_system_icon)
.addAction(replyNotificationAction(port))
.build()
}
private val workingNotification by unsafeLazy {
Notification.Builder(this, notificationChannel)
.setColor(getColor(R.color.notification))
.setContentTitle(getString(R.string.notification_adb_pairing_working_title))
.setSmallIcon(R.drawable.ic_system_icon)
.build()
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/adb/AdbPairingTutorialActivity.kt
================================================
package moe.shizuku.manager.adb
import android.app.AppOpsManager
import android.app.ForegroundServiceStartNotAllowedException
import android.app.NotificationManager
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.view.isGone
import androidx.core.view.isVisible
import moe.shizuku.manager.AppConstants
import moe.shizuku.manager.app.AppBarActivity
import moe.shizuku.manager.databinding.AdbPairingTutorialActivityBinding
import rikka.compatibility.DeviceCompatibility
@RequiresApi(Build.VERSION_CODES.R)
class AdbPairingTutorialActivity : AppBarActivity() {
private lateinit var binding: AdbPairingTutorialActivityBinding
private var notificationEnabled: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val context = this
binding = AdbPairingTutorialActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
notificationEnabled = isNotificationEnabled()
if (notificationEnabled) {
startPairingService()
}
binding.apply {
syncNotificationEnabled()
if (DeviceCompatibility.isMiui()) {
miui.isVisible = true
}
developerOptions.setOnClickListener {
val intent = Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
intent.putExtra(":settings:fragment_args_key", "toggle_adb_wireless")
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
}
}
notificationOptions.setOnClickListener {
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
}
}
}
}
private fun syncNotificationEnabled() {
binding.apply {
step1.isVisible = notificationEnabled
step2.isVisible = notificationEnabled
step3.isVisible = notificationEnabled
network.isVisible = notificationEnabled
notification.isVisible = notificationEnabled
notificationDisabled.isGone = notificationEnabled
}
}
private fun isNotificationEnabled(): Boolean {
val context = this
val nm = context.getSystemService(NotificationManager::class.java)
val channel = nm.getNotificationChannel(AdbPairingService.notificationChannel)
return nm.areNotificationsEnabled() &&
(channel == null || channel.importance != NotificationManager.IMPORTANCE_NONE)
}
override fun onResume() {
super.onResume()
val newNotificationEnabled = isNotificationEnabled()
if (newNotificationEnabled != notificationEnabled) {
notificationEnabled = newNotificationEnabled
syncNotificationEnabled()
if (newNotificationEnabled) {
startPairingService()
}
}
}
private fun startPairingService() {
val intent = AdbPairingService.startIntent(this)
try {
startForegroundService(intent)
} catch (e: Throwable) {
Log.e(AppConstants.TAG, "startForegroundService", e)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
&& e is ForegroundServiceStartNotAllowedException
) {
val mode = getSystemService(AppOpsManager::class.java)
.noteOpNoThrow("android:start_foreground", android.os.Process.myUid(), packageName, null, null)
if (mode == AppOpsManager.MODE_ERRORED) {
Toast.makeText(this, "OP_START_FOREGROUND is denied. What are you doing?", Toast.LENGTH_LONG).show()
}
startService(intent)
}
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/adb/AdbProtocol.kt
================================================
package moe.shizuku.manager.adb
object AdbProtocol {
const val A_SYNC = 0x434e5953
const val A_CNXN = 0x4e584e43
const val A_AUTH = 0x48545541
const val A_OPEN = 0x4e45504f
const val A_OKAY = 0x59414b4f
const val A_CLSE = 0x45534c43
const val A_WRTE = 0x45545257
const val A_STLS = 0x534C5453
const val A_VERSION = 0x01000000
const val A_MAXDATA = 4096
const val A_STLS_VERSION = 0x01000000
const val ADB_AUTH_TOKEN = 1
const val ADB_AUTH_SIGNATURE = 2
const val ADB_AUTH_RSAPUBLICKEY = 3
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/app/AppActivity.kt
================================================
package moe.shizuku.manager.app
import android.content.res.Resources
import android.content.res.Resources.Theme
import android.graphics.Color
import android.os.Build
import androidx.annotation.RequiresApi
import moe.shizuku.manager.R
import rikka.core.res.isNight
import rikka.core.res.resolveColor
import rikka.material.app.MaterialActivity
abstract class AppActivity : MaterialActivity() {
override fun computeUserThemeKey(): String {
return ThemeHelper.getTheme(this) + ThemeHelper.isUsingSystemColor()
}
override fun onApplyUserThemeResource(theme: Theme, isDecorView: Boolean) {
if (ThemeHelper.isUsingSystemColor()) {
if (resources.configuration.isNight())
theme.applyStyle(R.style.ThemeOverlay_DynamicColors_Dark, true)
else
theme.applyStyle(R.style.ThemeOverlay_DynamicColors_Light, true)
}
theme.applyStyle(ThemeHelper.getThemeStyleRes(this), true)
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onApplyTranslucentSystemBars() {
super.onApplyTranslucentSystemBars()
val window = window
val theme = theme
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window?.decorView?.post {
if (window.decorView.rootWindowInsets?.systemWindowInsetBottom ?: 0 >= Resources.getSystem().displayMetrics.density * 40) {
window.navigationBarColor =
theme.resolveColor(android.R.attr.navigationBarColor) and 0x00ffffff or -0x20000000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
} else {
window.navigationBarColor = Color.TRANSPARENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = true
}
}
}
}
}
override fun onSupportNavigateUp(): Boolean {
if (!super.onSupportNavigateUp()) {
finish()
}
return true
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/app/AppBarActivity.kt
================================================
package moe.shizuku.manager.app
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.LayoutRes
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
import com.google.android.material.appbar.AppBarLayout
import moe.shizuku.manager.R
import rikka.core.ktx.unsafeLazy
abstract class AppBarActivity : AppActivity() {
private val rootView: ViewGroup by unsafeLazy {
findViewById<ViewGroup>(R.id.root)
}
private val toolbarContainer: AppBarLayout by unsafeLazy {
findViewById<AppBarLayout>(R.id.toolbar_container)
}
private val toolbar: Toolbar by unsafeLazy {
findViewById<Toolbar>(R.id.toolbar)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
super.setContentView(getLayoutId())
setSupportActionBar(toolbar)
}
@LayoutRes
open fun getLayoutId(): Int {
return R.layout.appbar_activity
}
override fun setContentView(layoutResID: Int) {
layoutInflater.inflate(layoutResID, rootView, true)
rootView.bringChildToFront(toolbarContainer)
}
override fun setContentView(view: View?) {
setContentView(view, FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
}
override fun setContentView(view: View?, params: ViewGroup.LayoutParams?) {
rootView.addView(view, 0, params)
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onApplyTranslucentSystemBars() {
super.onApplyTranslucentSystemBars()
window?.statusBarColor = Color.TRANSPARENT
}
}
abstract class AppBarFragmentActivity : AppBarActivity() {
override fun getLayoutId(): Int {
return R.layout.appbar_fragment_activity
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/app/ThemeHelper.java
================================================
package moe.shizuku.manager.app;
import android.content.Context;
import android.os.Build;
import androidx.annotation.StyleRes;
import moe.shizuku.manager.R;
import moe.shizuku.manager.ShizukuSettings;
import moe.shizuku.manager.utils.EnvironmentUtils;
import rikka.core.util.ResourceUtils;
public class ThemeHelper {
private static final String THEME_DEFAULT = "DEFAULT";
private static final String THEME_BLACK = "BLACK";
public static final String KEY_LIGHT_THEME = "light_theme";
public static final String KEY_BLACK_NIGHT_THEME = "black_night_theme";
public static final String KEY_USE_SYSTEM_COLOR = "use_system_color";
public static boolean isBlackNightTheme(Context context) {
return ShizukuSettings.getPreferences().getBoolean(KEY_BLACK_NIGHT_THEME, EnvironmentUtils.isWatch(context));
}
public static boolean isUsingSystemColor() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
&& ShizukuSettings.getPreferences().getBoolean(KEY_USE_SYSTEM_COLOR, true);
}
public static String getTheme(Context context) {
if (isBlackNightTheme(context)
&& ResourceUtils.isNightMode(context.getResources().getConfiguration()))
return THEME_BLACK;
return ShizukuSettings.getPreferences().getString(KEY_LIGHT_THEME, THEME_DEFAULT);
}
@StyleRes
public static int getThemeStyleRes(Context context) {
switch (getTheme(context)) {
case THEME_BLACK:
return R.style.ThemeOverlay_Black;
case THEME_DEFAULT:
default:
return R.style.ThemeOverlay;
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/authorization/AuthorizationManager.kt
================================================
package moe.shizuku.manager.authorization
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Parcel
import moe.shizuku.manager.BuildConfig
import moe.shizuku.manager.Manifest
import moe.shizuku.manager.utils.Logger.LOGGER
import moe.shizuku.manager.utils.ShizukuSystemApis
import rikka.shizuku.server.ServerConstants
import rikka.parcelablelist.ParcelableListSlice
import rikka.shizuku.Shizuku
import java.util.*
object AuthorizationManager {
private const val FLAG_ALLOWED = 1 shl 1
private const val FLAG_DENIED = 1 shl 2
private const val MASK_PERMISSION = FLAG_ALLOWED or FLAG_DENIED
private fun getApplications(userId: Int): List<PackageInfo> {
val data = Parcel.obtain()
val reply = Parcel.obtain()
return try {
data.writeInterfaceToken("moe.shizuku.server.IShizukuService")
data.writeInt(userId)
try {
Shizuku.getBinder()!!.transact(ServerConstants.BINDER_TRANSACTION_getApplications, data, reply, 0)
} catch (e: Throwable) {
throw RuntimeException(e)
}
reply.readException()
@Suppress("UNCHECKED_CAST")
(ParcelableListSlice.CREATOR.createFromParcel(reply) as ParcelableListSlice<PackageInfo>).list!!
} finally {
reply.recycle()
data.recycle()
}
}
fun getPackages(): List<PackageInfo> {
val packages: MutableList<PackageInfo> = ArrayList()
if (Shizuku.isPreV11() || (Shizuku.getVersion() == 11 && Shizuku.getServerPatchVersion() < 3)) {
val allPackages: MutableList<PackageInfo> = ArrayList()
for (user in ShizukuSystemApis.getUsers(useCache = false)) {
try {
allPackages.addAll(ShizukuSystemApis.getInstalledPackages((PackageManager.GET_META_DATA or PackageManager.GET_PERMISSIONS).toLong(), user.id))
} catch (e: Throwable) {
LOGGER.w(e, "getInstalledPackages")
}
}
for (pi in allPackages) {
if (BuildConfig.APPLICATION_ID == pi.packageName) continue
if (pi.applicationInfo?.metaData?.getBoolean("moe.shizuku.client.V3_SUPPORT") != true) continue
if (pi.requestedPermissions?.contains(Manifest.permission.API_V23) != true) continue
packages.add(pi)
}
} else {
packages.addAll(getApplications(-1))
}
return packages
}
fun granted(packageName: String, uid: Int): Boolean {
return if (Shizuku.isPreV11()) {
ShizukuSystemApis.checkPermission(Manifest.permission.API_V23, packageName, uid / 100000) == PackageManager.PERMISSION_GRANTED
} else {
(Shizuku.getFlagsForUid(uid, MASK_PERMISSION) and FLAG_ALLOWED) == FLAG_ALLOWED
}
}
fun grant(packageName: String, uid: Int) {
if (Shizuku.isPreV11()) {
ShizukuSystemApis.grantRuntimePermission(packageName, Manifest.permission.API_V23, uid / 100000)
} else {
Shizuku.updateFlagsForUid(uid, MASK_PERMISSION, FLAG_ALLOWED)
}
}
fun revoke(packageName: String, uid: Int) {
if (Shizuku.isPreV11()) {
ShizukuSystemApis.revokeRuntimePermission(packageName, Manifest.permission.API_V23, uid / 100000)
} else {
Shizuku.updateFlagsForUid(uid, MASK_PERMISSION, 0)
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/authorization/RequestPermissionActivity.kt
================================================
package moe.shizuku.manager.authorization
import android.app.Dialog
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import moe.shizuku.manager.Helps
import moe.shizuku.manager.R
import moe.shizuku.manager.app.AppActivity
import moe.shizuku.manager.databinding.ConfirmationDialogBinding
import moe.shizuku.manager.ktx.toHtml
import moe.shizuku.manager.utils.Logger.LOGGER
import rikka.core.res.resolveColor
import rikka.html.text.HtmlCompat
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuApiConstants.REQUEST_PERMISSION_REPLY_ALLOWED
import rikka.shizuku.ShizukuApiConstants.REQUEST_PERMISSION_REPLY_IS_ONETIME
import rikka.shizuku.server.ktx.workerHandler
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
class RequestPermissionActivity : AppActivity() {
private lateinit var dialog: Dialog
private fun setResult(requestUid: Int, requestPid: Int, requestCode: Int, allowed: Boolean, onetime: Boolean) {
val data = Bundle()
data.putBoolean(REQUEST_PERMISSION_REPLY_ALLOWED, allowed)
data.putBoolean(REQUEST_PERMISSION_REPLY_IS_ONETIME, onetime)
try {
Shizuku.dispatchPermissionConfirmationResult(requestUid, requestPid, requestCode, data)
} catch (e: Throwable) {
LOGGER.e("dispatchPermissionConfirmationResult")
}
}
private fun checkSelfPermission(): Boolean {
val permission = Shizuku.checkRemotePermission("android.permission.GRANT_RUNTIME_PERMISSIONS") == PackageManager.PERMISSION_GRANTED
if (permission) return true
val icon = getDrawable(R.drawable.ic_system_icon)
icon?.setTint(theme.resolveColor(android.R.attr.colorAccent))
val dialog = MaterialAlertDialogBuilder(this)
.setIcon(icon)
.setTitle("Shizuku: ${getString(R.string.app_management_dialog_adb_is_limited_title)}")
.setMessage(getString(R.string.app_management_dialog_adb_is_limited_message, Helps.ADB.get()).toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE))
.setPositiveButton(android.R.string.ok, null)
.setOnDismissListener { finish() }
.create()
dialog.setOnShowListener {
(it as AlertDialog).findViewById<TextView>(android.R.id.message)?.movementMethod = LinkMovementMethod.getInstance()
}
try {
dialog.show()
} catch (ignored: Throwable) {
}
return false
}
private fun waitForBinder(): Boolean {
val countDownLatch = CountDownLatch(1)
val listener = object : Shizuku.OnBinderReceivedListener {
override fun onBinderReceived() {
countDownLatch.countDown()
Shizuku.removeBinderReceivedListener(this)
}
}
Shizuku.addBinderReceivedListenerSticky(listener, workerHandler)
return try {
countDownLatch.await(5, TimeUnit.SECONDS)
true
} catch (e: TimeoutException) {
LOGGER.e(e, "Binder not received in 5s")
false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!waitForBinder()) {
finish()
return
}
val uid = intent.getIntExtra("uid", -1)
val pid = intent.getIntExtra("pid", -1)
val requestCode = intent.getIntExtra("requestCode", -1)
val ai = intent.getParcelableExtra<ApplicationInfo>("applicationInfo")
if (uid == -1 || pid == -1 || ai == null) {
finish()
return
}
if (!checkSelfPermission()) {
setResult(uid, pid, requestCode, allowed = false, onetime = true)
return
}
val label = try {
ai.loadLabel(packageManager)
} catch (e: Exception) {
ai.packageName
}
val binding = ConfirmationDialogBinding.inflate(layoutInflater).apply {
button1.setOnClickListener {
setResult(uid, pid, requestCode, allowed = true, onetime = false)
dialog.dismiss()
}
button3.setOnClickListener {
setResult(uid, pid, requestCode, allowed = false, onetime = true)
dialog.dismiss()
}
title.text = HtmlCompat.fromHtml(getString(R.string.permission_warning_template,
label, getString(R.string.permission_group_description)))
}
dialog = MaterialAlertDialogBuilder(this)
.setView(binding.root)
.setCancelable(false)
.setOnDismissListener { finish() }
.create()
dialog.setCanceledOnTouchOutside(false)
dialog.show()
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/AdbDialogFragment.kt
================================================
package moe.shizuku.manager.home
import android.Manifest.permission.WRITE_SECURE_SETTINGS
import android.app.Dialog
import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.MutableLiveData
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import moe.shizuku.manager.R
import moe.shizuku.manager.adb.AdbMdns
import moe.shizuku.manager.databinding.AdbDialogBinding
import moe.shizuku.manager.starter.StarterActivity
import moe.shizuku.manager.utils.EnvironmentUtils
@RequiresApi(Build.VERSION_CODES.R)
class AdbDialogFragment : DialogFragment() {
private lateinit var binding: AdbDialogBinding
private lateinit var adbMdns: AdbMdns
private val port = MutableLiveData<Int>()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
binding = AdbDialogBinding.inflate(layoutInflater)
adbMdns = AdbMdns(context, AdbMdns.TLS_CONNECT) {
port.postValue(it)
}
val port = EnvironmentUtils.getAdbTcpPort()
val builder = MaterialAlertDialogBuilder(context).apply {
setTitle(R.string.dialog_adb_discovery)
setView(binding.root)
setNegativeButton(android.R.string.cancel, null)
setPositiveButton(R.string.development_settings, null)
if (port != -1) {
setNeutralButton("$port", null)
}
}
val dialog = builder.create()
dialog.setCanceledOnTouchOutside(false)
dialog.setOnShowListener { onDialogShow(dialog) }
return dialog
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
adbMdns.stop()
}
private fun onDialogShow(dialog: AlertDialog) {
adbMdns.start()
val context = dialog.context
if (context.checkSelfPermission(WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED) {
val cr = context.contentResolver
Settings.Global.putInt(cr, "adb_wifi_enabled", 1)
Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1)
Settings.Global.putLong(cr, "adb_allowed_connection_time", 0L)
}
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val intent = Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
intent.putExtra(":settings:fragment_args_key", "toggle_adb_wireless")
try {
it.context.startActivity(intent)
} catch (_: ActivityNotFoundException) {
}
}
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener {
startAndDismiss(EnvironmentUtils.getAdbTcpPort())
}
port.observe(this) {
if (it > 65535 || it < 1) return@observe
startAndDismiss(it)
}
}
private fun startAndDismiss(port: Int) {
val host = "127.0.0.1"
val intent = Intent(context, StarterActivity::class.java).apply {
putExtra(StarterActivity.EXTRA_IS_ROOT, false)
putExtra(StarterActivity.EXTRA_HOST, host)
putExtra(StarterActivity.EXTRA_PORT, port)
}
requireContext().startActivity(intent)
dismissAllowingStateLoss()
}
fun show(fragmentManager: FragmentManager) {
if (fragmentManager.isStateSaved) return
show(fragmentManager, javaClass.simpleName)
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/AdbPairDialogFragment.kt
================================================
package moe.shizuku.manager.home
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.provider.Settings
import android.view.Gravity
import android.view.LayoutInflater
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import moe.shizuku.manager.R
import moe.shizuku.manager.ShizukuSettings
import moe.shizuku.manager.adb.*
import moe.shizuku.manager.databinding.AdbPairDialogBinding
import rikka.lifecycle.viewModels
import java.net.ConnectException
@RequiresApi(VERSION_CODES.R)
class AdbPairDialogFragment : DialogFragment() {
private lateinit var binding: AdbPairDialogBinding
private val viewModel by viewModels { ViewModel(requireContext()) }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
binding = AdbPairDialogBinding.inflate(LayoutInflater.from(context))
val builder = MaterialAlertDialogBuilder(context).apply {
setTitle(R.string.dialog_adb_pairing_title)
setView(binding.root)
setNegativeButton(android.R.string.cancel, null)
setPositiveButton(android.R.string.ok, null)
setNeutralButton(R.string.development_settings, null)
}
val dialog = builder.create()
dialog.setCanceledOnTouchOutside(false)
dialog.setOnShowListener { onDialogShow(dialog) }
return dialog
}
private fun onDialogShow(dialog: AlertDialog) {
binding.pairingCode.editText!!.doAfterTextChanged {
binding.pairingCode.error = null
}
binding.pairingCode.error = null
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isVisible = false
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener {
val intent = Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
intent.putExtra(":settings:fragment_args_key", "toggle_adb_wireless")
try {
it.context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
}
}
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val context = it.context
val port = try {
binding.port.editText!!.text.toString().toInt()
} catch (e: Exception) {
-1
}
if (port > 65535 || port < 1) {
binding.port.isVisible = true
binding.port.error = context.getString(R.string.dialog_adb_invalid_port)
return@setOnClickListener
}
val password = binding.pairingCode.editText!!.text.toString()
viewModel.run(port, password)
}
viewModel.port.observe(this) {
if (it == -1) {
dialog.setTitle(R.string.dialog_adb_pairing_discovery)
binding.text1.isVisible = true
binding.pairingCode.isVisible = false
binding.port.editText!!.setText(it.toString())
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isVisible = false
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).isVisible = true
} else {
dialog.setTitle(R.string.dialog_adb_pairing_title)
binding.text1.isVisible = false
binding.pairingCode.isVisible = true
binding.port.editText!!.setText(it.toString())
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isVisible = true
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).isVisible = false
}
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val context = requireContext()
val inMultiScreenOrDisplay = (requireActivity().isInMultiWindowMode
|| (requireActivity().window?.decorView?.display?.displayId ?: -1) > 0)
binding.text1.isVisible = inMultiScreenOrDisplay
binding.text2.isVisible = !inMultiScreenOrDisplay
if (inMultiScreenOrDisplay) {
dialog?.setTitle(R.string.dialog_adb_pairing_discovery)
} else {
dialog?.setTitle(R.string.dialog_adb_pairing_title)
}
viewModel.result.observe(this) {
if (it == null) {
dismissAllowingStateLoss()
} else {
when (it) {
is ConnectException -> {
binding.port.error = context.getString(R.string.cannot_connect_port)
}
is AdbInvalidPairingCodeException -> {
binding.pairingCode.error = context.getString(R.string.paring_code_is_wrong)
}
is AdbKeyException -> {
Toast.makeText(context, context.getString(R.string.adb_error_key_store), Toast.LENGTH_LONG)
.apply { setGravity(Gravity.CENTER, 0, 0) }.show()
}
}
}
}
}
fun show(fragmentManager: FragmentManager) {
if (fragmentManager.isStateSaved) return
show(fragmentManager, javaClass.simpleName)
}
override fun getDialog(): AlertDialog? {
return super.getDialog() as AlertDialog?
}
}
@SuppressLint("NewApi")
private class ViewModel(context: Context) : androidx.lifecycle.ViewModel() {
private val _result = MutableLiveData<Throwable?>()
val result = _result as LiveData<Throwable?>
private val _port = MutableLiveData<Int>()
val port = _port as LiveData<Int>
private val adbMdns: AdbMdns = AdbMdns(context, AdbMdns.TLS_PAIRING) {
_port.postValue(it)
}
init {
adbMdns.start()
}
fun run(port: Int, password: String) {
GlobalScope.launch(Dispatchers.IO) {
val host = "127.0.0.1"
val key = try {
AdbKey(PreferenceAdbKeyStore(ShizukuSettings.getPreferences()), "shizuku")
} catch (e: Throwable) {
e.printStackTrace()
_result.postValue(AdbKeyException(e))
return@launch
}
AdbPairingClient(host, port, password, key).runCatching {
start()
}.onFailure {
_result.postValue(it)
it.printStackTrace()
}.onSuccess {
if (it) {
_result.postValue(null)
}
}
}
}
override fun onCleared() {
super.onCleared()
adbMdns.stop()
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/AdbPermissionLimitedViewHolder.kt
================================================
package moe.shizuku.manager.home
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import moe.shizuku.manager.Helps
import moe.shizuku.manager.databinding.HomeExtraStepRequiredBinding
import moe.shizuku.manager.databinding.HomeItemContainerBinding
import moe.shizuku.manager.utils.CustomTabsHelper
import rikka.recyclerview.BaseViewHolder
import rikka.recyclerview.BaseViewHolder.Creator
class AdbPermissionLimitedViewHolder(binding: HomeExtraStepRequiredBinding, root: View) : BaseViewHolder<Any?>(root) {
companion object {
val CREATOR = Creator<Any> { inflater: LayoutInflater, parent: ViewGroup? ->
val outer = HomeItemContainerBinding.inflate(inflater, parent, false)
val inner = HomeExtraStepRequiredBinding.inflate(inflater, outer.root, true)
AdbPermissionLimitedViewHolder(inner, outer.root)
}
}
init {
binding.button1.setOnClickListener { v: View -> CustomTabsHelper.launchUrlOrCopy(v.context, Helps.ADB_PERMISSION.get()) }
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/HomeActivity.kt
================================================
package moe.shizuku.manager.home
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Process
import android.text.method.LinkMovementMethod
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import moe.shizuku.manager.R
import moe.shizuku.manager.ShizukuSettings
import moe.shizuku.manager.app.AppBarActivity
import moe.shizuku.manager.databinding.AboutDialogBinding
import moe.shizuku.manager.databinding.HomeActivityBinding
import moe.shizuku.manager.ktx.toHtml
import moe.shizuku.manager.management.appsViewModel
import moe.shizuku.manager.settings.SettingsActivity
import moe.shizuku.manager.utils.AppIconCache
import rikka.core.ktx.unsafeLazy
import rikka.lifecycle.Status
import rikka.lifecycle.viewModels
import rikka.recyclerview.addEdgeSpacing
import rikka.recyclerview.addItemSpacing
import rikka.recyclerview.fixEdgeEffect
import rikka.shizuku.Shizuku
abstract class HomeActivity : AppBarActivity() {
private val binderReceivedListener = Shizuku.OnBinderReceivedListener {
checkServerStatus()
appsModel.load()
}
private val binderDeadListener = Shizuku.OnBinderDeadListener {
checkServerStatus()
}
private val homeModel by viewModels { HomeViewModel() }
private val appsModel by appsViewModel()
private val adapter by unsafeLazy { HomeAdapter(homeModel, appsModel) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = HomeActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
homeModel.serviceStatus.observe(this) {
if (it.status == Status.SUCCESS) {
val status = homeModel.serviceStatus.value?.data ?: return@observe
adapter.updateData()
ShizukuSettings.setLastLaunchMode(if (status.uid == 0) ShizukuSettings.LaunchMethod.ROOT else ShizukuSettings.LaunchMethod.ADB)
}
}
appsModel.grantedCount.observe(this) {
if (it.status == Status.SUCCESS) {
adapter.updateData()
}
}
val recyclerView = binding.list
recyclerView.adapter = adapter
recyclerView.fixEdgeEffect()
recyclerView.addItemSpacing(top = 4f, bottom = 4f, unit = TypedValue.COMPLEX_UNIT_DIP)
recyclerView.addEdgeSpacing(top = 4f, bottom = 4f, left = 16f, right = 16f, unit = TypedValue.COMPLEX_UNIT_DIP)
Shizuku.addBinderReceivedListenerSticky(binderReceivedListener)
Shizuku.addBinderDeadListener(binderDeadListener)
}
override fun onResume() {
super.onResume()
checkServerStatus()
}
private fun checkServerStatus() {
homeModel.reload()
}
override fun onDestroy() {
super.onDestroy()
Shizuku.removeBinderReceivedListener(binderReceivedListener)
Shizuku.removeBinderDeadListener(binderDeadListener)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_about -> {
val binding = AboutDialogBinding.inflate(LayoutInflater.from(this), null, false)
binding.sourceCode.movementMethod = LinkMovementMethod.getInstance()
binding.sourceCode.text = getString(
R.string.about_view_source_code,
"<b><a href=\"https://github.com/RikkaApps/Shizuku\">GitHub</a></b>"
).toHtml()
binding.icon.setImageBitmap(
AppIconCache.getOrLoadBitmap(
this,
applicationInfo,
Process.myUid() / 100000,
resources.getDimensionPixelOffset(R.dimen.default_app_icon_size)
)
)
binding.versionName.text = packageManager.getPackageInfo(packageName, 0).versionName
MaterialAlertDialogBuilder(this)
.setView(binding.root)
.show()
true
}
R.id.action_stop -> {
if (!Shizuku.pingBinder()) {
return true
}
MaterialAlertDialogBuilder(this)
.setMessage(R.string.dialog_stop_message)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
try {
Shizuku.exit()
} catch (e: Throwable) {
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
true
}
R.id.action_settings -> {
startActivity(Intent(this, SettingsActivity::class.java))
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/HomeAdapter.kt
================================================
package moe.shizuku.manager.home
import android.os.Build
import moe.shizuku.manager.management.AppsViewModel
import moe.shizuku.manager.utils.EnvironmentUtils
import moe.shizuku.manager.utils.UserHandleCompat
import rikka.recyclerview.IdBasedRecyclerViewAdapter
import rikka.recyclerview.IndexCreatorPool
import rikka.shizuku.Shizuku
class HomeAdapter(private val homeModel: HomeViewModel, private val appsModel: AppsViewModel) :
IdBasedRecyclerViewAdapter(ArrayList()) {
init {
updateData()
setHasStableIds(true)
}
companion object {
private const val ID_STATUS = 0L
private const val ID_APPS = 1L
private const val ID_TERMINAL = 2L
private const val ID_START_ROOT = 3L
private const val ID_START_WADB = 4L
private const val ID_START_ADB = 5L
private const val ID_LEARN_MORE = 6L
private const val ID_ADB_PERMISSION_LIMITED = 7L
}
override fun onCreateCreatorPool(): IndexCreatorPool {
return IndexCreatorPool()
}
fun updateData() {
val status = homeModel.serviceStatus.value?.data ?: return
val grantedCount = appsModel.grantedCount.value?.data ?: 0
val adbPermission = status.permission
val running = status.isRunning
val isPrimaryUser = UserHandleCompat.myUserId() == 0
clear()
addItem(ServerStatusViewHolder.CREATOR, status, ID_STATUS)
if (adbPermission) {
addItem(ManageAppsViewHolder.CREATOR, status to grantedCount, ID_APPS)
addItem(TerminalViewHolder.CREATOR, status, ID_TERMINAL)
}
if (running && !adbPermission) {
addItem(AdbPermissionLimitedViewHolder.CREATOR, status, ID_ADB_PERMISSION_LIMITED)
}
if (isPrimaryUser) {
val root = EnvironmentUtils.isRooted()
val rootRestart = running && status.uid == 0
if (root) {
addItem(StartRootViewHolder.CREATOR, rootRestart, ID_START_ROOT)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || EnvironmentUtils.getAdbTcpPort() > 0) {
addItem(StartWirelessAdbViewHolder.CREATOR, null, ID_START_WADB)
}
addItem(StartAdbViewHolder.CREATOR, null, ID_START_ADB)
if (!root) {
addItem(StartRootViewHolder.CREATOR, rootRestart, ID_START_ROOT)
}
}
addItem(LearnMoreViewHolder.CREATOR, null, ID_LEARN_MORE)
notifyDataSetChanged()
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/HomeViewModel.kt
================================================
package moe.shizuku.manager.home
import android.content.pm.PackageManager
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import moe.shizuku.manager.BuildConfig
import moe.shizuku.manager.Manifest
import moe.shizuku.manager.model.ServiceStatus
import moe.shizuku.manager.utils.Logger.LOGGER
import moe.shizuku.manager.utils.ShizukuSystemApis
import rikka.lifecycle.Resource
import rikka.shizuku.Shizuku
class HomeViewModel : ViewModel() {
private val _serviceStatus = MutableLiveData<Resource<ServiceStatus>>()
val serviceStatus = _serviceStatus as LiveData<Resource<ServiceStatus>>
private fun load(): ServiceStatus {
if (!Shizuku.pingBinder()) {
return ServiceStatus()
}
val uid = Shizuku.getUid()
val apiVersion = Shizuku.getVersion()
val patchVersion = Shizuku.getServerPatchVersion().let { if (it < 0) 0 else it }
val seContext = if (apiVersion >= 6) {
try {
Shizuku.getSELinuxContext()
} catch (tr: Throwable) {
LOGGER.w(tr, "getSELinuxContext")
null
}
} else null
val permissionTest =
Shizuku.checkRemotePermission("android.permission.GRANT_RUNTIME_PERMISSIONS") == PackageManager.PERMISSION_GRANTED
// Before a526d6bb, server will not exit on uninstall, manager installed later will get not permission
// Run a random remote transaction here, report no permission as not running
ShizukuSystemApis.checkPermission(Manifest.permission.API_V23, BuildConfig.APPLICATION_ID, 0)
return ServiceStatus(uid, apiVersion, patchVersion, seContext, permissionTest)
}
fun reload() {
viewModelScope.launch(Dispatchers.IO) {
try {
val status = load()
_serviceStatus.postValue(Resource.success(status))
} catch (e: CancellationException) {
} catch (e: Throwable) {
_serviceStatus.postValue(Resource.error(e, ServiceStatus()))
}
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/LearnMoreViewHolder.kt
================================================
package moe.shizuku.manager.home
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import moe.shizuku.manager.Helps
import moe.shizuku.manager.databinding.HomeItemContainerBinding
import moe.shizuku.manager.databinding.HomeLearnMoreBinding
import moe.shizuku.manager.utils.CustomTabsHelper
import rikka.recyclerview.BaseViewHolder
import rikka.recyclerview.BaseViewHolder.Creator
class LearnMoreViewHolder(binding: HomeLearnMoreBinding, root: View) : BaseViewHolder<Any?>(root) {
companion object {
val CREATOR = Creator<Any> { inflater: LayoutInflater, parent: ViewGroup? ->
val outer = HomeItemContainerBinding.inflate(inflater, parent, false)
val inner = HomeLearnMoreBinding.inflate(inflater, outer.root, true)
LearnMoreViewHolder(inner, outer.root)
}
}
init {
root.setOnClickListener { v: View -> CustomTabsHelper.launchUrlOrCopy(v.context, Helps.HOME.get()) }
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/ManageAppsViewHolder.kt
================================================
package moe.shizuku.manager.home
import android.content.Intent
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import moe.shizuku.manager.Helps
import moe.shizuku.manager.R
import moe.shizuku.manager.databinding.HomeItemContainerBinding
import moe.shizuku.manager.databinding.HomeManageAppsItemBinding
import moe.shizuku.manager.ktx.toHtml
import moe.shizuku.manager.management.ApplicationManagementActivity
import moe.shizuku.manager.model.ServiceStatus
import rikka.html.text.HtmlCompat
import rikka.recyclerview.BaseViewHolder
import rikka.recyclerview.BaseViewHolder.Creator
class ManageAppsViewHolder(private val binding: HomeManageAppsItemBinding, root: View) :
BaseViewHolder<Pair<ServiceStatus, Int>>(root), View.OnClickListener {
companion object {
val CREATOR = Creator<Pair<ServiceStatus, Int>> { inflater: LayoutInflater, parent: ViewGroup? ->
val outer = HomeItemContainerBinding.inflate(inflater, parent, false)
val inner = HomeManageAppsItemBinding.inflate(inflater, outer.root, true)
ManageAppsViewHolder(inner, outer.root)
}
}
init {
root.setOnClickListener(this)
}
private inline val title get() = binding.text1
private inline val summary get() = binding.text2
override fun onBind() {
val context = itemView.context
if (!data.first.isRunning) {
itemView.isEnabled = false
title.setText(R.string.home_app_management_title)
summary.text = context.getString(
R.string.home_status_service_not_running,
context.getString(R.string.app_name)
)
} else {
itemView.isEnabled = true
title.text = context.resources.getQuantityString(
R.plurals.home_app_management_authorized_apps_count,
data.second,
data.second
)
summary.text = context.getString(R.string.home_app_management_view_authorized_apps)
}
}
override fun onClick(v: View) {
v.context.startActivity(Intent(v.context, ApplicationManagementActivity::class.java))
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/ServerStatusViewHolder.kt
================================================
package moe.shizuku.manager.home
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import moe.shizuku.manager.R
import moe.shizuku.manager.databinding.HomeItemContainerBinding
import moe.shizuku.manager.databinding.HomeServerStatusBinding
import moe.shizuku.manager.model.ServiceStatus
import rikka.html.text.HtmlCompat
import rikka.html.text.toHtml
import rikka.recyclerview.BaseViewHolder
import rikka.recyclerview.BaseViewHolder.Creator
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuApiConstants
import rikka.shizuku.server.ServerConstants
class ServerStatusViewHolder(private val binding: HomeServerStatusBinding, root: View) :
BaseViewHolder<ServiceStatus>(root) {
companion object {
val CREATOR = Creator<ServiceStatus> { inflater: LayoutInflater, parent: ViewGroup? ->
val outer = HomeItemContainerBinding.inflate(inflater, parent, false)
val inner = HomeServerStatusBinding.inflate(inflater, outer.root, true)
ServerStatusViewHolder(inner, outer.root)
}
}
private inline val textView get() = binding.text1
private inline val summaryView get() = binding.text2
private inline val iconView get() = binding.icon
override fun onBind() {
val context = itemView.context
val status = data
val ok = status.isRunning
val isRoot = status.uid == 0
val apiVersion = status.apiVersion
val patchVersion = status.patchVersion
if (ok) {
iconView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_server_ok_24dp))
} else {
iconView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_server_error_24dp))
}
val user = if (isRoot) "root" else "adb"
val title = if (ok) {
context.getString(R.string.home_status_service_is_running, context.getString(R.string.app_name))
} else {
context.getString(R.string.home_status_service_not_running, context.getString(R.string.app_name))
}
val summary = if (ok) {
if (apiVersion != Shizuku.getLatestServiceVersion() || status.patchVersion != ShizukuApiConstants.SERVER_PATCH_VERSION) {
context.getString(
R.string.home_status_service_version_update, user,
"${apiVersion}.${patchVersion}",
"${Shizuku.getLatestServiceVersion()}.${ShizukuApiConstants.SERVER_PATCH_VERSION}"
)
} else {
context.getString(R.string.home_status_service_version, user, "${apiVersion}.${patchVersion}")
}
} else {
""
}
textView.text = title.toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE)
summaryView.text = summary.toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE)
if (TextUtils.isEmpty(summaryView.text)) {
summaryView.visibility = View.GONE
} else {
summaryView.visibility = View.VISIBLE
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/StartAdbViewHolder.kt
================================================
package moe.shizuku.manager.home
import android.content.Intent
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import moe.shizuku.manager.Helps
import moe.shizuku.manager.R
import moe.shizuku.manager.databinding.HomeItemContainerBinding
import moe.shizuku.manager.databinding.HomeStartAdbBinding
import moe.shizuku.manager.ktx.toHtml
import moe.shizuku.manager.starter.Starter
import rikka.core.util.ClipboardUtils
import rikka.html.text.HtmlCompat
import rikka.recyclerview.BaseViewHolder
import rikka.recyclerview.BaseViewHolder.Creator
class StartAdbViewHolder(binding: HomeStartAdbBinding, root: View) : BaseViewHolder<Any?>(root) {
companion object {
val CREATOR = Creator<Any> { inflater: LayoutInflater, parent: ViewGroup? ->
val outer = HomeItemContainerBinding.inflate(inflater, parent, false)
val inner = HomeStartAdbBinding.inflate(inflater, outer.root, true)
StartAdbViewHolder(inner, outer.root)
}
}
init {
binding.button1.setOnClickListener { v: View ->
val context = v.context
MaterialAlertDialogBuilder(context)
.setTitle(R.string.home_adb_button_view_command)
.setMessage(
HtmlCompat.fromHtml(
context.getString(
R.string.home_adb_dialog_view_command_message,
Starter.adbCommand
)
)
)
.setPositiveButton(R.string.home_adb_dialog_view_command_copy_button) { _, _ ->
if (ClipboardUtils.put(context, Starter.adbCommand)) {
Toast.makeText(
context,
context.getString(R.string.toast_copied_to_clipboard, Starter.adbCommand),
Toast.LENGTH_SHORT
).show()
}
}
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.home_adb_dialog_view_command_button_send) { _, _ ->
var intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_TEXT, Starter.adbCommand)
intent = Intent.createChooser(
intent,
context.getString(R.string.home_adb_dialog_view_command_button_send)
)
context.startActivity(intent)
}
.show()
}
binding.text1.movementMethod = LinkMovementMethod.getInstance()
binding.text1.text = context.getString(R.string.home_adb_description, Helps.ADB.get())
.toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE)
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/StartRootViewHolder.kt
================================================
package moe.shizuku.manager.home
import android.content.Intent
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import moe.shizuku.manager.Helps
import moe.shizuku.manager.R
import moe.shizuku.manager.databinding.HomeItemContainerBinding
import moe.shizuku.manager.databinding.HomeStartRootBinding
import moe.shizuku.manager.ktx.toHtml
import moe.shizuku.manager.starter.StarterActivity
import rikka.html.text.HtmlCompat
import rikka.recyclerview.BaseViewHolder
import rikka.recyclerview.BaseViewHolder.Creator
import rikka.shizuku.Shizuku
class StartRootViewHolder(private val binding: HomeStartRootBinding, root: View) :
BaseViewHolder<Boolean>(root) {
companion object {
val CREATOR = Creator<Boolean> { inflater: LayoutInflater, parent: ViewGroup? ->
val outer = HomeItemContainerBinding.inflate(inflater, parent, false)
val inner = HomeStartRootBinding.inflate(inflater, outer.root, true)
StartRootViewHolder(inner, outer.root)
}
}
private inline val start get() = binding.button1
private inline val restart get() = binding.button2
private var alertDialog: AlertDialog? = null
init {
val listener = View.OnClickListener { v: View -> onStartClicked(v) }
start.setOnClickListener(listener)
restart.setOnClickListener(listener)
binding.text1.movementMethod = LinkMovementMethod.getInstance()
}
private fun onStartClicked(v: View) {
val context = v.context
val intent = Intent(context, StarterActivity::class.java).apply {
putExtra(StarterActivity.EXTRA_IS_ROOT, true)
}
context.startActivity(intent)
}
override fun onBind() {
start.isEnabled = true
restart.isEnabled = true
if (data!!) {
start.visibility = View.GONE
restart.visibility = View.VISIBLE
} else {
start.visibility = View.VISIBLE
restart.visibility = View.GONE
}
val sb = StringBuilder()
.append(
context.getString(
R.string.home_root_description,
"<b><a href=\"https://dontkillmyapp.com/\">Don\'t kill my app!</a></b>"
)
)
if (Shizuku.pingBinder()) {
sb.append("<p>").append(
context.getString(
R.string.home_root_description_sui,
"<b><a href=\"${Helps.SUI.get()}\">Sui</a></b>",
"Sui"
)
)
}
binding.text1.text = sb.toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE)
}
override fun onRecycle() {
super.onRecycle()
alertDialog = null
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/StartWirelessAdbViewHolder.kt
================================================
package moe.shizuku.manager.home
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.SystemProperties
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import moe.shizuku.manager.Helps
import moe.shizuku.manager.R
import moe.shizuku.manager.adb.AdbPairingTutorialActivity
import moe.shizuku.manager.databinding.HomeItemContainerBinding
import moe.shizuku.manager.databinding.HomeStartWirelessAdbBinding
import moe.shizuku.manager.ktx.toHtml
import moe.shizuku.manager.starter.StarterActivity
import moe.shizuku.manager.utils.CustomTabsHelper
import moe.shizuku.manager.utils.EnvironmentUtils
import rikka.core.content.asActivity
import rikka.html.text.HtmlCompat
import rikka.recyclerview.BaseViewHolder
import rikka.recyclerview.BaseViewHolder.Creator
import java.net.Inet4Address
class StartWirelessAdbViewHolder(binding: HomeStartWirelessAdbBinding, root: View) :
BaseViewHolder<Any?>(root) {
companion object {
val CREATOR = Creator<Any> { inflater: LayoutInflater, parent: ViewGroup? ->
val outer = HomeItemContainerBinding.inflate(inflater, parent, false)
val inner = HomeStartWirelessAdbBinding.inflate(inflater, outer.root, true)
StartWirelessAdbViewHolder(inner, outer.root)
}
}
init {
binding.button1.setOnClickListener { v: View ->
onAdbClicked(v.context)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
binding.button3.setOnClickListener { v: View ->
CustomTabsHelper.launchUrlOrCopy(v.context, Helps.ADB_ANDROID11.get())
}
binding.button2.setOnClickListener { v: View ->
onPairClicked(v.context)
}
binding.text1.movementMethod = LinkMovementMethod.getInstance()
binding.text1.text = context.getString(R.string.home_wireless_adb_description)
.toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE)
} else {
binding.text1.text = context.getString(R.string.home_wireless_adb_description_pre_11)
.toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE)
binding.button2.isVisible = false
binding.button3.isVisible = false
}
}
override fun onBind(payloads: MutableList<Any>) {
super.onBind(payloads)
}
private fun onAdbClicked(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
AdbDialogFragment().show(context.asActivity<FragmentActivity>().supportFragmentManager)
return
}
val port = EnvironmentUtils.getAdbTcpPort()
if (port > 0) {
val host = "127.0.0.1"
val intent = Intent(context, StarterActivity::class.java).apply {
putExtra(StarterActivity.EXTRA_IS_ROOT, false)
putExtra(StarterActivity.EXTRA_HOST, host)
putExtra(StarterActivity.EXTRA_PORT, port)
}
context.startActivity(intent)
} else {
WadbNotEnabledDialogFragment().show(context.asActivity<FragmentActivity>().supportFragmentManager)
}
}
@RequiresApi(Build.VERSION_CODES.R)
private fun onPairClicked(context: Context) {
if ((context.display?.displayId ?: -1) > 0) {
// Running in a multi-display environment (e.g., Windows Subsystem for Android),
// pairing dialog can be displayed simultaneously with Shizuku.
// Input from notification is harder to use under this situation.
AdbPairDialogFragment().show(context.asActivity<FragmentActivity>().supportFragmentManager)
} else {
context.startActivity(Intent(context, AdbPairingTutorialActivity::class.java))
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/TerminalViewHolder.kt
================================================
package moe.shizuku.manager.home
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import moe.shizuku.manager.R
import moe.shizuku.manager.databinding.HomeItemContainerBinding
import moe.shizuku.manager.databinding.HomeTerminalBinding
import moe.shizuku.manager.model.ServiceStatus
import moe.shizuku.manager.shell.ShellTutorialActivity
import rikka.recyclerview.BaseViewHolder
import rikka.recyclerview.BaseViewHolder.Creator
class TerminalViewHolder(private val binding: HomeTerminalBinding, private val root: View) :
BaseViewHolder<ServiceStatus>(root),
View.OnClickListener {
companion object {
val CREATOR = Creator<ServiceStatus> { inflater: LayoutInflater, parent: ViewGroup? ->
val outer = HomeItemContainerBinding.inflate(inflater, parent, false)
val inner = HomeTerminalBinding.inflate(inflater, outer.root, true)
TerminalViewHolder(inner, outer.root)
}
}
init {
root.setOnClickListener(this)
}
private inline val summary get() = binding.text2
override fun onBind() {
val context = itemView.context
if (!data.isRunning) {
root.isEnabled = false
summary.text =
context.getString(R.string.home_status_service_not_running, context.getString(R.string.app_name))
} else {
root.isEnabled = true
summary.text = context.getString(R.string.home_terminal_description)
}
}
override fun onClick(v: View) {
v.context.startActivity(Intent(v.context, ShellTutorialActivity::class.java))
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/WadbNotEnabledDialogFragment.kt
================================================
package moe.shizuku.manager.home
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import moe.shizuku.manager.R
class WadbNotEnabledDialogFragment :DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
return MaterialAlertDialogBuilder(context)
.setMessage(R.string.dialog_wireless_adb_not_enabled)
.setPositiveButton(android.R.string.ok, null)
.create()
}
fun show(fragmentManager: FragmentManager) {
if (fragmentManager.isStateSaved) return
show(fragmentManager, javaClass.simpleName)
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/ktx/Context.kt
================================================
package moe.shizuku.manager.ktx
import android.content.Context
import android.os.Build
import android.os.UserManager
import moe.shizuku.manager.ShizukuApplication
val Context.application: ShizukuApplication
get() {
return applicationContext as ShizukuApplication
}
fun Context.createDeviceProtectedStorageContextCompat(): Context {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
createDeviceProtectedStorageContext()
} else {
this
}
}
fun Context.createDeviceProtectedStorageContextCompatWhenLocked(): Context {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && getSystemService(UserManager::class.java)?.isUserUnlocked != true) {
createDeviceProtectedStorageContext()
} else {
this
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/ktx/Log.kt
================================================
@file:Suppress("NOTHING_TO_INLINE")
package moe.shizuku.manager.ktx
import android.util.Log
inline val <reified T> T.TAG: String
get() =
T::class.java.simpleName.let {
if (it.isBlank()) throw IllegalStateException("tag is empty")
if (it.length > 23) it.substring(0, 23) else it
}
inline fun <reified T> T.logv(message: String, throwable: Throwable? = null) = logv(TAG, message, throwable)
inline fun <reified T> T.logi(message: String, throwable: Throwable? = null) = logi(TAG, message, throwable)
inline fun <reified T> T.logw(message: String, throwable: Throwable? = null) = logw(TAG, message, throwable)
inline fun <reified T> T.logd(message: String, throwable: Throwable? = null) = logd(TAG, message, throwable)
inline fun <reified T> T.loge(message: String, throwable: Throwable? = null) = loge(TAG, message, throwable)
inline fun <reified T> T.logv(tag: String, message: String, throwable: Throwable? = null) = Log.v(tag, message, throwable)
inline fun <reified T> T.logi(tag: String, message: String, throwable: Throwable? = null) = Log.i(tag, message, throwable)
inline fun <reified T> T.logw(tag: String, message: String, throwable: Throwable? = null) = Log.w(tag, message, throwable)
inline fun <reified T> T.logd(tag: String, message: String, throwable: Throwable? = null) = Log.d(tag, message, throwable)
inline fun <reified T> T.loge(tag: String, message: String, throwable: Throwable? = null) = Log.e(tag, message, throwable)
================================================
FILE: manager/src/main/java/moe/shizuku/manager/ktx/PackageManager.kt
================================================
package moe.shizuku.manager.ktx
import android.content.ComponentName
import android.content.pm.PackageManager
fun PackageManager.setComponentEnabled(componentName: ComponentName, enabled: Boolean) {
val oldState = getComponentEnabledSetting(componentName)
val newState = if (enabled) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED
if (newState != oldState) {
val flags = PackageManager.DONT_KILL_APP
setComponentEnabledSetting(componentName, newState, flags)
}
}
fun PackageManager.isComponentEnabled(componentName: ComponentName, defaultValue: Boolean = true): Boolean {
return when (getComponentEnabledSetting(componentName)) {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false
PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> defaultValue
else -> false
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/ktx/RecyclerView.kt
================================================
package moe.shizuku.manager.ktx
import android.graphics.Canvas
import android.widget.EdgeEffect
import androidx.recyclerview.widget.RecyclerView
class FixedAlwaysClipToPaddingEdgeEffectFactory(
private val paddingLeft: Int,
private val paddingTop: Int,
private val paddingRight: Int,
private val paddingBottom: Int
) : RecyclerView.EdgeEffectFactory() {
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
return object : EdgeEffect(view.context) {
private var ensureSize = false
private fun ensureSize() {
if (ensureSize) return
ensureSize = true
when (direction) {
DIRECTION_LEFT -> {
setSize(view.measuredHeight - paddingTop - paddingBottom,
view.measuredWidth - paddingLeft - paddingRight)
}
DIRECTION_TOP -> {
setSize(view.measuredWidth - paddingLeft - paddingRight,
view.measuredHeight - paddingTop - paddingBottom)
}
DIRECTION_RIGHT -> {
setSize(view.measuredHeight - paddingTop - paddingBottom,
view.measuredWidth - paddingLeft - paddingRight)
}
DIRECTION_BOTTOM -> {
setSize(view.measuredWidth - paddingLeft - paddingRight,
view.measuredHeight - paddingTop - paddingBottom)
}
}
}
override fun draw(c: Canvas): Boolean {
ensureSize()
val restore = c.save()
when (direction) {
DIRECTION_LEFT -> {
c.translate(paddingBottom.toFloat(), 0f)
}
DIRECTION_TOP -> {
c.translate(paddingLeft.toFloat(), paddingTop.toFloat())
}
DIRECTION_RIGHT -> {
c.translate(-paddingTop.toFloat(), 0f)
}
DIRECTION_BOTTOM -> {
c.translate(paddingRight.toFloat(), paddingBottom.toFloat())
}
}
val res = super.draw(c)
c.restoreToCount(restore)
return res
}
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/ktx/String.kt
================================================
package moe.shizuku.manager.ktx
import android.text.Spanned
import rikka.html.text.HtmlCompat
fun CharSequence.toHtml(flags: Int = 0): Spanned {
return HtmlCompat.fromHtml(this.toString(), flags)
}
fun CharSequence.toHtml(tagHandler: HtmlCompat.TagHandler): Spanned {
return HtmlCompat.fromHtml(this.toString(), null, tagHandler)
}
fun CharSequence.toHtml(flags: Int, tagHandler: HtmlCompat.TagHandler): Spanned {
return HtmlCompat.fromHtml(this.toString(), flags, null, tagHandler)
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/legacy/LegacyIsNotSupportedActivity.kt
================================================
package moe.shizuku.manager.legacy
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import moe.shizuku.manager.MainActivity
import moe.shizuku.manager.R
import moe.shizuku.manager.app.AppActivity
import moe.shizuku.manager.ktx.toHtml
import rikka.html.text.HtmlCompat
class LegacyIsNotSupportedActivity : AppActivity() {
companion object {
/**
* Activity result: user denied request (only API pre-23).
*/
private inline val RESULT_CANCELED get() = Activity.RESULT_CANCELED
/**
* Activity result: error, such as manager app itself not authorized.
*/
private const val RESULT_ERROR = 1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callingComponent = callingActivity
if (callingComponent == null) {
setResult(RESULT_CANCELED)
finish()
return
}
val ai = try {
packageManager.getApplicationInfo(callingComponent.packageName, PackageManager.GET_META_DATA)
} catch (e: Throwable) {
finish()
return
}
val label = try {
ai.loadLabel(packageManager)
} catch (e: Exception) {
ai.packageName
}
val v3Support = ai.metaData?.getBoolean("moe.shizuku.client.V3_SUPPORT") == true
if (v3Support) {
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.dialog_requesting_legacy_title, label))
.setMessage(getString(R.string.dialog_requesting_legacy_message, label).toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE))
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(R.string.dialog_requesting_legacy_button_open_shizuku) { _, _ ->
startActivity(Intent(this, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
.setOnDismissListener {
setResult(RESULT_ERROR)
finish()
}
.setCancelable(false)
.show()
} else {
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.dialog_legacy_not_support_title, label))
.setMessage(getString(R.string.dialog_legacy_not_support_message, label).toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE))
.setPositiveButton(android.R.string.ok, null)
.setOnDismissListener {
setResult(RESULT_ERROR)
finish()
}
.setCancelable(false)
.show()
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/legacy/ShellRequestHandlerActivity.kt
================================================
package moe.shizuku.manager.legacy
import android.os.Bundle
import android.widget.Toast
import moe.shizuku.manager.app.AppActivity
import moe.shizuku.manager.shell.ShellBinderRequestHandler
class ShellRequestHandlerActivity : AppActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ShellBinderRequestHandler.handleRequest(this, intent)
finish()
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/management/AppViewHolder.kt
================================================
package moe.shizuku.manager.management
import android.content.pm.PackageInfo
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.content.res.AppCompatResources
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Job
import moe.shizuku.manager.Helps
import moe.shizuku.manager.R
import moe.shizuku.manager.authorization.AuthorizationManager
import moe.shizuku.manager.databinding.AppListItemBinding
import moe.shizuku.manager.ktx.toHtml
import moe.shizuku.manager.utils.AppIconCache
import moe.shizuku.manager.utils.ShizukuSystemApis
import moe.shizuku.manager.utils.UserHandleCompat
import rikka.html.text.HtmlCompat
import rikka.recyclerview.BaseViewHolder
import rikka.recyclerview.BaseViewHolder.Creator
import rikka.shizuku.Shizuku
class AppViewHolder(private val binding: AppListItemBinding) : BaseViewHolder<PackageInfo>(binding.root), View.OnClickListener {
companion object {
@JvmField
val CREATOR = Creator<PackageInfo> { inflater: LayoutInflater, parent: ViewGroup? -> AppViewHolder(AppListItemBinding.inflate(inflater, parent, false)) }
}
private val icon get() = binding.icon
private val name get() = binding.title
private val pkg get() = binding.summary
private val switchWidget get() = binding.switchWidget
private val root get() = binding.requiresRoot
init {
itemView.filterTouchesWhenObscured = true
itemView.setOnClickListener(this)
}
private inline val packageName get() = data.packageName
private inline val ai get() = data.applicationInfo!!
private inline val uid get() = ai.uid
private var loadIconJob: Job? = null
override fun onClick(v: View) {
val context = v.context
try {
if (AuthorizationManager.granted(packageName, uid)) {
AuthorizationManager.revoke(packageName, uid)
} else {
AuthorizationManager.grant(packageName, uid)
}
} catch (e: SecurityException) {
val uid = try {
Shizuku.getUid()
} catch (ex: Throwable) {
return
}
if (uid != 0) {
val dialog = MaterialAlertDialogBuilder(context)
.setTitle(R.string.app_management_dialog_adb_is_limited_title)
.setMessage(context.getString(R.string.app_management_dialog_adb_is_limited_message, Helps.ADB.get()).toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE))
.setPositiveButton(android.R.string.ok, null)
.create()
dialog.setOnShowListener {
(it as AlertDialog).findViewById<TextView>(android.R.id.message)?.movementMethod = LinkMovementMethod.getInstance()
}
try {
dialog.show()
} catch (ignored: Throwable) {
}
}
}
adapter.notifyItemChanged(adapterPosition, Any())
}
override fun onBind() {
val pm = itemView.context.packageManager
val userId = UserHandleCompat.getUserId(uid)
icon.setImageDrawable(ai.loadIcon(pm))
name.text = if (userId != UserHandleCompat.myUserId()) {
val userInfo = ShizukuSystemApis.getUserInfo(userId)
"${ai.loadLabel(pm)} - ${userInfo.name} ($userId)"
} else {
ai.loadLabel(pm)
}
pkg.text = ai.packageName
switchWidget.isChecked = AuthorizationManager.granted(packageName, uid)
root.visibility = if (ai.metaData != null && ai.metaData.getBoolean("moe.shizuku.client.V3_REQUIRES_ROOT")) View.VISIBLE else View.GONE
loadIconJob = AppIconCache.loadIconBitmapAsync(context, ai, ai.uid / 100000, icon)
}
override fun onBind(payloads: List<Any>) {
switchWidget.isChecked = AuthorizationManager.granted(packageName, uid)
}
override fun onRecycle() {
if (loadIconJob?.isActive == true) {
loadIconJob?.cancel()
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/management/ApplicationManagementActivity.kt
================================================
package moe.shizuku.manager.management
import android.os.Bundle
import android.util.TypedValue
import android.view.MenuItem
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import moe.shizuku.manager.Helps
import moe.shizuku.manager.R
import moe.shizuku.manager.app.AppBarActivity
import moe.shizuku.manager.databinding.AppsActivityBinding
import moe.shizuku.manager.utils.CustomTabsHelper
import rikka.lifecycle.Status
import rikka.recyclerview.addEdgeSpacing
import rikka.recyclerview.fixEdgeEffect
import rikka.shizuku.Shizuku
import java.util.*
class ApplicationManagementActivity : AppBarActivity() {
private val viewModel by appsViewModel()
private val adapter = AppsAdapter()
private val binderDeadListener = Shizuku.OnBinderDeadListener {
if (!isFinishing) {
finish()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!Shizuku.pingBinder()) {
finish()
return
}
val binding = AppsActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
viewModel.packages.observe(this) {
when (it.status) {
Status.SUCCESS -> {
adapter.updateData(it.data)
}
Status.ERROR -> {
finish()
val tr = it.error
Toast.makeText(this, Objects.toString(tr, "unknown"), Toast.LENGTH_SHORT).show()
tr.printStackTrace()
}
Status.LOADING -> {
}
}
}
if (viewModel.packages.value == null) {
viewModel.load()
}
val recyclerView = binding.list
recyclerView.adapter = adapter
recyclerView.fixEdgeEffect()
recyclerView.addEdgeSpacing(top = 8f, bottom = 8f, unit = TypedValue.COMPLEX_UNIT_DIP)
adapter.registerAdapterDataObserver(object : AdapterDataObserver() {
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
viewModel.load(true)
}
})
Shizuku.addBinderDeadListener(binderDeadListener)
}
override fun onDestroy() {
super.onDestroy()
Shizuku.removeBinderDeadListener(binderDeadListener)
}
override fun onResume() {
super.onResume()
adapter.notifyDataSetChanged()
gitextract_t0twfram/
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ └── config.yml
│ └── workflows/
│ └── app.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── build.gradle
├── common/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── moe/
│ └── shizuku/
│ └── common/
│ └── util/
│ ├── BuildUtils.java
│ └── OsUtils.java
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── manager/
│ ├── .gitignore
│ ├── aapt2-resources.cfg
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── .gitignore
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ └── rish
│ ├── java/
│ │ └── moe/
│ │ └── shizuku/
│ │ └── manager/
│ │ ├── AppConstants.java
│ │ ├── Helps.java
│ │ ├── MainActivity.java
│ │ ├── Manifest.java
│ │ ├── ShizukuApplication.kt
│ │ ├── ShizukuManagerProvider.kt
│ │ ├── ShizukuSettings.java
│ │ ├── adb/
│ │ │ ├── AdbClient.kt
│ │ │ ├── AdbException.kt
│ │ │ ├── AdbKey.kt
│ │ │ ├── AdbMdns.kt
│ │ │ ├── AdbMessage.kt
│ │ │ ├── AdbPairingClient.kt
│ │ │ ├── AdbPairingService.kt
│ │ │ ├── AdbPairingTutorialActivity.kt
│ │ │ └── AdbProtocol.kt
│ │ ├── app/
│ │ │ ├── AppActivity.kt
│ │ │ ├── AppBarActivity.kt
│ │ │ └── ThemeHelper.java
│ │ ├── authorization/
│ │ │ ├── AuthorizationManager.kt
│ │ │ └── RequestPermissionActivity.kt
│ │ ├── home/
│ │ │ ├── AdbDialogFragment.kt
│ │ │ ├── AdbPairDialogFragment.kt
│ │ │ ├── AdbPermissionLimitedViewHolder.kt
│ │ │ ├── HomeActivity.kt
│ │ │ ├── HomeAdapter.kt
│ │ │ ├── HomeViewModel.kt
│ │ │ ├── LearnMoreViewHolder.kt
│ │ │ ├── ManageAppsViewHolder.kt
│ │ │ ├── ServerStatusViewHolder.kt
│ │ │ ├── StartAdbViewHolder.kt
│ │ │ ├── StartRootViewHolder.kt
│ │ │ ├── StartWirelessAdbViewHolder.kt
│ │ │ ├── TerminalViewHolder.kt
│ │ │ └── WadbNotEnabledDialogFragment.kt
│ │ ├── ktx/
│ │ │ ├── Context.kt
│ │ │ ├── Log.kt
│ │ │ ├── PackageManager.kt
│ │ │ ├── RecyclerView.kt
│ │ │ └── String.kt
│ │ ├── legacy/
│ │ │ ├── LegacyIsNotSupportedActivity.kt
│ │ │ └── ShellRequestHandlerActivity.kt
│ │ ├── management/
│ │ │ ├── AppViewHolder.kt
│ │ │ ├── ApplicationManagementActivity.kt
│ │ │ ├── AppsAdapter.java
│ │ │ ├── AppsViewModel.kt
│ │ │ └── EmptyViewHolder.kt
│ │ ├── model/
│ │ │ └── ServiceStatus.kt
│ │ ├── receiver/
│ │ │ ├── BootCompleteReceiver.kt
│ │ │ └── ShizukuReceiver.kt
│ │ ├── settings/
│ │ │ ├── IntegerSimpleMenuPreference.java
│ │ │ ├── SettingsActivity.kt
│ │ │ └── SettingsFragment.kt
│ │ ├── shell/
│ │ │ ├── Shell.java
│ │ │ ├── ShellBinderRequestHandler.kt
│ │ │ └── ShellTutorialActivity.kt
│ │ ├── starter/
│ │ │ ├── Starter.kt
│ │ │ └── StarterActivity.kt
│ │ ├── utils/
│ │ │ ├── AppIconCache.kt
│ │ │ ├── CustomTabsHelper.java
│ │ │ ├── EmptySharedPreferencesImpl.java
│ │ │ ├── EnvironmentUtils.kt
│ │ │ ├── Logger.java
│ │ │ ├── MultiLocaleEntity.java
│ │ │ ├── ShizukuSystemApis.kt
│ │ │ ├── UserHandleCompat.java
│ │ │ └── UserInfoCompat.java
│ │ └── widget/
│ │ ├── CheckedImageView.java
│ │ └── VerticalPaddingDecoration.java
│ ├── jni/
│ │ ├── CMakeLists.txt
│ │ ├── adb_pairing.cpp
│ │ ├── adb_pairing.h
│ │ ├── android.cpp
│ │ ├── android.h
│ │ ├── cgroup.cpp
│ │ ├── cgroup.h
│ │ ├── helper.cpp
│ │ ├── logging.h
│ │ ├── misc.cpp
│ │ ├── misc.h
│ │ ├── selinux.cpp
│ │ ├── selinux.h
│ │ └── starter.cpp
│ └── res/
│ ├── animator/
│ │ └── alpha_animator.xml
│ ├── color/
│ │ ├── grant_permissions_button_ripple_color_selector.xml
│ │ ├── home_card_background_color.xml
│ │ └── home_card_foreground_color.xml
│ ├── color-night/
│ │ ├── home_card_background_color.xml
│ │ └── home_card_foreground_color.xml
│ ├── drawable/
│ │ ├── card_btn_background.xml
│ │ ├── grant_permissions_buttons_bottom.xml
│ │ ├── grant_permissions_buttons_top.xml
│ │ ├── home_card_foreground.xml
│ │ ├── ic_action_about_24dp.xml
│ │ ├── ic_action_settings_24dp.xml
│ │ ├── ic_adb_24dp.xml
│ │ ├── ic_baseline_link_24.xml
│ │ ├── ic_close_24.xml
│ │ ├── ic_code_24dp.xml
│ │ ├── ic_default_app_icon_background.xml
│ │ ├── ic_help_outline_24dp.xml
│ │ ├── ic_launcher.xml
│ │ ├── ic_learn_more_24dp.xml
│ │ ├── ic_monochrome.xml
│ │ ├── ic_numeric_1_circle_outline_24.xml
│ │ ├── ic_numeric_2_circle_outline_24.xml
│ │ ├── ic_numeric_3_circle_outline_24.xml
│ │ ├── ic_outline_arrow_upward_24.xml
│ │ ├── ic_outline_dark_mode_24.xml
│ │ ├── ic_outline_info_24.xml
│ │ ├── ic_outline_notifications_active_24.xml
│ │ ├── ic_outline_open_in_new_24.xml
│ │ ├── ic_outline_play_arrow_24.xml
│ │ ├── ic_outline_translate_24.xml
│ │ ├── ic_root_24dp.xml
│ │ ├── ic_server_error_24dp.xml
│ │ ├── ic_server_ok_24dp.xml
│ │ ├── ic_server_restart.xml
│ │ ├── ic_server_start_24dp.xml
│ │ ├── ic_settings_outline_24dp.xml
│ │ ├── ic_system_icon.xml
│ │ ├── ic_terminal_24.xml
│ │ ├── ic_wadb_24.xml
│ │ ├── ic_warning_24.xml
│ │ └── shape_circle_icon_background.xml
│ ├── drawable-v24/
│ │ └── ic_default_app_icon_foreground.xml
│ ├── drawable-v26/
│ │ ├── ic_default_app_icon.xml
│ │ └── ic_launcher.xml
│ ├── layout/
│ │ ├── about_dialog.xml
│ │ ├── adb_dialog.xml
│ │ ├── adb_pair_dialog.xml
│ │ ├── adb_pairing_tutorial_activity.xml
│ │ ├── app_list_empty.xml
│ │ ├── app_list_item.xml
│ │ ├── appbar.xml
│ │ ├── appbar_activity.xml
│ │ ├── appbar_fragment_activity.xml
│ │ ├── apps_activity.xml
│ │ ├── confirmation_dialog.xml
│ │ ├── home_activity.xml
│ │ ├── home_extra_step_required.xml
│ │ ├── home_item_container.xml
│ │ ├── home_learn_more.xml
│ │ ├── home_manage_apps_item.xml
│ │ ├── home_server_status.xml
│ │ ├── home_start_adb.xml
│ │ ├── home_start_root.xml
│ │ ├── home_start_wireless_adb.xml
│ │ ├── home_terminal.xml
│ │ ├── preference_recyclerview.xml
│ │ ├── shell_dialog.xml
│ │ ├── starter_activity.xml
│ │ └── terminal_tutorial_activity.xml
│ ├── menu/
│ │ └── main.xml
│ ├── values/
│ │ ├── arrays.xml
│ │ ├── attrs.xml
│ │ ├── bools.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ ├── strings_untranslatable.xml
│ │ ├── styles.xml
│ │ ├── themes.xml
│ │ ├── themes_overlay.xml
│ │ └── values.xml
│ ├── values-ang/
│ │ └── strings.xml
│ ├── values-ar/
│ │ └── strings.xml
│ ├── values-ars/
│ │ └── strings.xml
│ ├── values-az/
│ │ └── strings.xml
│ ├── values-b+es+419/
│ │ └── strings.xml
│ ├── values-bg/
│ │ └── strings.xml
│ ├── values-bn/
│ │ └── strings.xml
│ ├── values-ca/
│ │ └── strings.xml
│ ├── values-ckb/
│ │ └── strings.xml
│ ├── values-cs/
│ │ └── strings.xml
│ ├── values-da/
│ │ └── strings.xml
│ ├── values-de/
│ │ └── strings.xml
│ ├── values-el/
│ │ └── strings.xml
│ ├── values-enm/
│ │ └── strings.xml
│ ├── values-eo/
│ │ └── strings.xml
│ ├── values-es/
│ │ └── strings.xml
│ ├── values-es-rCL/
│ │ └── strings.xml
│ ├── values-et/
│ │ └── strings.xml
│ ├── values-fa/
│ │ └── strings.xml
│ ├── values-fi/
│ │ └── strings.xml
│ ├── values-fil/
│ │ └── strings.xml
│ ├── values-fr/
│ │ └── strings.xml
│ ├── values-he/
│ │ └── strings.xml
│ ├── values-hi/
│ │ └── strings.xml
│ ├── values-hr/
│ │ └── strings.xml
│ ├── values-hu/
│ │ └── strings.xml
│ ├── values-hy/
│ │ └── strings.xml
│ ├── values-id/
│ │ └── strings.xml
│ ├── values-it/
│ │ └── strings.xml
│ ├── values-ja/
│ │ └── strings.xml
│ ├── values-ka/
│ │ └── strings.xml
│ ├── values-kk/
│ │ └── strings.xml
│ ├── values-km/
│ │ └── strings.xml
│ ├── values-kn/
│ │ └── strings.xml
│ ├── values-ko/
│ │ └── strings.xml
│ ├── values-lb/
│ │ └── strings.xml
│ ├── values-lv/
│ │ └── strings.xml
│ ├── values-mk/
│ │ └── strings.xml
│ ├── values-ml/
│ │ └── strings.xml
│ ├── values-ms/
│ │ └── strings.xml
│ ├── values-my/
│ │ └── strings.xml
│ ├── values-night/
│ │ └── styles.xml
│ ├── values-nl/
│ │ └── strings.xml
│ ├── values-or/
│ │ └── strings.xml
│ ├── values-pl/
│ │ └── strings.xml
│ ├── values-pt/
│ │ └── strings.xml
│ ├── values-pt-rBR/
│ │ └── strings.xml
│ ├── values-ro/
│ │ └── strings.xml
│ ├── values-ru/
│ │ └── strings.xml
│ ├── values-sk/
│ │ └── strings.xml
│ ├── values-sl/
│ │ └── strings.xml
│ ├── values-sr/
│ │ └── strings.xml
│ ├── values-sv/
│ │ └── strings.xml
│ ├── values-sw600dp/
│ │ ├── bools.xml
│ │ ├── dimens.xml
│ │ └── values.xml
│ ├── values-ta/
│ │ └── strings.xml
│ ├── values-te/
│ │ └── strings.xml
│ ├── values-th/
│ │ └── strings.xml
│ ├── values-tr/
│ │ └── strings.xml
│ ├── values-ug/
│ │ └── strings.xml
│ ├── values-uk/
│ │ └── strings.xml
│ ├── values-ur/
│ │ └── strings.xml
│ ├── values-v21/
│ │ └── themes_override.xml
│ ├── values-v31/
│ │ └── themes_overlay.xml
│ ├── values-vi/
│ │ └── strings.xml
│ ├── values-zh-rCN/
│ │ └── strings.xml
│ ├── values-zh-rTW/
│ │ └── strings.xml
│ └── xml/
│ ├── backup_descriptor.xml
│ ├── data_extraction_rules.xml
│ └── settings.xml
├── server/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── rikka/
│ └── shizuku/
│ └── server/
│ ├── ApkChangedObservers.kt
│ ├── BinderSender.java
│ ├── ServerConstants.java
│ ├── ShizukuClientManager.java
│ ├── ShizukuConfig.java
│ ├── ShizukuConfigManager.java
│ ├── ShizukuService.java
│ ├── ShizukuUserServiceManager.java
│ ├── api/
│ │ └── IContentProviderUtils.java
│ └── ktx/
│ └── Handler.kt
├── settings.gradle
├── shell/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── rikka/
│ └── shizuku/
│ └── shell/
│ └── ShizukuShellLoader.java
├── signing.gradle
└── starter/
├── .gitignore
├── build.gradle
└── src/
└── main/
├── AndroidManifest.xml
└── java/
└── moe/
└── shizuku/
└── starter/
├── ServiceStarter.java
└── util/
└── IContentProviderCompat.java
SYMBOL INDEX (276 symbols across 40 files)
FILE: common/src/main/java/moe/shizuku/common/util/BuildUtils.java
class BuildUtils (line 8) | public class BuildUtils {
method atLeast31 (line 14) | public static boolean atLeast31() {
method atLeast30 (line 18) | public static boolean atLeast30() {
method atLeast29 (line 22) | public static boolean atLeast29() {
method atLeast28 (line 26) | public static boolean atLeast28() {
method atLeast26 (line 30) | public static boolean atLeast26() {
method atLeast24 (line 34) | public static boolean atLeast24() {
method atLeast23 (line 38) | public static boolean atLeast23() {
FILE: common/src/main/java/moe/shizuku/common/util/OsUtils.java
class OsUtils (line 5) | public class OsUtils {
method getUid (line 22) | public static int getUid() {
method getPid (line 26) | public static int getPid() {
method getSELinuxContext (line 30) | public static String getSELinuxContext() {
FILE: manager/src/main/java/moe/shizuku/manager/AppConstants.java
class AppConstants (line 3) | public class AppConstants {
FILE: manager/src/main/java/moe/shizuku/manager/Helps.java
class Helps (line 5) | public class Helps {
FILE: manager/src/main/java/moe/shizuku/manager/MainActivity.java
class MainActivity (line 5) | public class MainActivity extends HomeActivity {
FILE: manager/src/main/java/moe/shizuku/manager/Manifest.java
class Manifest (line 3) | public class Manifest {
class permission (line 5) | public static class permission {
FILE: manager/src/main/java/moe/shizuku/manager/ShizukuSettings.java
class ShizukuSettings (line 22) | public class ShizukuSettings {
method getPreferences (line 31) | public static SharedPreferences getPreferences() {
method getSettingsStorageContext (line 35) | @NonNull
method initialize (line 59) | public static void initialize(Context context) {
method getLastLaunchMode (line 78) | @LaunchMethod
method setLastLaunchMode (line 83) | public static void setLastLaunchMode(@LaunchMethod int method) {
method getNightMode (line 87) | @AppCompatDelegate.NightMode
method getLocale (line 96) | public static Locale getLocale() {
FILE: manager/src/main/java/moe/shizuku/manager/app/ThemeHelper.java
class ThemeHelper (line 13) | public class ThemeHelper {
method isBlackNightTheme (line 22) | public static boolean isBlackNightTheme(Context context) {
method isUsingSystemColor (line 26) | public static boolean isUsingSystemColor() {
method getTheme (line 31) | public static String getTheme(Context context) {
method getThemeStyleRes (line 39) | @StyleRes
FILE: manager/src/main/java/moe/shizuku/manager/management/AppsAdapter.java
class AppsAdapter (line 10) | public class AppsAdapter extends BaseRecyclerViewAdapter<ClassCreatorPoo...
method AppsAdapter (line 12) | public AppsAdapter() {
method getItemId (line 20) | @Override
method onCreateCreatorPool (line 25) | @Override
method updateData (line 30) | public void updateData(List<PackageInfo> data) {
FILE: manager/src/main/java/moe/shizuku/manager/settings/IntegerSimpleMenuPreference.java
class IntegerSimpleMenuPreference (line 29) | @SuppressLint("RestrictedApi")
method IntegerSimpleMenuPreference (line 42) | @SuppressLint("RestrictedApi")
method IntegerSimpleMenuPreference (line 90) | public IntegerSimpleMenuPreference(Context context, AttributeSet attrs...
method IntegerSimpleMenuPreference (line 94) | public IntegerSimpleMenuPreference(Context context, AttributeSet attrs) {
method IntegerSimpleMenuPreference (line 98) | public IntegerSimpleMenuPreference(Context context) {
method getIntArray (line 102) | @SuppressLint("RestrictedApi")
method onClick (line 109) | @Override
method setEntries (line 138) | public void setEntries(CharSequence[] entries) {
method setEntries (line 148) | public void setEntries(@ArrayRes int entriesResId) {
method getEntries (line 157) | public CharSequence[] getEntries() {
method setEntryValues (line 168) | public void setEntryValues(int[] entryValues) {
method setEntryValues (line 176) | public void setEntryValues(@ArrayRes int entryValuesResId) {
method getEntryValues (line 185) | public int[] getEntryValues() {
method setValue (line 195) | public void setValue(int value) {
method getSummary (line 216) | @Override
method setSummary (line 235) | @Override
method setValueIndex (line 250) | public void setValueIndex(int index) {
method getValue (line 262) | public int getValue() {
method getEntry (line 271) | public CharSequence getEntry() {
method findIndexOfValue (line 282) | public int findIndexOfValue(int value) {
method getValueIndex (line 294) | private int getValueIndex() {
method onGetDefaultValue (line 298) | @Override
method onSetInitialValue (line 303) | @Override
method onSaveInstanceState (line 311) | @Override
method onRestoreInstanceState (line 324) | @Override
class SavedState (line 337) | private static class SavedState extends BaseSavedState {
method SavedState (line 340) | public SavedState(Parcel source) {
method writeToParcel (line 345) | @Override
method SavedState (line 351) | public SavedState(Parcelable superState) {
method createFromParcel (line 357) | public SavedState createFromParcel(Parcel in) {
method newArray (line 361) | public SavedState[] newArray(int size) {
method onBindViewHolder (line 367) | @Override
FILE: manager/src/main/java/moe/shizuku/manager/shell/Shell.java
class Shell (line 12) | public class Shell extends Rish {
method requestPermission (line 14) | @Override
method main (line 41) | public static void main(String[] args, String packageName, IBinder bin...
FILE: manager/src/main/java/moe/shizuku/manager/utils/CustomTabsHelper.java
class CustomTabsHelper (line 21) | public class CustomTabsHelper {
type OnCreateIntentBuilderListener (line 23) | public interface OnCreateIntentBuilderListener {
method onCreateHelpIntentBuilder (line 24) | void onCreateHelpIntentBuilder(Context context, CustomTabsIntent.Bui...
method setOnCreateIntentBuilderListener (line 29) | public static void setOnCreateIntentBuilderListener(OnCreateIntentBuil...
method createBuilder (line 33) | public static CustomTabsIntent.Builder createBuilder() {
method launchHelp (line 39) | public static boolean launchHelp(Context context, Uri uri) {
method launchUrl (line 54) | public static boolean launchUrl(Context context, Uri uri) {
method launchUrl (line 58) | private static boolean launchUrl(Context context, CustomTabsIntent cus...
method launchUrlOrCopy (line 67) | public static void launchUrlOrCopy(Context context, String url) {
FILE: manager/src/main/java/moe/shizuku/manager/utils/EmptySharedPreferencesImpl.java
class EmptySharedPreferencesImpl (line 11) | public class EmptySharedPreferencesImpl implements SharedPreferences {
method getAll (line 13) | @Override
method getString (line 18) | @Nullable
method getStringSet (line 24) | @Nullable
method getInt (line 30) | @Override
method getLong (line 35) | @Override
method getFloat (line 40) | @Override
method getBoolean (line 45) | @Override
method contains (line 50) | @Override
method edit (line 55) | @Override
method registerOnSharedPreferenceChangeListener (line 60) | @Override
method unregisterOnSharedPreferenceChangeListener (line 65) | @Override
class EditorImpl (line 70) | private static class EditorImpl implements Editor {
method putString (line 72) | @Override
method putStringSet (line 77) | @Override
method putInt (line 82) | @Override
method putLong (line 87) | @Override
method putFloat (line 92) | @Override
method putBoolean (line 97) | @Override
method remove (line 102) | @Override
method clear (line 107) | @Override
method commit (line 112) | @Override
method apply (line 117) | @Override
FILE: manager/src/main/java/moe/shizuku/manager/utils/Logger.java
class Logger (line 7) | public class Logger {
method Logger (line 13) | public Logger(String TAG) {
method isLoggable (line 17) | public boolean isLoggable(String tag, int level) {
method v (line 21) | public void v(String msg) {
method v (line 27) | public void v(String fmt, Object... args) {
method v (line 33) | public void v(String msg, Throwable tr) {
method d (line 39) | public void d(String msg) {
method d (line 45) | public void d(String fmt, Object... args) {
method d (line 51) | public void d(String msg, Throwable tr) {
method i (line 57) | public void i(String msg) {
method i (line 63) | public void i(String fmt, Object... args) {
method i (line 69) | public void i(String msg, Throwable tr) {
method w (line 75) | public void w(String msg) {
method w (line 81) | public void w(String fmt, Object... args) {
method w (line 87) | public void w(Throwable tr, String fmt, Object... args) {
method w (line 93) | public void w(String msg, Throwable tr) {
method e (line 99) | public void e(String msg) {
method e (line 105) | public void e(String fmt, Object... args) {
method e (line 111) | public void e(String msg, Throwable tr) {
method e (line 117) | public void e(Throwable tr, String fmt, Object... args) {
FILE: manager/src/main/java/moe/shizuku/manager/utils/MultiLocaleEntity.java
class MultiLocaleEntity (line 10) | public class MultiLocaleEntity extends LinkedHashMap<String, String> {
class LocaleProvider (line 12) | public abstract static class LocaleProvider {
method get (line 13) | public abstract Locale get();
method get (line 17) | @Override
method setLocaleProvider (line 25) | public static void setLocaleProvider(@NonNull LocaleProvider localePro...
method get (line 29) | public String get() {
method get (line 33) | public String get(@NonNull Locale locale) {
FILE: manager/src/main/java/moe/shizuku/manager/utils/UserHandleCompat.java
class UserHandleCompat (line 5) | public class UserHandleCompat {
method getUserId (line 11) | public static int getUserId(int uid) {
method getAppId (line 15) | public static int getAppId(int uid) {
method myUserId (line 19) | public static int myUserId() {
FILE: manager/src/main/java/moe/shizuku/manager/utils/UserInfoCompat.java
class UserInfoCompat (line 3) | public class UserInfoCompat {
method UserInfoCompat (line 8) | public UserInfoCompat(int id, String name) {
FILE: manager/src/main/java/moe/shizuku/manager/widget/CheckedImageView.java
class CheckedImageView (line 10) | public class CheckedImageView extends ImageView implements Checkable {
method CheckedImageView (line 18) | public CheckedImageView(Context context) {
method CheckedImageView (line 22) | public CheckedImageView(Context context, @Nullable AttributeSet attrs) {
method CheckedImageView (line 26) | public CheckedImageView(Context context, @Nullable AttributeSet attrs,...
method CheckedImageView (line 30) | public CheckedImageView(Context context, @Nullable AttributeSet attrs,...
method setChecked (line 34) | @Override
method isChecked (line 42) | @Override
method toggle (line 47) | @Override
method onCreateDrawableState (line 52) | @Override
FILE: manager/src/main/java/moe/shizuku/manager/widget/VerticalPaddingDecoration.java
class VerticalPaddingDecoration (line 10) | public class VerticalPaddingDecoration extends RecyclerView.ItemDecorati...
method VerticalPaddingDecoration (line 15) | public VerticalPaddingDecoration(Context context) {
method VerticalPaddingDecoration (line 19) | public VerticalPaddingDecoration(Context context, int paddingDp) {
method setAllowTop (line 25) | public void setAllowTop(boolean allowTop) {
method setAllowBottom (line 29) | public void setAllowBottom(boolean allowBottom) {
method getItemOffsets (line 33) | @Override
FILE: manager/src/main/jni/adb_pairing.cpp
type PairingContextNative (line 26) | struct PairingContextNative {
function jlong (line 36) | static jlong PairingContext_Constructor(JNIEnv *env, jclass clazz, jbool...
function jbyteArray (line 86) | static jbyteArray PairingContext_Msg(JNIEnv *env, jobject obj, jlong ptr) {
function jboolean (line 93) | static jboolean PairingContext_InitCipher(JNIEnv *env, jobject obj, jlon...
function jbyteArray (line 140) | static jbyteArray PairingContext_Encrypt(JNIEnv *env, jobject obj, jlong...
function jbyteArray (line 171) | static jbyteArray PairingContext_Decrypt(JNIEnv *env, jobject obj, jlong...
function PairingContext_Destroy (line 202) | static void PairingContext_Destroy(JNIEnv *env, jobject obj, jlong ptr) {
function JNIEXPORT (line 211) | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
FILE: manager/src/main/jni/android.cpp
type android (line 7) | namespace android {
function GetApiLevel (line 9) | int GetApiLevel() {
function GetPreviewApiLevel (line 20) | int GetPreviewApiLevel() {
FILE: manager/src/main/jni/android.h
function namespace (line 3) | namespace android {
FILE: manager/src/main/jni/cgroup.cpp
type cgroup (line 6) | namespace cgroup {
function switch_cgroup (line 7) | bool switch_cgroup(const char *cgroup, int pid) {
FILE: manager/src/main/jni/cgroup.h
function namespace (line 4) | namespace cgroup {
FILE: manager/src/main/jni/helper.cpp
function jint (line 15) | static jint setcontext(JNIEnv *env, jobject thiz, jstring jName) {
function registerNativeMethods (line 33) | static int registerNativeMethods(JNIEnv *env, const char *className,
function registerNatives (line 46) | static int registerNatives(JNIEnv *env) {
function JNIEXPORT (line 54) | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
FILE: manager/src/main/jni/misc.cpp
function fdgets (line 15) | ssize_t fdgets(char *buf, const size_t size, int fd) {
function get_proc_name (line 27) | int get_proc_name(int pid, char *name, size_t size) {
function is_num (line 38) | int is_num(const char *s) {
function copyfileat (line 46) | int copyfileat(int src_path_fd, const char *src_path, int dst_path_fd, c...
function copyfile (line 90) | int copyfile(const char *src_path, const char *dst_path) {
function memsearch (line 94) | uintptr_t memsearch(const uintptr_t start, const uintptr_t end, const vo...
function switch_mnt_ns (line 107) | int switch_mnt_ns(int pid) {
function foreach_proc (line 120) | void foreach_proc(foreach_proc_function *func) {
FILE: manager/src/main/jni/selinux.cpp
type se (line 10) | namespace se {
function __getcon (line 12) | static int __getcon(char **context) {
function __setcon (line 56) | static int __setcon(const char *ctx) {
function __setfilecon (line 66) | static int __setfilecon(const char *path, const char *ctx) {
function __selinux_check_access (line 76) | static int __selinux_check_access(const char *scon, const char *tcon,
function __freecon (line 81) | static void __freecon(char *con) {
function init (line 91) | void init() {
FILE: manager/src/main/jni/selinux.h
function namespace (line 4) | namespace se {
FILE: manager/src/main/jni/starter.cpp
function run_server (line 48) | static void run_server(const char *dex_path, const char *main_class, con...
function start_server (line 116) | static void start_server(const char *path, const char *main_class, const...
function switch_cgroup (line 157) | static int switch_cgroup() {
function main (line 184) | int main(int argc, char *argv[]) {
FILE: server/src/main/java/rikka/shizuku/server/BinderSender.java
class BinderSender (line 28) | public class BinderSender {
class ProcessObserver (line 37) | private static class ProcessObserver extends ProcessObserverAdapter {
method onForegroundActivitiesChanged (line 41) | @Override
method onProcessDied (line 55) | @Override
method onProcessStateChanged (line 67) | @Override
class UidObserver (line 82) | @RequiresApi(api = Build.VERSION_CODES.N)
method onUidActive (line 87) | @Override
method onUidCachedChanged (line 94) | @Override
method onUidIdle (line 103) | @Override
method onUidGone (line 110) | @Override
method uidStarts (line 117) | private void uidStarts(int uid) throws RemoteException {
method uidGone (line 130) | private void uidGone(int uid) {
method sendBinder (line 141) | private static void sendBinder(int uid, int pid) throws RemoteException {
method register (line 172) | public static void register(ShizukuService shizukuService) {
FILE: server/src/main/java/rikka/shizuku/server/ServerConstants.java
class ServerConstants (line 3) | public class ServerConstants {
FILE: server/src/main/java/rikka/shizuku/server/ShizukuClientManager.java
class ShizukuClientManager (line 3) | public class ShizukuClientManager extends ClientManager<ShizukuConfigMan...
method ShizukuClientManager (line 5) | public ShizukuClientManager(ShizukuConfigManager configManager) {
FILE: server/src/main/java/rikka/shizuku/server/ShizukuConfig.java
class ShizukuConfig (line 10) | public class ShizukuConfig {
class PackageEntry (line 20) | public static class PackageEntry extends ConfigPackageEntry {
method PackageEntry (line 31) | public PackageEntry(int uid, int flags) {
method isAllowed (line 37) | @Override
method isDenied (line 42) | @Override
method ShizukuConfig (line 48) | public ShizukuConfig() {
method ShizukuConfig (line 51) | public ShizukuConfig(@NonNull List<PackageEntry> packages) {
FILE: server/src/main/java/rikka/shizuku/server/ShizukuConfigManager.java
class ShizukuConfigManager (line 32) | public class ShizukuConfigManager extends ConfigManager {
method load (line 45) | public static ShizukuConfig load() {
method write (line 70) | public static void write(ShizukuConfig config) {
method run (line 95) | @Override
method ShizukuConfigManager (line 103) | public ShizukuConfigManager() {
method scheduleWriteLocked (line 182) | private void scheduleWriteLocked() {
method findLocked (line 193) | private ShizukuConfig.PackageEntry findLocked(int uid) {
method find (line 202) | @Nullable
method updateLocked (line 209) | private void updateLocked(int uid, List<String> packages, int mask, in...
method update (line 232) | public void update(int uid, List<String> packages, int mask, int value...
method removeLocked (line 238) | private void removeLocked(int uid) {
method remove (line 247) | public void remove(int uid) {
FILE: server/src/main/java/rikka/shizuku/server/ShizukuService.java
class ShizukuService (line 59) | public class ShizukuService extends Service<ShizukuUserServiceManager, S...
method main (line 61) | public static void main(String[] args) {
method waitSystemService (line 70) | private static void waitSystemService(String name) {
method getManagerApplicationInfo (line 81) | public static ApplicationInfo getManagerApplicationInfo() {
method ShizukuService (line 92) | public ShizukuService() {
method onCreateUserServiceManager (line 130) | @Override
method onCreateClientManager (line 135) | @Override
method onCreateConfigManager (line 140) | @Override
method checkCallerManagerPermission (line 145) | @Override
method checkCallingPermission (line 150) | private int checkCallingPermission() {
method checkCallerPermission (line 161) | @Override
method exit (line 172) | @Override
method attachUserService (line 179) | @Override
method attachApplication (line 186) | @Override
method showPermissionConfirmation (line 255) | @Override
method dispatchPermissionConfirmationResult (line 283) | @Override
method getFlagsForUidInternal (line 337) | private int getFlagsForUidInternal(int uid, int mask, boolean allowRu...
method getFlagsForUid (line 363) | @Override
method updateFlagsForUid (line 372) | @Override
method onPermissionRevoked (line 416) | private void onPermissionRevoked(String packageName) {
method getApplications (line 421) | private ParcelableListSlice<PackageInfo> getApplications(int userId) {
method onTransact (line 458) | @Override
method sendBinderToClient (line 472) | void sendBinderToClient() {
method sendBinderToClient (line 478) | private static void sendBinderToClient(Binder binder, int userId) {
method sendBinderToManager (line 493) | void sendBinderToManager() {
method sendBinderToManger (line 497) | private static void sendBinderToManger(Binder binder) {
method sendBinderToManger (line 503) | static void sendBinderToManger(Binder binder, int userId) {
method sendBinderToUserApp (line 507) | static void sendBinderToUserApp(Binder binder, String packageName, int...
method sendBinderToUserApp (line 511) | static void sendBinderToUserApp(Binder binder, String packageName, int...
method dispatchPackageChanged (line 583) | @Override
method isHidden (line 588) | @Override
FILE: server/src/main/java/rikka/shizuku/server/ShizukuUserServiceManager.java
class ShizukuUserServiceManager (line 17) | public class ShizukuUserServiceManager extends UserServiceManager {
method ShizukuUserServiceManager (line 22) | public ShizukuUserServiceManager() {
method getUserServiceStartCmd (line 26) | @Override
method onUserServiceRecordCreated (line 41) | @Override
method onUserServiceRecordRemoved (line 74) | @Override
FILE: server/src/main/java/rikka/shizuku/server/api/IContentProviderUtils.java
class IContentProviderUtils (line 14) | public class IContentProviderUtils {
method callCompat (line 16) | public static Bundle callCompat(@NonNull IContentProvider provider, @N...
FILE: shell/src/main/java/rikka/shizuku/shell/ShizukuShellLoader.java
class ShizukuShellLoader (line 25) | public class ShizukuShellLoader {
method onTransact (line 33) | @Override
method requestForBinder (line 52) | private static void requestForBinder() throws RemoteException {
method onBinderReceived (line 99) | private static void onBinderReceived(IBinder binder, String sourceDir) {
method main (line 124) | public static void main(String[] args) {
method abort (line 166) | private static void abort(String message) {
FILE: starter/src/main/java/moe/shizuku/starter/ServiceStarter.java
class ServiceStarter (line 20) | public class ServiceStarter {
method commandForUserService (line 53) | public static String commandForUserService(String appProcess, String m...
method main (line 61) | public static void main(String[] args) {
method sendBinder (line 90) | private static boolean sendBinder(IBinder binder, String token) {
method sendBinder (line 94) | private static boolean sendBinder(IBinder binder, String token, boolea...
FILE: starter/src/main/java/moe/shizuku/starter/util/IContentProviderCompat.java
class IContentProviderCompat (line 13) | public class IContentProviderCompat {
method call (line 15) | @Nullable
Condensed preview — 287 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (916K chars).
[
{
"path": ".gitattributes",
"chars": 52,
"preview": "* text=auto eol=lf\n\n*.bat text eol=crlf\n*.jar binary"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 1382,
"preview": "name: Bug report\ndescription: Report a bug of Shizuku\n\nbody:\n - type: checkboxes\n id: requirements\n attributes:\n "
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 185,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: GitHub Community Support\n url: https://github.com/RikkaApps/Shiz"
},
{
"path": ".github/workflows/app.yml",
"chars": 2103,
"preview": "name: App\n\non:\n push:\n paths-ignore:\n - '.github/ISSUE_TEMPLATE'\n\njobs:\n build:\n runs-on: ubuntu-latest\n\n "
},
{
"path": ".gitignore",
"chars": 178,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n/."
},
{
"path": ".gitmodules",
"chars": 95,
"preview": "[submodule \"api\"]\n\tpath = api\n\turl = git@github.com:RikkaApps/Shizuku-API.git\n\tbranch = master\n"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 5244,
"preview": "# Shizuku\n\n## Background\n\nWhen developing apps that requires root, the most common method is to run some commands in the"
},
{
"path": "build.gradle",
"chars": 937,
"preview": "plugins {\n id(\"idea\")\n}\n\nidea.module {\n excludeDirs += file('out')\n}\n\nsubprojects {\n plugins.withId(\"com.androi"
},
{
"path": "common/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "common/build.gradle",
"chars": 196,
"preview": "plugins {\n id 'com.android.library'\n}\n\nandroid {\n namespace 'rikka.shizuku.common'\n buildFeatures {\n bui"
},
{
"path": "common/src/main/AndroidManifest.xml",
"chars": 52,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest />\n"
},
{
"path": "common/src/main/java/moe/shizuku/common/util/BuildUtils.java",
"chars": 850,
"preview": "package moe.shizuku.common.util;\n\nimport android.os.Build;\n\n/**\n * TODO: Replace it with {@link rikka.core.util.BuildUti"
},
{
"path": "common/src/main/java/moe/shizuku/common/util/OsUtils.java",
"chars": 679,
"preview": "package moe.shizuku.common.util;\n\nimport android.os.SELinux;\n\npublic class OsUtils {\n\n private static final int UID ="
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 251,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
},
{
"path": "gradle.properties",
"chars": 139,
"preview": "org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\nandroid.useAndroidX=true\nandroid.nonTransitiveRClass=false\nandroid.no"
},
{
"path": "gradlew",
"chars": 8710,
"preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "gradlew.bat",
"chars": 2937,
"preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
},
{
"path": "manager/.gitignore",
"chars": 32,
"preview": "/build\n/signing.properties\n/.cxx"
},
{
"path": "manager/aapt2-resources.cfg",
"chars": 1052,
"preview": "color/material_orange_50#no_obfuscate\ncolor/material_indigo_50#no_obfuscate\ncolor/material_blue_grey_50#no_obfuscate\ncol"
},
{
"path": "manager/build.gradle",
"chars": 6613,
"preview": "import java.nio.file.Paths\nimport com.android.build.gradle.internal.tasks.CompileArtProfileTask\n\nplugins {\n id('com.a"
},
{
"path": "manager/proguard-rules.pro",
"chars": 1605,
"preview": "-keepclassmembers class * implements android.os.Parcelable {\n public static final ** CREATOR;\n}\n\n-keepclasseswithmemb"
},
{
"path": "manager/src/main/.gitignore",
"chars": 13,
"preview": "/assets/*.dex"
},
{
"path": "manager/src/main/AndroidManifest.xml",
"chars": 7458,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "manager/src/main/assets/rish",
"chars": 882,
"preview": "#!/system/bin/sh\nBASEDIR=$(dirname \"$0\")\nDEX=\"$BASEDIR\"/rish_shizuku.dex\n\nif [ ! -f \"$DEX\" ]; then\n echo \"Cannot find $"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/AppConstants.java",
"chars": 491,
"preview": "package moe.shizuku.manager;\n\npublic class AppConstants {\n\n public static final String TAG = \"ShizukuManager\";\n\n p"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/Helps.java",
"chars": 2681,
"preview": "package moe.shizuku.manager;\n\nimport moe.shizuku.manager.utils.MultiLocaleEntity;\n\npublic class Helps {\n\n public stat"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/MainActivity.java",
"chars": 128,
"preview": "package moe.shizuku.manager;\n\nimport moe.shizuku.manager.home.HomeActivity;\n\npublic class MainActivity extends HomeActiv"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/Manifest.java",
"chars": 188,
"preview": "package moe.shizuku.manager;\n\npublic class Manifest {\n\n public static class permission {\n\n public static final"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/ShizukuApplication.kt",
"chars": 1217,
"preview": "package moe.shizuku.manager\n\nimport android.app.Application\nimport android.content.Context\nimport android.os.Build\nimpor"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/ShizukuManagerProvider.kt",
"chars": 2728,
"preview": "package moe.shizuku.manager\n\nimport android.os.Bundle\nimport androidx.core.os.bundleOf\nimport moe.shizuku.api.BinderCont"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/ShizukuSettings.java",
"chars": 3311,
"preview": "package moe.shizuku.manager;\n\nimport android.app.ActivityThread;\nimport android.content.Context;\nimport android.content."
},
{
"path": "manager/src/main/java/moe/shizuku/manager/adb/AdbClient.kt",
"chars": 6179,
"preview": "package moe.shizuku.manager.adb\n\nimport android.util.Log\nimport moe.shizuku.manager.adb.AdbProtocol.ADB_AUTH_RSAPUBLICKE"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/adb/AdbException.kt",
"chars": 501,
"preview": "package moe.shizuku.manager.adb\n\n@Suppress(\"NOTHING_TO_INLINE\")\ninline fun adbError(message: Any): Nothing = throw AdbEx"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/adb/AdbKey.kt",
"chars": 13348,
"preview": "package moe.shizuku.manager.adb\n\nimport android.annotation.SuppressLint\nimport android.content.SharedPreferences\nimport "
},
{
"path": "manager/src/main/java/moe/shizuku/manager/adb/AdbMdns.kt",
"chars": 4031,
"preview": "package moe.shizuku.manager.adb\n\nimport android.content.Context\nimport android.net.nsd.NsdManager\nimport android.net.nsd"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/adb/AdbMessage.kt",
"chars": 3947,
"preview": "package moe.shizuku.manager.adb\n\nimport moe.shizuku.manager.adb.AdbProtocol.A_AUTH\nimport moe.shizuku.manager.adb.AdbPro"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/adb/AdbPairingClient.kt",
"chars": 9665,
"preview": "package moe.shizuku.manager.adb\n\nimport android.os.Build\nimport android.util.Log\nimport androidx.annotation.RequiresApi\n"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/adb/AdbPairingService.kt",
"chars": 10705,
"preview": "package moe.shizuku.manager.adb\n\nimport android.annotation.TargetApi\nimport android.app.*\nimport android.content.Context"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/adb/AdbPairingTutorialActivity.kt",
"chars": 4410,
"preview": "package moe.shizuku.manager.adb\n\nimport android.app.AppOpsManager\nimport android.app.ForegroundServiceStartNotAllowedExc"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/adb/AdbProtocol.kt",
"chars": 551,
"preview": "package moe.shizuku.manager.adb\n\nobject AdbProtocol {\n\n const val A_SYNC = 0x434e5953\n const val A_CNXN = 0x4e584e"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/app/AppActivity.kt",
"chars": 2183,
"preview": "package moe.shizuku.manager.app\n\nimport android.content.res.Resources\nimport android.content.res.Resources.Theme\nimport "
},
{
"path": "manager/src/main/java/moe/shizuku/manager/app/AppBarActivity.kt",
"chars": 1935,
"preview": "package moe.shizuku.manager.app\n\nimport android.graphics.Color\nimport android.os.Build\nimport android.os.Bundle\nimport a"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/app/ThemeHelper.java",
"chars": 1668,
"preview": "package moe.shizuku.manager.app;\n\nimport android.content.Context;\nimport android.os.Build;\n\nimport androidx.annotation.S"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/authorization/AuthorizationManager.kt",
"chars": 3515,
"preview": "package moe.shizuku.manager.authorization\n\nimport android.content.pm.PackageInfo\nimport android.content.pm.PackageManage"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/authorization/RequestPermissionActivity.kt",
"chars": 5093,
"preview": "package moe.shizuku.manager.authorization\n\nimport android.app.Dialog\nimport android.content.pm.ApplicationInfo\nimport an"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/AdbDialogFragment.kt",
"chars": 3893,
"preview": "package moe.shizuku.manager.home\n\nimport android.Manifest.permission.WRITE_SECURE_SETTINGS\nimport android.app.Dialog\nimp"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/AdbPairDialogFragment.kt",
"chars": 7356,
"preview": "package moe.shizuku.manager.home\n\nimport android.annotation.SuppressLint\nimport android.app.Dialog\nimport android.conten"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/AdbPermissionLimitedViewHolder.kt",
"chars": 1057,
"preview": "package moe.shizuku.manager.home\n\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGr"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/HomeActivity.kt",
"chars": 5235,
"preview": "package moe.shizuku.manager.home\n\nimport android.content.DialogInterface\nimport android.content.Intent\nimport android.os"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/HomeAdapter.kt",
"chars": 2529,
"preview": "package moe.shizuku.manager.home\n\nimport android.os.Build\nimport moe.shizuku.manager.management.AppsViewModel\nimport moe"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/HomeViewModel.kt",
"chars": 2298,
"preview": "package moe.shizuku.manager.home\n\nimport android.content.pm.PackageManager\nimport androidx.lifecycle.LiveData\nimport and"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/LearnMoreViewHolder.kt",
"chars": 990,
"preview": "package moe.shizuku.manager.home\n\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGr"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/ManageAppsViewHolder.kt",
"chars": 2245,
"preview": "package moe.shizuku.manager.home\n\nimport android.content.Intent\nimport android.text.method.LinkMovementMethod\nimport and"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/ServerStatusViewHolder.kt",
"chars": 3142,
"preview": "package moe.shizuku.manager.home\n\nimport android.text.TextUtils\nimport android.view.LayoutInflater\nimport android.view.V"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/StartAdbViewHolder.kt",
"chars": 3033,
"preview": "package moe.shizuku.manager.home\n\nimport android.content.Intent\nimport android.text.method.LinkMovementMethod\nimport and"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/StartRootViewHolder.kt",
"chars": 2875,
"preview": "package moe.shizuku.manager.home\n\nimport android.content.Intent\nimport android.text.method.LinkMovementMethod\nimport and"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/StartWirelessAdbViewHolder.kt",
"chars": 4020,
"preview": "package moe.shizuku.manager.home\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Build\ni"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/TerminalViewHolder.kt",
"chars": 1671,
"preview": "package moe.shizuku.manager.home\n\nimport android.content.Intent\nimport android.view.LayoutInflater\nimport android.view.V"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/home/WadbNotEnabledDialogFragment.kt",
"chars": 813,
"preview": "package moe.shizuku.manager.home\n\nimport android.app.Dialog\nimport android.os.Bundle\nimport androidx.fragment.app.Dialog"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/ktx/Context.kt",
"chars": 787,
"preview": "package moe.shizuku.manager.ktx\n\nimport android.content.Context\nimport android.os.Build\nimport android.os.UserManager\nim"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/ktx/Log.kt",
"chars": 1488,
"preview": "@file:Suppress(\"NOTHING_TO_INLINE\")\n\npackage moe.shizuku.manager.ktx\n\nimport android.util.Log\n\ninline val <reified T> T."
},
{
"path": "manager/src/main/java/moe/shizuku/manager/ktx/PackageManager.kt",
"chars": 950,
"preview": "package moe.shizuku.manager.ktx\n\nimport android.content.ComponentName\nimport android.content.pm.PackageManager\n\nfun Pack"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/ktx/RecyclerView.kt",
"chars": 2513,
"preview": "package moe.shizuku.manager.ktx\n\nimport android.graphics.Canvas\nimport android.widget.EdgeEffect\nimport androidx.recycle"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/ktx/String.kt",
"chars": 500,
"preview": "package moe.shizuku.manager.ktx\n\nimport android.text.Spanned\nimport rikka.html.text.HtmlCompat\n\nfun CharSequence.toHtml("
},
{
"path": "manager/src/main/java/moe/shizuku/manager/legacy/LegacyIsNotSupportedActivity.kt",
"chars": 2992,
"preview": "package moe.shizuku.manager.legacy\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.content.pm."
},
{
"path": "manager/src/main/java/moe/shizuku/manager/legacy/ShellRequestHandlerActivity.kt",
"chars": 433,
"preview": "package moe.shizuku.manager.legacy\n\nimport android.os.Bundle\nimport android.widget.Toast\nimport moe.shizuku.manager.app."
},
{
"path": "manager/src/main/java/moe/shizuku/manager/management/AppViewHolder.kt",
"chars": 4253,
"preview": "package moe.shizuku.manager.management\n\nimport android.content.pm.PackageInfo\nimport android.text.method.LinkMovementMet"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/management/ApplicationManagementActivity.kt",
"chars": 2575,
"preview": "package moe.shizuku.manager.management\n\nimport android.os.Bundle\nimport android.util.TypedValue\nimport android.view.Menu"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/management/AppsAdapter.java",
"chars": 990,
"preview": "package moe.shizuku.manager.management;\n\nimport android.content.pm.PackageInfo;\n\nimport java.util.List;\n\nimport rikka.re"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/management/AppsViewModel.kt",
"chars": 1997,
"preview": "package moe.shizuku.manager.management\n\nimport android.content.Context\nimport android.content.pm.PackageInfo\nimport andr"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/management/EmptyViewHolder.kt",
"chars": 1333,
"preview": "package moe.shizuku.manager.management\n\nimport android.content.pm.PackageInfo\nimport android.text.method.LinkMovementMet"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/model/ServiceStatus.kt",
"chars": 349,
"preview": "package moe.shizuku.manager.model\n\nimport rikka.shizuku.Shizuku\n\ndata class ServiceStatus(\n val uid: Int = -1,\n "
},
{
"path": "manager/src/main/java/moe/shizuku/manager/receiver/BootCompleteReceiver.kt",
"chars": 3581,
"preview": "package moe.shizuku.manager.receiver\n\nimport android.Manifest.permission.WRITE_SECURE_SETTINGS\nimport android.content.Br"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/receiver/ShizukuReceiver.kt",
"chars": 474,
"preview": "package moe.shizuku.manager.receiver\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport and"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/settings/IntegerSimpleMenuPreference.java",
"chars": 12222,
"preview": "package moe.shizuku.manager.settings;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport an"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/settings/SettingsActivity.kt",
"chars": 851,
"preview": "package moe.shizuku.manager.settings\n\nimport android.content.res.Resources\nimport android.os.Bundle\nimport moe.shizuku.m"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/settings/SettingsFragment.kt",
"chars": 9315,
"preview": "package moe.shizuku.manager.settings\n\nimport android.content.ComponentName\nimport android.content.Context\nimport android"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/shell/Shell.java",
"chars": 2000,
"preview": "package moe.shizuku.manager.shell;\n\nimport android.content.pm.PackageManager;\nimport android.os.Handler;\nimport android."
},
{
"path": "manager/src/main/java/moe/shizuku/manager/shell/ShellBinderRequestHandler.kt",
"chars": 1098,
"preview": "package moe.shizuku.manager.shell\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.IBinde"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/shell/ShellTutorialActivity.kt",
"chars": 4140,
"preview": "package moe.shizuku.manager.shell\n\nimport android.net.Uri\nimport android.os.Bundle\nimport android.provider.DocumentsCont"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/starter/Starter.kt",
"chars": 407,
"preview": "package moe.shizuku.manager.starter\n\nimport moe.shizuku.manager.application\nimport java.io.File\n\nobject Starter {\n\n p"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/starter/StarterActivity.kt",
"chars": 6621,
"preview": "package moe.shizuku.manager.starter\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.util.Log\nimp"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/utils/AppIconCache.kt",
"chars": 4373,
"preview": "package moe.shizuku.manager.utils\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android."
},
{
"path": "manager/src/main/java/moe/shizuku/manager/utils/CustomTabsHelper.java",
"chars": 3056,
"preview": "package moe.shizuku.manager.utils;\n\nimport android.content.ActivityNotFoundException;\nimport android.content.Context;\nim"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/utils/EmptySharedPreferencesImpl.java",
"chars": 2542,
"preview": "package moe.shizuku.manager.utils;\n\nimport android.content.SharedPreferences;\n\nimport androidx.annotation.Nullable;\n\nimp"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/utils/EnvironmentUtils.kt",
"chars": 787,
"preview": "package moe.shizuku.manager.utils\n\nimport android.app.UiModeManager\nimport android.content.Context\nimport android.conten"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/utils/Logger.java",
"chars": 2890,
"preview": "package moe.shizuku.manager.utils;\n\nimport android.util.Log;\n\nimport java.util.Locale;\n\npublic class Logger {\n\n publi"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/utils/MultiLocaleEntity.java",
"chars": 2091,
"preview": "package moe.shizuku.manager.utils;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.LinkedHashMap;\nimport java.uti"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/utils/ShizukuSystemApis.kt",
"chars": 3470,
"preview": "package moe.shizuku.manager.utils\n\nimport android.content.pm.PackageInfo\nimport android.content.pm.PackageManager\nimport"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/utils/UserHandleCompat.java",
"chars": 459,
"preview": "package moe.shizuku.manager.utils;\n\nimport android.system.Os;\n\npublic class UserHandleCompat {\n\n private static final"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/utils/UserInfoCompat.java",
"chars": 228,
"preview": "package moe.shizuku.manager.utils;\n\npublic class UserInfoCompat {\n\n public final int id;\n public final String name"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/widget/CheckedImageView.java",
"chars": 1562,
"preview": "package moe.shizuku.manager.widget;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.wi"
},
{
"path": "manager/src/main/java/moe/shizuku/manager/widget/VerticalPaddingDecoration.java",
"chars": 1418,
"preview": "package moe.shizuku.manager.widget;\n\nimport android.content.Context;\nimport android.graphics.Rect;\nimport android.view.V"
},
{
"path": "manager/src/main/jni/CMakeLists.txt",
"chars": 1293,
"preview": "cmake_minimum_required(VERSION 3.31)\n\nproject(\"shizuku\")\n\nset(CMAKE_CXX_STANDARD 17)\n\nadd_compile_options(-Werror=format"
},
{
"path": "manager/src/main/jni/adb_pairing.cpp",
"chars": 7728,
"preview": "#include <jni.h>\n#include <dirent.h>\n#include <cstring>\n#include <cstdlib>\n#include <cinttypes>\n#include <sys/system_pro"
},
{
"path": "manager/src/main/jni/adb_pairing.h",
"chars": 45,
"preview": "#ifndef ADB_H\n#define ADB_H\n\n#endif // ADB_H\n"
},
{
"path": "manager/src/main/jni/android.cpp",
"chars": 732,
"preview": "#include <sys/system_properties.h>\n#include <cstring>\n#include <climits>\n#include <fcntl.h>\n#include <unistd.h>\n\nnamespa"
},
{
"path": "manager/src/main/jni/android.h",
"chars": 90,
"preview": "#pragma once\n\nnamespace android {\n\n int GetApiLevel();\n\n int GetPreviewApiLevel();\n}"
},
{
"path": "manager/src/main/jni/cgroup.cpp",
"chars": 566,
"preview": "#include <cstring>\n#include <fcntl.h>\n#include <unistd.h>\n#include <cstdio>\n\nnamespace cgroup {\n bool switch_cgroup(c"
},
{
"path": "manager/src/main/jni/cgroup.h",
"chars": 129,
"preview": "#ifndef CGROUP_H\n#define CGROUP_H\n\nnamespace cgroup {\n bool switch_cgroup(const char *cgroup, int pid);\n}\n\n#endif // "
},
{
"path": "manager/src/main/jni/helper.cpp",
"chars": 1689,
"preview": "#include <jni.h>\n#include <dirent.h>\n#include <unistd.h>\n#include <mntent.h>\n#include <sys/types.h>\n#include <sys/stat.h"
},
{
"path": "manager/src/main/jni/logging.h",
"chars": 848,
"preview": "#ifndef _LOGGING_H\n#define _LOGGING_H\n\n#include <errno.h>\n#include \"android/log.h\"\n\n#ifndef LOG_TAG\n#define LOG_TAG \""
},
{
"path": "manager/src/main/jni/misc.cpp",
"chars": 4114,
"preview": "#include <sys/types.h>\n#include <sys/sendfile.h>\n#include <sys/stat.h>\n#include <zconf.h>\n#include <dirent.h>\n#include <"
},
{
"path": "manager/src/main/jni/misc.h",
"chars": 402,
"preview": "#ifndef MISC_H\n#define MISC_H\n\nint copyfile(const char *src_path, const char *dst_path);\nuintptr_t memsearch(const uintp"
},
{
"path": "manager/src/main/jni/selinux.cpp",
"chars": 2805,
"preview": "#include <fcntl.h>\n#include <cstring>\n#include <unistd.h>\n#include <dlfcn.h>\n#include <cerrno>\n#include <syscall.h>\n#inc"
},
{
"path": "manager/src/main/jni/selinux.h",
"chars": 589,
"preview": "#ifndef SELINUX_H\n#define SELINUX_H\n\nnamespace se {\n void init();\n\n using getcon_t = int(char **);\n using setco"
},
{
"path": "manager/src/main/jni/starter.cpp",
"chars": 8363,
"preview": "#include <cstdio>\n#include <cstdlib>\n#include <fcntl.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <ctime>\n#includ"
},
{
"path": "manager/src/main/res/animator/alpha_animator.xml",
"chars": 770,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:ap"
},
{
"path": "manager/src/main/res/color/grant_permissions_button_ripple_color_selector.xml",
"chars": 1968,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:tools="
},
{
"path": "manager/src/main/res/color/home_card_background_color.xml",
"chars": 199,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item a"
},
{
"path": "manager/src/main/res/color/home_card_foreground_color.xml",
"chars": 173,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item a"
},
{
"path": "manager/src/main/res/color-night/home_card_background_color.xml",
"chars": 199,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item a"
},
{
"path": "manager/src/main/res/color-night/home_card_foreground_color.xml",
"chars": 173,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item a"
},
{
"path": "manager/src/main/res/drawable/card_btn_background.xml",
"chars": 764,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:aapt=\"http://schemas.android.com/aapt\"\n xmlns:android=\"http:"
},
{
"path": "manager/src/main/res/drawable/grant_permissions_buttons_bottom.xml",
"chars": 546,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:co"
},
{
"path": "manager/src/main/res/drawable/grant_permissions_buttons_top.xml",
"chars": 546,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:co"
},
{
"path": "manager/src/main/res/drawable/home_card_foreground.xml",
"chars": 894,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n Copyright (C) 2019 The Android Open Source Project\n\n Licensed under the A"
},
{
"path": "manager/src/main/res/drawable/ic_action_about_24dp.xml",
"chars": 483,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "manager/src/main/res/drawable/ic_action_settings_24dp.xml",
"chars": 2043,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_adb_24dp.xml",
"chars": 647,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "manager/src/main/res/drawable/ic_baseline_link_24.xml",
"chars": 525,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_close_24.xml",
"chars": 423,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_code_24dp.xml",
"chars": 399,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "manager/src/main/res/drawable/ic_default_app_icon_background.xml",
"chars": 4880,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n android:height=\"108dp\"\n android:width=\"108dp\"\n android:viewport"
},
{
"path": "manager/src/main/res/drawable/ic_help_outline_24dp.xml",
"chars": 1113,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "manager/src/main/res/drawable/ic_launcher.xml",
"chars": 146,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<bitmap android:src=\"@mipmap/ic_launcher\"\n xmlns:android=\"http://schemas.andro"
},
{
"path": "manager/src/main/res/drawable/ic_learn_more_24dp.xml",
"chars": 562,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "manager/src/main/res/drawable/ic_monochrome.xml",
"chars": 976,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"108dp\"\n android:height=\"108dp\"\n"
},
{
"path": "manager/src/main/res/drawable/ic_numeric_1_circle_outline_24.xml",
"chars": 452,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:he"
},
{
"path": "manager/src/main/res/drawable/ic_numeric_2_circle_outline_24.xml",
"chars": 520,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:he"
},
{
"path": "manager/src/main/res/drawable/ic_numeric_3_circle_outline_24.xml",
"chars": 549,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:he"
},
{
"path": "manager/src/main/res/drawable/ic_outline_arrow_upward_24.xml",
"chars": 380,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_outline_dark_mode_24.xml",
"chars": 688,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_outline_info_24.xml",
"chars": 481,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_outline_notifications_active_24.xml",
"chars": 737,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_outline_open_in_new_24.xml",
"chars": 489,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_outline_play_arrow_24.xml",
"chars": 368,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_outline_translate_24.xml",
"chars": 644,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_root_24dp.xml",
"chars": 553,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:height=\"24dp\"\n android:width=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_server_error_24dp.xml",
"chars": 543,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "manager/src/main/res/drawable/ic_server_ok_24dp.xml",
"chars": 539,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "manager/src/main/res/drawable/ic_server_restart.xml",
"chars": 676,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "manager/src/main/res/drawable/ic_server_start_24dp.xml",
"chars": 311,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "manager/src/main/res/drawable/ic_settings_outline_24dp.xml",
"chars": 2103,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "manager/src/main/res/drawable/ic_system_icon.xml",
"chars": 645,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_terminal_24.xml",
"chars": 486,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:he"
},
{
"path": "manager/src/main/res/drawable/ic_wadb_24.xml",
"chars": 708,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/ic_warning_24.xml",
"chars": 324,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "manager/src/main/res/drawable/shape_circle_icon_background.xml",
"chars": 317,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "manager/src/main/res/drawable-v24/ic_default_app_icon_foreground.xml",
"chars": 1880,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:aapt=\"http://schemas.android.com/aapt\"\n "
},
{
"path": "manager/src/main/res/drawable-v26/ic_default_app_icon.xml",
"chars": 286,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "manager/src/main/res/drawable-v26/ic_launcher.xml",
"chars": 331,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "manager/src/main/res/layout/about_dialog.xml",
"chars": 1618,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "manager/src/main/res/layout/adb_dialog.xml",
"chars": 1361,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "manager/src/main/res/layout/adb_pair_dialog.xml",
"chars": 3643,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:"
},
{
"path": "manager/src/main/res/layout/adb_pairing_tutorial_activity.xml",
"chars": 14546,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rikka.widget.borderview.BorderNestedScrollView xmlns:android=\"http://schemas.and"
},
{
"path": "manager/src/main/res/layout/app_list_empty.xml",
"chars": 953,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "manager/src/main/res/layout/app_list_item.xml",
"chars": 2613,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "manager/src/main/res/layout/appbar.xml",
"chars": 957,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.appbar.AppBarLayout xmlns:android=\"http://schemas.an"
},
{
"path": "manager/src/main/res/layout/appbar_activity.xml",
"chars": 655,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schema"
},
{
"path": "manager/src/main/res/layout/appbar_fragment_activity.xml",
"chars": 810,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schema"
},
{
"path": "manager/src/main/res/layout/apps_activity.xml",
"chars": 823,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rikka.widget.borderview.BorderRecyclerView xmlns:android=\"http://schemas.android"
},
{
"path": "manager/src/main/res/layout/confirmation_dialog.xml",
"chars": 1470,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "manager/src/main/res/layout/home_activity.xml",
"chars": 927,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rikka.widget.borderview.BorderRecyclerView xmlns:android=\"http://schemas.android"
},
{
"path": "manager/src/main/res/layout/home_extra_step_required.xml",
"chars": 2346,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "manager/src/main/res/layout/home_item_container.xml",
"chars": 660,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas."
},
{
"path": "manager/src/main/res/layout/home_learn_more.xml",
"chars": 1354,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n styl"
},
{
"path": "manager/src/main/res/layout/home_manage_apps_item.xml",
"chars": 1392,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n styl"
},
{
"path": "manager/src/main/res/layout/home_server_status.xml",
"chars": 1457,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "manager/src/main/res/layout/home_start_adb.xml",
"chars": 1740,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "manager/src/main/res/layout/home_start_root.xml",
"chars": 2110,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "manager/src/main/res/layout/home_start_wireless_adb.xml",
"chars": 2184,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "manager/src/main/res/layout/home_terminal.xml",
"chars": 1451,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "manager/src/main/res/layout/preference_recyclerview.xml",
"chars": 779,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rikka.widget.borderview.BorderRecyclerView xmlns:android=\"http://schemas.android"
},
{
"path": "manager/src/main/res/layout/shell_dialog.xml",
"chars": 724,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "manager/src/main/res/layout/starter_activity.xml",
"chars": 1415,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns"
},
{
"path": "manager/src/main/res/layout/terminal_tutorial_activity.xml",
"chars": 7848,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rikka.widget.borderview.BorderNestedScrollView xmlns:android=\"http://schemas.and"
},
{
"path": "manager/src/main/res/menu/main.xml",
"chars": 576,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <item\n "
},
{
"path": "manager/src/main/res/values/arrays.xml",
"chars": 436,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string-array nam"
},
{
"path": "manager/src/main/res/values/attrs.xml",
"chars": 172,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <attr name=\"homeCardStyle\" format=\"reference\" />\n <attr name=\""
},
{
"path": "manager/src/main/res/values/bools.xml",
"chars": 185,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\"\n tools:ignore=\"Unuse"
},
{
"path": "manager/src/main/res/values/colors.xml",
"chars": 211,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"notification\">#3F51B5</color>\n\n <color name=\"app_"
},
{
"path": "manager/src/main/res/values/dimens.xml",
"chars": 685,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\"\n tools:ignore=\"Unuse"
},
{
"path": "manager/src/main/res/values/strings.xml",
"chars": 14974,
"preview": "<resources>\n <string name=\"app_name\" translatable=\"false\">Shizuku</string>\n\n <!-- To translators: you can leave yo"
},
{
"path": "manager/src/main/res/values/strings_untranslatable.xml",
"chars": 171,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"translation_url\" translatable=\"false\">https://rikka"
},
{
"path": "manager/src/main/res/values/styles.xml",
"chars": 4937,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <style name=\"AppTheme\" parent=\"Theme.Light\" />\n\n <style name="
},
{
"path": "manager/src/main/res/values/themes.xml",
"chars": 1102,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <style name=\"Base.AppTheme.Light\" parent=\"Theme.Material3.Light."
},
{
"path": "manager/src/main/res/values/themes_overlay.xml",
"chars": 651,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <style name=\"ThemeOverlay\"/>\n\n <style name=\"ThemeOverlay.Black"
},
{
"path": "manager/src/main/res/values/values.xml",
"chars": 181,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"main_layout_manager\" translatable=\"false\">androidx."
},
{
"path": "manager/src/main/res/values-ang/strings.xml",
"chars": 314,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <plurals name=\"home_app_management_authorized_apps_count\">\n "
},
{
"path": "manager/src/main/res/values-ar/strings.xml",
"chars": 13827,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"home_status_service_version\">الإصدار %2$s, %1$s</st"
},
{
"path": "manager/src/main/res/values-ars/strings.xml",
"chars": 673,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"dialog_adb_discovery_message\">“ارجوك شغل ”المعالجة "
},
{
"path": "manager/src/main/res/values-az/strings.xml",
"chars": 13924,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"translation_contributors\">By3lish</string>\n <str"
},
{
"path": "manager/src/main/res/values-b+es+419/strings.xml",
"chars": 12530,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"paring_code_is_wrong\">El código de emparejamiento e"
},
{
"path": "manager/src/main/res/values-bg/strings.xml",
"chars": 62,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
},
{
"path": "manager/src/main/res/values-bn/strings.xml",
"chars": 788,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"home_adb_dialog_view_command_copy_button\">নকল</stri"
},
{
"path": "manager/src/main/res/values-ca/strings.xml",
"chars": 1931,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"dialog_adb_discovery_message\">Activeu \\\"Depuració s"
},
{
"path": "manager/src/main/res/values-ckb/strings.xml",
"chars": 62,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
},
{
"path": "manager/src/main/res/values-cs/strings.xml",
"chars": 13897,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"dialog_requesting_legacy_button_open_shizuku\">Otevř"
}
]
// ... and 87 more files (download for full content)
About this extraction
This page contains the full source code of the RikkaApps/Shizuku GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 287 files (841.3 KB), approximately 231.2k tokens, and a symbol index with 276 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.