├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ ├── crowdin.yml │ ├── issue.yml │ ├── main.yml │ └── pull_request.yml ├── .gitignore ├── README.md ├── README_zh_CN.md ├── app ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── icu │ │ └── nullptr │ │ └── hidemyapplist │ │ ├── MyApp.kt │ │ ├── data │ │ └── UpdateInfo.kt │ │ ├── service │ │ ├── ConfigManager.kt │ │ ├── PrefManager.kt │ │ ├── ServiceClient.kt │ │ └── ServiceProvider.kt │ │ ├── ui │ │ ├── activity │ │ │ ├── AboutActivity.kt │ │ │ └── MainActivity.kt │ │ ├── adapter │ │ │ ├── AppManageAdapter.kt │ │ │ ├── AppScopeAdapter.kt │ │ │ ├── AppSelectAdapter.kt │ │ │ ├── LogAdapter.kt │ │ │ └── TemplateAdapter.kt │ │ ├── fragment │ │ │ ├── AppManageFragment.kt │ │ │ ├── AppSelectFragment.kt │ │ │ ├── AppSettingsFragment.kt │ │ │ ├── HomeFragment.kt │ │ │ ├── LogsFragment.kt │ │ │ ├── ScopeFragment.kt │ │ │ ├── SettingsFragment.kt │ │ │ ├── TemplateManageFragment.kt │ │ │ └── TemplateSettingsFragment.kt │ │ ├── receiver │ │ │ └── AppChangeReceiver.kt │ │ ├── util │ │ │ ├── Fragment.kt │ │ │ ├── ThemeUtils.kt │ │ │ └── Toast.kt │ │ ├── view │ │ │ ├── AppItemView.kt │ │ │ └── ListItemView.kt │ │ └── viewmodel │ │ │ ├── AppSettingsViewModel.kt │ │ │ └── TemplateSettingsViewModel.kt │ │ └── util │ │ ├── PackageHelper.kt │ │ └── SuUtils.kt │ └── res │ ├── drawable │ ├── baseline_add_24.xml │ ├── baseline_apps_24.xml │ ├── baseline_arrow_back_24.xml │ ├── baseline_assignment_24.xml │ ├── baseline_call_split_24.xml │ ├── baseline_home_24.xml │ ├── baseline_my_location_24.xml │ ├── baseline_refresh_24.xml │ ├── baseline_settings_24.xml │ ├── cont_author.webp │ ├── cont_aviraxp.webp │ ├── cont_cpp_master.webp │ ├── cont_icon_designer.webp │ ├── cont_k.webp │ ├── ic_home_checkable.xml │ ├── ic_logs_checkable.xml │ ├── ic_outline_layers_24.xml │ ├── ic_settings_checkable.xml │ ├── outline_android_24.xml │ ├── outline_assignment_24.xml │ ├── outline_backup_24.xml │ ├── outline_bug_report_24.xml │ ├── outline_cleaning_services_24.xml │ ├── outline_dark_mode_24.xml │ ├── outline_delete_24.xml │ ├── outline_discount_24.xml │ ├── outline_done_all_24.xml │ ├── outline_edit_24.xml │ ├── outline_extension_off_24.xml │ ├── outline_format_color_fill_24.xml │ ├── outline_hide_image_24.xml │ ├── outline_home_24.xml │ ├── outline_info_24.xml │ ├── outline_invert_colors_24.xml │ ├── outline_language_24.xml │ ├── outline_palette_24.xml │ ├── outline_save_24.xml │ ├── outline_sd_storage_24.xml │ ├── outline_settings_24.xml │ ├── outline_settings_backup_restore_24.xml │ ├── outline_shield_24.xml │ ├── outline_speed_24.xml │ ├── outline_stop_circle_24.xml │ ├── outline_storage_24.xml │ ├── outline_translate_24.xml │ └── outline_update_disabled_24.xml │ ├── layout │ ├── activity_main.xml │ ├── app_item_view.xml │ ├── fragment_app_select.xml │ ├── fragment_home.xml │ ├── fragment_logs.xml │ ├── fragment_settings.xml │ ├── fragment_template_manage.xml │ ├── fragment_template_settings.xml │ ├── line.xml │ ├── list_item_view.xml │ └── log_item_view.xml │ ├── menu │ ├── menu_about.xml │ ├── menu_app_list.xml │ ├── menu_delete.xml │ ├── menu_logs.xml │ └── menu_nav.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── navigation │ ├── home_nav_graph.xml │ └── main_nav_graph.xml │ ├── values-af-rZA │ └── strings.xml │ ├── values-ar-rSA │ └── strings.xml │ ├── values-ca-rES │ └── strings.xml │ ├── values-cs-rCZ │ └── strings.xml │ ├── values-da-rDK │ └── strings.xml │ ├── values-de-rDE │ └── strings.xml │ ├── values-el-rGR │ └── strings.xml │ ├── values-es-rES │ └── strings.xml │ ├── values-fi-rFI │ └── strings.xml │ ├── values-fr-rFR │ └── strings.xml │ ├── values-hu-rHU │ └── strings.xml │ ├── values-it-rIT │ └── strings.xml │ ├── values-iw-rIL │ └── strings.xml │ ├── values-ja-rJP │ └── strings.xml │ ├── values-ko-rKR │ └── strings.xml │ ├── values-night │ └── styles.xml │ ├── values-nl-rNL │ └── strings.xml │ ├── values-no-rNO │ └── strings.xml │ ├── values-pl-rPL │ └── strings.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-pt-rPT │ └── strings.xml │ ├── values-ro-rRO │ └── strings.xml │ ├── values-ru-rRU │ └── strings.xml │ ├── values-sr-rSP │ └── strings.xml │ ├── values-sv-rSE │ └── strings.xml │ ├── values-tr-rTR │ └── strings.xml │ ├── values-uk-rUA │ └── strings.xml │ ├── values-vi-rVN │ └── strings.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values │ ├── arrays.xml │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ ├── styles.xml │ ├── themes.xml │ ├── themes_custom.xml │ ├── themes_overlay.xml │ └── themes_override.xml │ └── xml │ ├── app_settings.xml │ ├── settings.xml │ └── settings_data_isolation.xml ├── banner.png ├── build.gradle.kts ├── common ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── aidl │ └── icu │ │ └── nullptr │ │ └── hidemyapplist │ │ └── common │ │ └── IHMAService.aidl │ └── java │ └── icu │ └── nullptr │ └── hidemyapplist │ └── common │ ├── CommonUtils.kt │ ├── Constants.java │ └── JsonConfig.kt ├── crowdin.yml ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── xposed ├── build.gradle.kts ├── proguard-rules.pro └── src └── main ├── assets └── xposed_init └── java └── icu └── nullptr └── hidemyapplist └── xposed ├── HMAService.kt ├── Logcat.kt ├── UserService.kt ├── Utils.kt ├── XposedEntry.kt └── hook ├── IFrameworkHook.kt ├── PlatformCompatHook.kt ├── PmsHookTarget28.kt ├── PmsHookTarget30.kt ├── PmsHookTarget33.kt └── PmsHookTarget34.kt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto eol=lf 3 | 4 | # Declare files that will always have CRLF line endings on checkout. 5 | *.cmd text eol=crlf 6 | *.bat text eol=crlf 7 | 8 | # Denote all files that are truly binary and should not be modified. 9 | *.so binary 10 | *.dex binary 11 | *.jar binary 12 | *.png binary 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Dr-TSNG] 2 | custom: ['https://afdian.com/a/dr-tsng'] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report/反馈 Bug 2 | description: Report errors or unexpected behavior./反馈错误或异常行为。 3 | labels: [bug] 4 | title: "[Bug] " 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for reporting issues of Hide-My-Applist! 10 | 11 | To prevent spam, please fill in the title after "[Bug]". 12 | 13 | To make it easier for us to help you please enter detailed information below. 14 | 15 | 感谢给 Hide-My-Applist 汇报问题! 16 | 为了使我们更好地帮助你,请提供以下信息。 17 | 为了防止重复汇报与垃圾信息,标题请务必使用英文,在“[Bug]”之后填写内容。 18 | - type: textarea 19 | attributes: 20 | label: Steps to reproduce/复现步骤 21 | placeholder: | 22 | 1. 23 | 2. 24 | 3. 25 | validations: 26 | required: true 27 | - type: textarea 28 | attributes: 29 | label: Expected behaviour/预期行为 30 | placeholder: Tell us what should happen/正常情况下应该发生什么 31 | validations: 32 | required: true 33 | - type: textarea 34 | attributes: 35 | label: Actual behaviour/实际行为 36 | placeholder: Tell us what happens instead/实际上发生了什么 37 | validations: 38 | required: true 39 | - type: textarea 40 | attributes: 41 | label: Xposed Module List/Xposed 模块列表 42 | render: Shell 43 | validations: 44 | required: true 45 | - type: textarea 46 | attributes: 47 | label: Magisk Module List/Magisk 模块列表 48 | render: Shell 49 | validations: 50 | required: true 51 | - type: input 52 | attributes: 53 | label: Hide-My-Applist version/Hide-My-Applist 版本 54 | description: Please check [Actions](https://github.com/Dr-TSNG/Hide-My-Applist/actions/workflows/main.yml) for the latest CI debug build. Don't just type "latest". Specify actual version (**MUST** contains `.rXXX.XXX-debug`), otherwise your issue will be closed./请前往 [Actions](https://github.com/Dr-TSNG/Hide-My-Applist/actions/workflows/main.yml) 获取最新的 CI debug 版本。不要直接填用“最新版”。版本号**必须**包含 `·rXXX.XXX-debug`,否则 issue 会被自动关闭。 55 | placeholder: V_._._(-Beta).r___._______-debug 56 | validations: 57 | required: true 58 | - type: input 59 | attributes: 60 | label: Android version/Android 版本 61 | validations: 62 | required: true 63 | - type: input 64 | attributes: 65 | label: Magisk version/Magisk 版本 66 | validations: 67 | required: true 68 | - type: dropdown 69 | id: latest 70 | attributes: 71 | label: Version requirement/版本要求 72 | description: Select the version being used for feedback./选择反馈时正使用的版本。 73 | multiple: false 74 | options: 75 | - Latest CI debug build/最新 CI debug 构建 76 | - Version required by the developer/开发者要求的版本 77 | - Public release/beta version/公开发布/测试版 78 | - Other/其他 79 | validations: 80 | required: true 81 | - type: textarea 82 | attributes: 83 | label: Logs/日志 84 | description: For usage issues, please provide the log zip saved from manager; for activation issues, please provide [bugreport](https://developer.android.com/studio/debug/bug-report). Without log, the issue will be closed. /使用问题请提供从管理器保存的日志压缩包;激活问题请提供 [bugreport](https://developer.android.google.cn/studio/debug/bug-report?hl=zh-cn) 日志。无日志提交会被关闭。 85 | placeholder: Upload logs by clicking the bar on the bottom. /点击文本框底栏上传日志文件。 86 | validations: 87 | required: true 88 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/workflows/crowdin.yml: -------------------------------------------------------------------------------- 1 | name: Crowdin Action 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ master ] 7 | paths: 8 | - app/src/main/res/values/strings.xml 9 | 10 | jobs: 11 | synchronize-with-crowdin: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: crowdin action 18 | uses: crowdin/github-action@master 19 | with: 20 | upload_translations: false 21 | download_translations: false 22 | upload_sources: true 23 | config: 'crowdin.yml' 24 | crowdin_branch_name: master 25 | env: 26 | CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} 27 | CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths-ignore: 7 | - '.github/ISSUE_TEMPLATE/**' 8 | - '.github/workflows/issue.yml' 9 | - '**.md' 10 | 11 | jobs: 12 | build: 13 | name: Build on ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ ubuntu-latest ] 19 | if: ${{ !startsWith(github.event.head_commit.message, '[skip ci]') }} 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | with: 25 | submodules: 'recursive' 26 | fetch-depth: 0 27 | 28 | - name: Gradle wrapper validation 29 | uses: gradle/wrapper-validation-action@v3 30 | 31 | - name: Set up JDK 21 32 | uses: actions/setup-java@v4 33 | with: 34 | distribution: 'temurin' 35 | java-version: '21' 36 | 37 | - name: Write key 38 | if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' 39 | run: | 40 | echo officialBuild=true >> local.properties 41 | echo buildWithGitSuffix=true >> local.properties 42 | echo storePassword='${{ secrets.KEY_STORE_PASSWORD }}' >> local.properties 43 | echo keyAlias='${{ secrets.ALIAS }}' >> local.properties 44 | echo keyPassword='${{ secrets.ALIAS_KEY_PASSWORD }}' >> local.properties 45 | echo fileDir=`pwd`/key.jks >> local.properties 46 | echo "${{ secrets.KEY_STORE }}" | base64 --decode > key.jks 47 | echo "${{ secrets.GOOGLE_SERVICES_JSON }}" | base64 --decode > app/google-services.json 48 | 49 | - name: Cache gradle 50 | uses: actions/cache@v4 51 | with: 52 | path: | 53 | ~/.gradle/caches 54 | ~/.gradle/wrapper 55 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 56 | restore-keys: ${{ runner.os }}-gradle- 57 | 58 | - name: Gradle prebuild 59 | run: | 60 | echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties 61 | ./gradlew prebuild 62 | 63 | - name: Build release 64 | id: buildRelease 65 | run: | 66 | ./gradlew :app:buildRelease 67 | echo "releaseName=$(ls app/build/apk/release/HMA*-release.apk | awk -F '(/|.apk)' '{print $5}')" >> $GITHUB_OUTPUT 68 | 69 | - name: Build debug 70 | id: buildDebug 71 | run: | 72 | ./gradlew :app:buildDebug 73 | echo "debugName=$(ls app/build/apk/debug/HMA*-debug.apk | awk -F '(/|.apk)' '{print $5}')" >> $GITHUB_OUTPUT 74 | 75 | - name: Upload release 76 | if: success() 77 | uses: actions/upload-artifact@v4 78 | with: 79 | name: ${{ steps.buildRelease.outputs.releaseName }} 80 | path: "app/build/apk/release/HMA*-release.apk" 81 | 82 | - name: Upload debug 83 | if: success() 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: ${{ steps.buildDebug.outputs.debugName }} 87 | path: "app/build/apk/debug/HMA*-debug.apk" 88 | 89 | - name: Upload mappings 90 | uses: actions/upload-artifact@v4 91 | with: 92 | name: mappings 93 | path: "app/build/outputs/mapping/release" 94 | 95 | - name: Post to group 96 | if: ${{ github.event_name != 'pull_request' && success() && github.ref == 'refs/heads/master' }} 97 | env: 98 | CHANNEL_ID: ${{ secrets.TELEGRAM_CHANNEL }} 99 | TOPIC_ID: ${{ secrets.TELEGRAM_TOPIC }} 100 | BOT_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} 101 | COMMIT_URL: ${{ github.event.head_commit.url }} 102 | COMMIT_MESSAGE: ${{ github.event.head_commit.message }} 103 | run: | 104 | OUTPUT="app/build/apk/" 105 | export release=$(find $OUTPUT -name "HMA*-release.apk") 106 | export debug=$(find $OUTPUT -name "HMA*-debug.apk") 107 | ESCAPED=`python3 -c 'import json,os,urllib.parse; msg = json.dumps(os.environ["COMMIT_MESSAGE"]); print(urllib.parse.quote(msg if len(msg) <= 1024 else json.dumps(os.environ["COMMIT_URL"])))'` 108 | curl -v "https://api.telegram.org/bot${BOT_TOKEN}/sendMediaGroup?chat_id=${CHANNEL_ID}&message_thread_id=${TOPIC_ID}&media=%5B%7B%22type%22:%22document%22,%20%22media%22:%22attach://release%22%7D,%7B%22type%22:%22document%22,%20%22media%22:%22attach://debug%22,%22caption%22:${ESCAPED}%7D%5D" -F release="@$release" -F debug="@$debug" 109 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | name: Build on ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [ ubuntu-latest ] 13 | if: ${{ !startsWith(github.event.head_commit.message, 'docs:') }} 14 | 15 | steps: 16 | - name: Check out 17 | uses: actions/checkout@v4 18 | with: 19 | submodules: 'recursive' 20 | fetch-depth: 0 21 | 22 | - name: Gradle wrapper validation 23 | uses: gradle/wrapper-validation-action@v3 24 | 25 | - name: Set up JDK 21 26 | uses: actions/setup-java@v4 27 | with: 28 | java-version: '21' 29 | distribution: 'temurin' 30 | 31 | - name: Write properties 32 | run: | 33 | echo buildWithGitSuffix=true >> local.properties 34 | 35 | - name: Gradle prebuild 36 | run: | 37 | echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties 38 | ./gradlew prebuild 39 | 40 | - name: Build debug 41 | id: buildDebug 42 | run: | 43 | ./gradlew :app:assembleDebug 44 | echo "debugName=$(ls app/build/apk/debug/HMA*-debug.apk | awk -F '(/|.apk)' '{print $6}')" >> $GITHUB_OUTPUT 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | release/ 19 | app/debug 20 | 21 | # Gradle files 22 | .gradle/ 23 | build/ 24 | 25 | # Local configuration file (sdk path, etc) 26 | local.properties 27 | 28 | # Proguard folder generated by Eclipse 29 | proguard/ 30 | 31 | # Log Files 32 | *.log 33 | 34 | # Android Studio Navigation editor temp files 35 | .navigation/ 36 | 37 | # Android Studio captures folder 38 | captures/ 39 | 40 | # IntelliJ 41 | *.iml 42 | .idea/ 43 | 44 | # Keystore files 45 | # Uncomment the following lines if you do not want to check your keystore files in. 46 | #*.jks 47 | #*.keystore 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | .cxx/ 52 | 53 | # Google Services (e.g. APIs or Firebase) 54 | google-services.json 55 | 56 | # Freeline 57 | freeline.py 58 | freeline/ 59 | freeline_project_description.json 60 | 61 | # fastlane 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | fastlane/readme.md 67 | 68 | # Version control 69 | vcs.xml 70 | 71 | # lint 72 | lint/intermediates/ 73 | lint/generated/ 74 | lint/outputs/ 75 | lint/tmp/ 76 | # lint/reports/ 77 | 78 | # Android Profiling 79 | *.hprof 80 | 81 | updates/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hide My Applist 2 | 3 | [![Stars](https://img.shields.io/github/stars/Dr-TSNG/Hide-My-Applist?label=Stars)](https://github.com/Dr-TSNG) 4 | [![Crowdin](https://badges.crowdin.net/hide-my-applist/localized.svg)](https://crowdin.com/project/hide-my-applist) 5 | [![Build](https://img.shields.io/github/actions/workflow/status/Dr-TSNG/Hide-My-Applist/main.yml?branch=master&logo=github)](https://github.com/Dr-TSNG/Hide-My-Applist/actions) 6 | [![Release](https://img.shields.io/github/v/release/Dr-TSNG/Hide-My-Applist?label=Release)](https://github.com/Dr-TSNG/Hide-My-Applist/releases/latest) 7 | [![Download](https://img.shields.io/github/downloads/Dr-TSNG/Hide-My-Applist/total)](https://github.com/Dr-TSNG/Hide-My-Applist/releases/latest) 8 | [![Channel](https://img.shields.io/badge/Telegram-Channel-blue.svg?logo=telegram)](https://t.me/HideMyApplist) 9 | [![License](https://img.shields.io/github/license/Dr-TSNG/Hide-My-Applist?label=License)](https://choosealicense.com/licenses/gpl-3.0/) 10 | 11 | ![banner](banner.png) 12 | 13 | - English 14 | - [中文(简体)](README_zh_CN.md) 15 | 16 | ## About this module 17 | 18 | Although it's bad practice to detect the installation of specific apps, not every app using root provides random package name support. In this case, if apps related to root (such as Fake Location and Storage Isolation) are detected, it is tantamount to detecting that the device is rooted. 19 | 20 | Additionally, some apps use various loopholes to acquire your app list, in order to use it as fingerprinting data or for other nefarious purposes. 21 | 22 | This module can work as an Xposed module to hide apps or reject app list requests, and provides some methods to test whether you have hidden your app list properly. 23 | 24 | ## Copyright Notice 25 | 26 | Copyright © 2025 HMA developers. All rights reserved. 27 | 28 | The software Hide My Applist, starting from version v3.4, is no longer under the AGPL-3.0 License. Instead, certain rights to the software are reserved by the owner. 29 | 30 | The following conditions now apply: 31 | 32 | 1. **No Modifications**: The software may not be modified in any way. This includes but is not limited to changing, adding, or removing any part of the software's code or functionality. 33 | 34 | 2. **No Redistribution**: The software may not be redistributed in any form. This includes but is not limited to renaming, selling, or including the software as part of another project. 35 | 36 | 3. **No Picking without Credit**: No parts, pieces, or components of the software may be extracted and submitted to other projects without proper credit. This includes, but is not limited to, code snippets, functions, and released binaries. 37 | 38 | 4. **No Claim to Succession**: Any fork of the software that was created before the license change may not claim to be an official or unofficial successor to the project. This includes but is not limited to using the project's name, branding, or reputation to imply a connection to the original project. 39 | 40 | ## Translation Contributing 41 | You can contribute translation [here](https://crowdin.com/project/hide-my-applist). 42 | 43 | ## Update Log 44 | [Reference to the release page](https://github.com/Dr-TSNG/Hide-My-Applist/releases) 45 | -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 | # Hide My Applist 2 | 3 | [![Stars](https://img.shields.io/github/stars/Dr-TSNG/Hide-My-Applist?label=Stars)](https://github.com/Dr-TSNG) 4 | [![Crowdin](https://badges.crowdin.net/hide-my-applist/localized.svg)](https://crowdin.com/project/hide-my-applist) 5 | [![Build](https://img.shields.io/github/actions/workflow/status/Dr-TSNG/Hide-My-Applist/main.yml?branch=master&logo=github)](https://github.com/Dr-TSNG/Hide-My-Applist/actions) 6 | [![Release](https://img.shields.io/github/v/release/Dr-TSNG/Hide-My-Applist?label=Release)](https://github.com/Dr-TSNG/Hide-My-Applist/releases/latest) 7 | [![Download](https://img.shields.io/github/downloads/Dr-TSNG/Hide-My-Applist/total)](https://github.com/Dr-TSNG/Hide-My-Applist/releases/latest) 8 | [![Channel](https://img.shields.io/badge/Telegram-Channel-blue.svg?logo=telegram)](https://t.me/HideMyApplist) 9 | [![License](https://img.shields.io/github/license/Dr-TSNG/Hide-My-Applist?label=License)](https://choosealicense.com/licenses/gpl-3.0/) 10 | 11 | ![banner](banner.png) 12 | 13 | - [English](README.md) 14 | - 中文(简体) 15 | 16 | ## 关于该模块 17 | 虽然“检测安装的应用”是不正确的做法,但是并不是所有的与 root 相关联的插件类应用都提供了随机包名支持。这就意味着检测到安装了此类应用(如 Fake Location 、存储空间隔离)与检测到了 root 本身区别不大。(会使用检测手段的 app 可不会认为你是在“我就蹭蹭不进去”) 18 | 与此同时,部分“不安分”的应用会使用各种漏洞绕过系统权限来获取你的应用列表,从而对你建立用户画像。(如陈叔叔将安装了 V2Ray 的用户分为一类),或是类似于某某校园某某乐跑的软件会要求你卸载作弊软件。 19 | 该模块提供了一些检测方式用于测试您是否成功地隐藏了某些特定的包名,如 Magisk/Edxposed Manager;同时可作为 Xposed 模块用于隐藏应用列表或特定应用,保护隐私。 20 | 21 | ## 版权声明 22 | 版权所有 © 2025 HMA 开发者。保留所有权利。 23 | 24 | 从版本 v3.4 开始,Hide My Applist 不再适用 AGPL-3.0 许可证。相反,某些权利将由所有者保留。 25 | 26 | 以下条件现适用: 27 | 28 | 1. **禁止修改**:不得以任何方式修改软件。这包括但不限于更改、添加或删除软件的任何部分代码或功能。 29 | 30 | 2. **禁止再分发**:不得以任何形式再分发软件。这包括但不限于重新命名、销售或将软件作为其他项目的一部分。 31 | 32 | 3. **禁止不注明出处的摘取**:不得提取软件的任何部分、片段或组件并提交到其他项目中,除非以合适方式注明出处。这包括但不限于代码片段、函数和已发布的二进制文件。 33 | 34 | 4. **禁止声称继承**:在许可证变更之前创建的任何软件分支均不得声称是该项目的官方或非官方继承者。这包括但不限于使用项目的名称、品牌或声誉来暗示与原项目的关联。 35 | 36 | ## 更新日志 37 | [参考发布页面](https://github.com/Dr-TSNG/Hide-My-Applist/releases) 38 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Xposed 2 | -keepclassmembers class icu.nullptr.hidemyapplist.MyApp { 3 | boolean isHooked; 4 | } 5 | 6 | # Enum class 7 | -keepclassmembers,allowoptimization enum * { 8 | public static **[] values(); 9 | public static ** valueOf(java.lang.String); 10 | } 11 | 12 | -keep class icu.nullptr.hidemyapplist.data.UpdateData { *; } 13 | -keep class icu.nullptr.hidemyapplist.data.UpdateData$* { *; } 14 | 15 | -keep,allowoptimization class * extends androidx.preference.PreferenceFragmentCompat 16 | -keepclassmembers class com.tsng.hidemyapplist.databinding.** { 17 | public ; 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 18 | 19 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | 52 | 55 | 58 | 61 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicianGuo/Hide-My-Applist/9fef3f069a79ea58cc44d44423bfae793d6a2093/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/MyApp.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Application 5 | import androidx.appcompat.app.AppCompatDelegate 6 | import com.tsng.hidemyapplist.R 7 | import icu.nullptr.hidemyapplist.service.ConfigManager 8 | import icu.nullptr.hidemyapplist.service.PrefManager 9 | import icu.nullptr.hidemyapplist.ui.receiver.AppChangeReceiver 10 | import icu.nullptr.hidemyapplist.ui.util.makeToast 11 | import kotlinx.coroutines.CoroutineScope 12 | import kotlinx.coroutines.Dispatchers 13 | import me.zhanghai.android.appiconloader.AppIconLoader 14 | import rikka.material.app.LocaleDelegate 15 | import java.util.* 16 | import kotlin.system.exitProcess 17 | 18 | lateinit var hmaApp: MyApp 19 | 20 | class MyApp : Application() { 21 | 22 | @JvmField 23 | var isHooked = false 24 | 25 | val globalScope = CoroutineScope(Dispatchers.Default) 26 | val appIconLoader by lazy { 27 | val iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size) 28 | AppIconLoader(iconSize, false, this) 29 | } 30 | 31 | @Suppress("DEPRECATION") 32 | @SuppressLint("SdCardPath") 33 | override fun onCreate() { 34 | super.onCreate() 35 | hmaApp = this 36 | if (!filesDir.absolutePath.startsWith("/data/user/0/")) { 37 | makeToast(R.string.do_not_dual) 38 | exitProcess(0) 39 | } 40 | AppChangeReceiver.register(this) 41 | ConfigManager.init() 42 | 43 | AppCompatDelegate.setDefaultNightMode(PrefManager.darkTheme) 44 | LocaleDelegate.defaultLocale = getLocale(PrefManager.locale) 45 | val config = resources.configuration 46 | config.setLocale(LocaleDelegate.defaultLocale) 47 | resources.updateConfiguration(config, resources.displayMetrics) 48 | } 49 | 50 | fun getLocale(tag: String): Locale { 51 | return if (tag == "SYSTEM") LocaleDelegate.systemLocale 52 | else Locale.forLanguageTag(tag) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/data/UpdateInfo.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.data 2 | 3 | import icu.nullptr.hidemyapplist.common.Constants 4 | import icu.nullptr.hidemyapplist.service.PrefManager 5 | import kotlinx.serialization.Serializable 6 | import rxhttp.* 7 | import rxhttp.wrapper.param.RxHttp 8 | import java.util.* 9 | 10 | class UpdateInfo( 11 | val versionName: String, 12 | val versionCode: Int, 13 | val content: String, 14 | val downloadUrl: String 15 | ) 16 | 17 | @Serializable 18 | private data class UpdateData( 19 | val release: Item?, 20 | val beta: Item? 21 | ) { 22 | @Serializable 23 | data class Item( 24 | val versionName: String, 25 | val versionCode: Int, 26 | val downloadUrl: String 27 | ) 28 | } 29 | 30 | suspend fun fetchLatestUpdate(): UpdateInfo? { 31 | val updateData = RxHttp.get(Constants.UPDATE_URL_BASE + "updates.json") 32 | .toAwait() 33 | .tryAwait() ?: return null 34 | val isBeta = PrefManager.receiveBetaUpdate && updateData.beta != null 35 | val item = (if (isBeta) updateData.beta else updateData.release) ?: return null 36 | val variantPrefix = if (isBeta) "beta" else "release" 37 | val languagePrefix = if (Locale.getDefault().language.contains("zh")) "zh" else "en" 38 | val content = RxHttp.get(Constants.UPDATE_URL_BASE + variantPrefix + "-" + languagePrefix + ".html") 39 | .toAwaitString() 40 | .tryAwait() ?: return null 41 | return UpdateInfo(item.versionName, item.versionCode, content, item.downloadUrl) 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/service/PrefManager.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.service 2 | 3 | import android.content.ComponentName 4 | import android.content.Context.MODE_PRIVATE 5 | import android.content.pm.PackageManager 6 | import androidx.appcompat.app.AppCompatDelegate 7 | import icu.nullptr.hidemyapplist.hmaApp 8 | 9 | object PrefManager { 10 | 11 | private const val PREF_LAST_VERSION = "last_version" 12 | 13 | private const val PREF_LOCALE = "language" 14 | 15 | private const val PREF_DARK_THEME = "dark_theme" 16 | private const val PREF_BLACK_DARK_THEME = "black_dark_theme" 17 | private const val PREF_FOLLOW_SYSTEM_ACCENT = "follow_system_accent" 18 | private const val PREF_THEME_COLOR = "theme_color" 19 | 20 | private const val PREF_HIDE_ICON = "hide_icon" 21 | private const val PREF_DISABLE_UPDATE = "disable_update" 22 | private const val PREF_RECEIVE_BETA_UPDATE = "receive_beta_update" 23 | 24 | private const val PREF_APP_FILTER_SHOW_SYSTEM = "app_filter_show_system" 25 | private const val PREF_APP_FILTER_SORT_METHOD = "app_filter_sort_method" 26 | private const val PREF_APP_FILTER_REVERSE_ORDER = "app_filter_reverse_order" 27 | private const val PREF_LOG_FILTER_LEVEL = "log_filter_level" 28 | private const val PREF_LOG_FILTER_REVERSE_ORDER = "log_filter_reverse_order" 29 | 30 | enum class SortMethod { 31 | BY_LABEL, BY_PACKAGE_NAME, BY_INSTALL_TIME, BY_UPDATE_TIME 32 | } 33 | 34 | private val pref = hmaApp.getSharedPreferences("settings", MODE_PRIVATE) 35 | 36 | var lastVersion: Int 37 | get() = pref.getInt(PREF_LAST_VERSION, 0) 38 | set(value) = pref.edit().putInt(PREF_LAST_VERSION, value).apply() 39 | 40 | var locale: String 41 | get() = pref.getString(PREF_LOCALE, "SYSTEM")!! 42 | set(value) = pref.edit().putString(PREF_LOCALE, value).apply() 43 | 44 | var darkTheme: Int 45 | get() = pref.getInt(PREF_DARK_THEME, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) 46 | set(value) = pref.edit().putInt(PREF_DARK_THEME, value).apply() 47 | 48 | var blackDarkTheme: Boolean 49 | get() = pref.getBoolean(PREF_BLACK_DARK_THEME, false) 50 | set(value) = pref.edit().putBoolean(PREF_BLACK_DARK_THEME, value).apply() 51 | 52 | var followSystemAccent: Boolean 53 | get() = pref.getBoolean(PREF_FOLLOW_SYSTEM_ACCENT, true) 54 | set(value) = pref.edit().putBoolean(PREF_FOLLOW_SYSTEM_ACCENT, value).apply() 55 | 56 | var themeColor: String 57 | get() = pref.getString(PREF_THEME_COLOR, "MATERIAL_BLUE")!! 58 | set(value) = pref.edit().putString(PREF_THEME_COLOR, value).apply() 59 | 60 | var hideIcon: Boolean 61 | get() = pref.getBoolean(PREF_HIDE_ICON, false) 62 | set(value) { 63 | pref.edit().putBoolean(PREF_HIDE_ICON, value).apply() 64 | val component = ComponentName(hmaApp, "com.tsng.hidemyapplist.MainActivityLauncher") 65 | val status = 66 | if (value) PackageManager.COMPONENT_ENABLED_STATE_DISABLED 67 | else PackageManager.COMPONENT_ENABLED_STATE_ENABLED 68 | hmaApp.packageManager.setComponentEnabledSetting(component, status, PackageManager.DONT_KILL_APP) 69 | } 70 | 71 | var disableUpdate: Boolean 72 | get() = pref.getBoolean(PREF_DISABLE_UPDATE, false) 73 | set(value) = pref.edit().putBoolean(PREF_DISABLE_UPDATE, value).apply() 74 | 75 | var receiveBetaUpdate: Boolean 76 | get() = pref.getBoolean(PREF_RECEIVE_BETA_UPDATE, false) 77 | set(value) = pref.edit().putBoolean(PREF_RECEIVE_BETA_UPDATE, value).apply() 78 | 79 | var appFilter_showSystem: Boolean 80 | get() = pref.getBoolean(PREF_APP_FILTER_SHOW_SYSTEM, false) 81 | set(value) = pref.edit().putBoolean(PREF_APP_FILTER_SHOW_SYSTEM, value).apply() 82 | 83 | var appFilter_sortMethod: SortMethod 84 | get() = SortMethod.values()[pref.getInt(PREF_APP_FILTER_SORT_METHOD, SortMethod.BY_LABEL.ordinal)] 85 | set(value) = pref.edit().putInt(PREF_APP_FILTER_SORT_METHOD, value.ordinal).apply() 86 | 87 | var appFilter_reverseOrder: Boolean 88 | get() = pref.getBoolean(PREF_APP_FILTER_REVERSE_ORDER, false) 89 | set(value) = pref.edit().putBoolean(PREF_APP_FILTER_REVERSE_ORDER, value).apply() 90 | 91 | var logFilter_level: Int 92 | get() = pref.getInt(PREF_LOG_FILTER_LEVEL, 0) 93 | set(value) = pref.edit().putInt(PREF_LOG_FILTER_LEVEL, value).apply() 94 | 95 | var logFilter_reverseOrder: Boolean 96 | get() = pref.getBoolean(PREF_LOG_FILTER_REVERSE_ORDER, false) 97 | set(value) = pref.edit().putBoolean(PREF_LOG_FILTER_REVERSE_ORDER, value).apply() 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.service 2 | 3 | import android.os.IBinder 4 | import android.os.IBinder.DeathRecipient 5 | import android.os.Parcel 6 | import android.os.RemoteException 7 | import android.os.ServiceManager 8 | import android.util.Log 9 | import icu.nullptr.hidemyapplist.common.Constants 10 | import icu.nullptr.hidemyapplist.common.IHMAService 11 | import java.lang.reflect.InvocationHandler 12 | import java.lang.reflect.Method 13 | import java.lang.reflect.Proxy 14 | 15 | object ServiceClient : IHMAService, DeathRecipient { 16 | 17 | private const val TAG = "ServiceClient" 18 | 19 | private class ServiceProxy(private val obj: IHMAService) : InvocationHandler { 20 | override fun invoke(proxy: Any?, method: Method, args: Array?): Any? { 21 | val result = method.invoke(obj, *args.orEmpty()) 22 | if (result == null) Log.i(TAG, "Call service method ${method.name}") 23 | else Log.i(TAG, "Call service method ${method.name} with result " + result.toString().take(20)) 24 | return result 25 | } 26 | } 27 | 28 | @Volatile 29 | private var service: IHMAService? = null 30 | 31 | fun linkService(binder: IBinder) { 32 | service = Proxy.newProxyInstance( 33 | javaClass.classLoader, 34 | arrayOf(IHMAService::class.java), 35 | ServiceProxy(IHMAService.Stub.asInterface(binder)) 36 | ) as IHMAService 37 | binder.linkToDeath(this, 0) 38 | } 39 | 40 | private fun getServiceLegacy(): IHMAService? { 41 | if (service != null) return service 42 | val pm = ServiceManager.getService("package") 43 | val data = Parcel.obtain() 44 | val reply = Parcel.obtain() 45 | val remote = try { 46 | data.writeInterfaceToken(Constants.DESCRIPTOR) 47 | data.writeInt(Constants.ACTION_GET_BINDER) 48 | pm.transact(Constants.TRANSACTION, data, reply, 0) 49 | reply.readException() 50 | val binder = reply.readStrongBinder() 51 | IHMAService.Stub.asInterface(binder) 52 | } catch (e: RemoteException) { 53 | Log.d(TAG, "Failed to get binder") 54 | null 55 | } finally { 56 | data.recycle() 57 | reply.recycle() 58 | } 59 | if (remote != null) { 60 | Log.i(TAG, "Binder acquired") 61 | remote.asBinder().linkToDeath(this, 0) 62 | service = Proxy.newProxyInstance( 63 | javaClass.classLoader, 64 | arrayOf(IHMAService::class.java), 65 | ServiceProxy(remote) 66 | ) as IHMAService 67 | } 68 | return service 69 | } 70 | 71 | override fun binderDied() { 72 | service = null 73 | Log.e(TAG, "Binder died") 74 | } 75 | 76 | override fun asBinder() = service?.asBinder() 77 | 78 | override fun getServiceVersion() = getServiceLegacy()?.serviceVersion ?: 0 79 | 80 | override fun getFilterCount() = getServiceLegacy()?.filterCount ?: 0 81 | 82 | override fun getLogs() = getServiceLegacy()?.logs 83 | 84 | override fun clearLogs() { 85 | getServiceLegacy()?.clearLogs() 86 | } 87 | 88 | override fun syncConfig(json: String) { 89 | getServiceLegacy()?.syncConfig(json) 90 | } 91 | 92 | override fun stopService(cleanEnv: Boolean) { 93 | getServiceLegacy()?.stopService(cleanEnv) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceProvider.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.service 2 | 3 | import android.content.ContentProvider 4 | import android.content.ContentValues 5 | import android.net.Uri 6 | import android.os.Bundle 7 | 8 | class ServiceProvider : ContentProvider() { 9 | 10 | override fun onCreate() = false 11 | 12 | override fun query(p0: Uri, p1: Array?, p2: String?, p3: Array?, p4: String?) = null 13 | 14 | override fun getType(p0: Uri) = null 15 | 16 | override fun insert(p0: Uri, p1: ContentValues?) = null 17 | 18 | override fun delete(p0: Uri, p1: String?, p2: Array?) = 0 19 | 20 | override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array?) = 0 21 | 22 | override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { 23 | if (callingPackage != "android" || extras == null) return null 24 | val binder = extras.getBinder("binder") ?: return null 25 | ServiceClient.linkService(binder) 26 | return Bundle() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/activity/AboutActivity.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.activity 2 | 3 | import android.annotation.SuppressLint 4 | import android.widget.ImageView 5 | import android.widget.TextView 6 | import com.drakeet.about.* 7 | import com.tsng.hidemyapplist.BuildConfig 8 | import com.tsng.hidemyapplist.R 9 | 10 | class AboutActivity : AbsAboutActivity() { 11 | 12 | @SuppressLint("SetTextI18n") 13 | override fun onCreateHeader(icon: ImageView, slogan: TextView, version: TextView) { 14 | icon.setImageResource(R.mipmap.ic_launcher) 15 | slogan.text = applicationInfo.loadLabel(packageManager) 16 | version.text = "V" + BuildConfig.VERSION_NAME 17 | } 18 | 19 | override fun onItemsCreated(items: MutableList) { 20 | items.add(Category(getString(R.string.title_about))) 21 | items.add(Card(getString(R.string.about_description))) 22 | 23 | items.add(Category(getString(R.string.about_how_to_use_title))) 24 | items.add(Card(getString(R.string.about_how_to_use_description_1))) 25 | items.add(Line()) 26 | items.add(Card(getString(R.string.about_how_to_use_description_2))) 27 | 28 | items.add(Category(getString(R.string.about_developer))) 29 | items.add(Contributor(R.drawable.cont_author, "\uD835\uDD93\uD835\uDD9A\uD835\uDD91\uD835\uDD91\uD835\uDD95\uD835\uDD99\uD835\uDD97", "Developer", "https://github.com/Dr-TSNG")) 30 | items.add(Line()) 31 | items.add(Contributor(R.drawable.cont_k, "Ketal", "Collaborator", "https://github.com/keta1")) 32 | items.add(Line()) 33 | items.add(Contributor(R.drawable.cont_aviraxp, "aviraxp", "Collaborator", "https://github.com/aviraxp")) 34 | items.add(Line()) 35 | items.add(Contributor(R.drawable.cont_icon_designer, "辉少菌", "Icon designer", "http://www.coolapk.com/u/1560270")) 36 | items.add(Line()) 37 | items.add(Contributor(R.drawable.cont_cpp_master, "LoveSy", "Idea provider", "https://github.com/yujincheng08")) 38 | 39 | items.add(Category(getString(R.string.about_support))) 40 | items.add(Card("Github\nhttps://github.com/Dr-TSNG/Hide-My-Applist")) 41 | items.add(Line()) 42 | items.add(Card("Telegram\nhttps://t.me/HideMyApplist")) 43 | items.add(Line()) 44 | items.add(Card("Play store\nhttps://play.google.com/store/apps/details?id=com.tsng.hidemyapplist")) 45 | 46 | items.add(Category(getString(R.string.about_open_source))) 47 | items.add(License("MultiType", "drakeet", License.APACHE_2, "https://github.com/drakeet/MultiType")) 48 | items.add(License("about-page", "drakeet", License.APACHE_2, "https://github.com/drakeet/about-page")) 49 | items.add(License("EzXHelper", "KyuubiRan", License.APACHE_2, "https://github.com/KyuubiRan/EzXHelper")) 50 | items.add(License("libsu", "topjohnwu", License.APACHE_2, "https://github.com/topjohnwu/libsu")) 51 | items.add(License("okhttp", "square", License.APACHE_2, "https://github.com/square/okhttp")) 52 | items.add(License("rxhttp", "liujingxing", License.APACHE_2, "https://github.com/liujingxing/rxhttp")) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.activity 2 | 3 | import android.content.res.Resources 4 | import android.graphics.Color 5 | import android.os.Bundle 6 | import androidx.navigation.findNavController 7 | import androidx.navigation.fragment.NavHostFragment 8 | import androidx.navigation.ui.NavigationUI.setupWithNavController 9 | import com.google.android.gms.ads.MobileAds 10 | import com.google.android.material.color.DynamicColors 11 | import com.tsng.hidemyapplist.R 12 | import com.tsng.hidemyapplist.databinding.ActivityMainBinding 13 | import icu.nullptr.hidemyapplist.ui.util.ThemeUtils 14 | import rikka.material.app.MaterialActivity 15 | 16 | class MainActivity : MaterialActivity() { 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | val binding = ActivityMainBinding.inflate(layoutInflater) 21 | setContentView(binding.root) 22 | 23 | if (ThemeUtils.isSystemAccent) DynamicColors.applyToActivityIfAvailable(this) 24 | val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment 25 | val navController = navHostFragment.navController 26 | setupWithNavController(binding.bottomNav, navController) 27 | 28 | MobileAds.initialize(this) 29 | } 30 | 31 | override fun onSupportNavigateUp(): Boolean { 32 | val navController = this.findNavController(R.id.nav_host_fragment) 33 | return navController.navigateUp() || super.onSupportNavigateUp() 34 | } 35 | 36 | override fun onApplyUserThemeResource(theme: Resources.Theme, isDecorView: Boolean) { 37 | if (!ThemeUtils.isSystemAccent) theme.applyStyle(ThemeUtils.colorThemeStyleRes, true) 38 | theme.applyStyle(ThemeUtils.getNightThemeStyleRes(this), true) 39 | } 40 | 41 | override fun computeUserThemeKey() = ThemeUtils.colorTheme + ThemeUtils.getNightThemeStyleRes(this) 42 | 43 | override fun onApplyTranslucentSystemBars() { 44 | super.onApplyTranslucentSystemBars() 45 | window.statusBarColor = Color.TRANSPARENT 46 | window.navigationBarColor = Color.TRANSPARENT 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppManageAdapter.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.adapter 2 | 3 | import android.view.ViewGroup 4 | import icu.nullptr.hidemyapplist.service.ConfigManager 5 | import icu.nullptr.hidemyapplist.ui.view.AppItemView 6 | 7 | class AppManageAdapter( 8 | private val onItemClickListener: (String) -> Unit 9 | ) : AppSelectAdapter() { 10 | 11 | inner class ViewHolder(view: AppItemView) : AppSelectAdapter.ViewHolder(view) { 12 | init { 13 | view.setOnClickListener { 14 | onItemClickListener.invoke(filteredList[absoluteAdapterPosition]) 15 | } 16 | } 17 | 18 | override fun bind(packageName: String) { 19 | (itemView as AppItemView).let { 20 | it.load(packageName) 21 | it.showEnabled = ConfigManager.isHideEnabled(packageName) 22 | } 23 | } 24 | } 25 | 26 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 27 | val view = AppItemView(parent.context, false) 28 | view.layoutParams = ViewGroup.LayoutParams( 29 | ViewGroup.LayoutParams.MATCH_PARENT, 30 | ViewGroup.LayoutParams.WRAP_CONTENT 31 | ) 32 | return ViewHolder(view) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppScopeAdapter.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.adapter 2 | 3 | import android.view.ViewGroup 4 | import icu.nullptr.hidemyapplist.ui.view.AppItemView 5 | 6 | class AppScopeAdapter( 7 | private val checked: MutableSet, 8 | firstFilter: ((String) -> Boolean)?, 9 | ) : AppSelectAdapter(firstFilter) { 10 | 11 | private inline var String.isChecked 12 | get() = checked.contains(this) 13 | set(value) { 14 | if (value) checked.add(this) else checked.remove(this) 15 | } 16 | 17 | inner class ViewHolder(view: AppItemView) : AppSelectAdapter.ViewHolder(view) { 18 | init { 19 | view.setOnClickListener { 20 | val packageName = filteredList[absoluteAdapterPosition] 21 | packageName.isChecked = !packageName.isChecked 22 | view.isChecked = packageName.isChecked 23 | } 24 | } 25 | 26 | override fun bind(packageName: String) { 27 | (itemView as AppItemView).let { 28 | it.load(packageName) 29 | it.isChecked = packageName.isChecked 30 | } 31 | } 32 | } 33 | 34 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 35 | val view = AppItemView(parent.context, true) 36 | view.layoutParams = ViewGroup.LayoutParams( 37 | ViewGroup.LayoutParams.MATCH_PARENT, 38 | ViewGroup.LayoutParams.WRAP_CONTENT 39 | ) 40 | return ViewHolder(view) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppSelectAdapter.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.adapter 2 | 3 | import android.widget.Filter 4 | import android.widget.Filterable 5 | import androidx.recyclerview.widget.RecyclerView 6 | import icu.nullptr.hidemyapplist.service.PrefManager 7 | import icu.nullptr.hidemyapplist.ui.view.AppItemView 8 | import icu.nullptr.hidemyapplist.util.PackageHelper 9 | import kotlinx.coroutines.flow.first 10 | import kotlinx.coroutines.runBlocking 11 | 12 | abstract class AppSelectAdapter( 13 | private val firstFilter: ((String) -> Boolean)? = null 14 | ) : RecyclerView.Adapter(), Filterable { 15 | 16 | abstract class ViewHolder(view: AppItemView) : RecyclerView.ViewHolder(view) { 17 | abstract fun bind(packageName: String) 18 | } 19 | 20 | private inner class AppFilter : Filter() { 21 | override fun performFiltering(constraint: CharSequence): FilterResults { 22 | return runBlocking { 23 | val constraintLowered = constraint.toString().lowercase() 24 | val filteredList = PackageHelper.appList.first().filter { 25 | if (firstFilter?.invoke(it) == false) return@filter false 26 | if (!PrefManager.appFilter_showSystem && PackageHelper.isSystem(it)) return@filter false 27 | val label = PackageHelper.loadAppLabel(it) 28 | val packageInfo = PackageHelper.loadPackageInfo(it) 29 | label.lowercase().contains(constraintLowered) || packageInfo.packageName.lowercase().contains(constraintLowered) 30 | } 31 | 32 | FilterResults().also { it.values = filteredList } 33 | } 34 | } 35 | 36 | @Suppress("UNCHECKED_CAST", "NotifyDataSetChanged") 37 | override fun publishResults(constraint: CharSequence, results: FilterResults) { 38 | filteredList = results.values as List 39 | notifyDataSetChanged() 40 | } 41 | } 42 | 43 | private val mFilter = AppFilter() 44 | 45 | protected var filteredList: List = listOf() 46 | 47 | override fun getItemCount() = filteredList.size 48 | 49 | override fun getItemId(position: Int) = filteredList[position].hashCode().toLong() 50 | 51 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(filteredList[position]) 52 | 53 | override fun getFilter(): Filter = mFilter 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/LogAdapter.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.adapter 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Color 6 | import android.os.Build 7 | import android.view.LayoutInflater 8 | import android.view.ViewGroup 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.tsng.hidemyapplist.R 11 | import com.tsng.hidemyapplist.databinding.LogItemViewBinding 12 | import icu.nullptr.hidemyapplist.service.PrefManager 13 | import icu.nullptr.hidemyapplist.ui.util.ThemeUtils.themeColor 14 | import java.util.regex.Pattern 15 | 16 | class LogAdapter(context: Context) : RecyclerView.Adapter() { 17 | 18 | class LogItem( 19 | val level: String, 20 | val date: String, 21 | val tag: String, 22 | val message: String 23 | ) 24 | 25 | companion object { 26 | private val pattern = Pattern.compile("\\[ ?(.*)] (.*) \\((.*)\\) (.*)", Pattern.DOTALL) 27 | 28 | fun parseLog(text: String): LogItem? { 29 | val matcher = pattern.matcher(text) 30 | matcher.find() 31 | val level = matcher.group(1) ?: return null 32 | if (level == "DEBUG" && PrefManager.logFilter_level > 0 || 33 | level == "INFO" && PrefManager.logFilter_level > 1 || 34 | level == "WARN" && PrefManager.logFilter_level > 2 35 | ) return null 36 | val date = matcher.group(2) ?: return null 37 | val tag = matcher.group(3) ?: return null 38 | val message = matcher.group(4) ?: return null 39 | return LogItem(level, date, tag, message) 40 | } 41 | } 42 | 43 | private val colorDebug = context.getColor(R.color.debug) 44 | private val colorInfo = context.getColor(R.color.info) 45 | private val colorWarn = context.getColor(R.color.warn) 46 | private val colorError = 47 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) Color.RED 48 | else context.themeColor(android.R.attr.colorError) 49 | 50 | var logs = listOf() 51 | @SuppressLint("NotifyDataSetChanged") 52 | set(value) { 53 | field = value 54 | notifyDataSetChanged() 55 | } 56 | 57 | inner class ViewHolder(private val binding: LogItemViewBinding) : RecyclerView.ViewHolder(binding.root) { 58 | fun bind(logItem: LogItem) { 59 | val color = when (logItem.level) { 60 | "DEBUG" -> colorDebug 61 | "INFO" -> colorInfo 62 | "WARN" -> colorWarn 63 | "ERROR" -> colorError 64 | else -> throw IllegalArgumentException("Unknown level: ${logItem.level}") 65 | } 66 | 67 | binding.level.setBackgroundColor(color) 68 | binding.level.text = logItem.level.substring(0, 1) 69 | binding.date.text = logItem.date 70 | binding.tag.text = logItem.tag 71 | binding.message.text = logItem.message 72 | } 73 | } 74 | 75 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 76 | val binding = LogItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false) 77 | return ViewHolder(binding) 78 | } 79 | 80 | override fun getItemCount() = logs.size 81 | 82 | override fun getItemId(position: Int) = logs[position].hashCode().toLong() 83 | 84 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(logs[position]) 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/TemplateAdapter.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.adapter 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.tsng.hidemyapplist.R 6 | import icu.nullptr.hidemyapplist.service.ConfigManager 7 | import icu.nullptr.hidemyapplist.ui.view.ListItemView 8 | import java.text.Collator 9 | import java.util.* 10 | 11 | class TemplateAdapter( 12 | private val onClickListener: ((ConfigManager.TemplateInfo) -> Unit)? 13 | ) : RecyclerView.Adapter() { 14 | 15 | private lateinit var list: List 16 | 17 | init { 18 | updateList() 19 | } 20 | 21 | inner class ViewHolder(view: ListItemView) : RecyclerView.ViewHolder(view) { 22 | init { 23 | view.setOnClickListener { 24 | onClickListener?.invoke(list[absoluteAdapterPosition]) 25 | } 26 | } 27 | 28 | fun bind(info: ConfigManager.TemplateInfo) { 29 | with(itemView as ListItemView) { 30 | setIcon( 31 | if (info.isWhiteList) R.drawable.outline_assignment_24 32 | else R.drawable.baseline_assignment_24 33 | ) 34 | text = info.name 35 | } 36 | } 37 | } 38 | 39 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 40 | val view = ListItemView(parent.context) 41 | view.layoutParams = ViewGroup.LayoutParams( 42 | ViewGroup.LayoutParams.MATCH_PARENT, 43 | ViewGroup.LayoutParams.WRAP_CONTENT 44 | ) 45 | return ViewHolder(view) 46 | } 47 | 48 | override fun getItemCount() = list.size 49 | 50 | override fun getItemId(position: Int) = list[position].name.hashCode().toLong() 51 | 52 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(list[position]) 53 | 54 | fun updateList() { 55 | list = ConfigManager.getTemplateList().apply { 56 | sortWith { o1, o2 -> 57 | if (o1.isWhiteList != o2.isWhiteList) { 58 | o1.isWhiteList.compareTo(o2.isWhiteList) 59 | } else { 60 | Collator.getInstance(Locale.getDefault()).compare(o1.name, o2.name) 61 | } 62 | } 63 | } 64 | notifyDataSetChanged() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/AppManageFragment.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.fragment 2 | 3 | import android.os.Bundle 4 | import com.google.android.material.transition.MaterialSharedAxis 5 | import com.tsng.hidemyapplist.R 6 | import icu.nullptr.hidemyapplist.service.ConfigManager 7 | import icu.nullptr.hidemyapplist.ui.adapter.AppManageAdapter 8 | import icu.nullptr.hidemyapplist.ui.util.navController 9 | 10 | class AppManageFragment : AppSelectFragment() { 11 | 12 | override val firstComparator: Comparator = Comparator.comparing(ConfigManager::isHideEnabled).reversed() 13 | 14 | override val adapter = AppManageAdapter { 15 | val args = AppSettingsFragmentArgs(it) 16 | navController.navigate(R.id.nav_app_settings, args.toBundle()) 17 | } 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) 22 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/ScopeFragment.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.fragment 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.setFragmentResult 5 | import androidx.navigation.fragment.navArgs 6 | import icu.nullptr.hidemyapplist.service.ConfigManager 7 | import icu.nullptr.hidemyapplist.ui.adapter.AppScopeAdapter 8 | import icu.nullptr.hidemyapplist.ui.util.navController 9 | 10 | class ScopeFragment : AppSelectFragment() { 11 | 12 | private lateinit var checked: MutableSet 13 | 14 | override val firstComparator: Comparator = Comparator.comparing { !checked.contains(it) } 15 | 16 | override val adapter by lazy { 17 | val args by navArgs() 18 | checked = args.checked.toMutableSet() 19 | if (!args.filterOnlyEnabled) AppScopeAdapter(checked, null) 20 | else AppScopeAdapter(checked) { ConfigManager.getAppConfig(it)?.useWhitelist == args.isWhiteList } 21 | } 22 | 23 | override fun onBack() { 24 | setFragmentResult("app_select", Bundle().apply { 25 | putStringArrayList("checked", ArrayList(checked)) 26 | }) 27 | navController.navigateUp() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/TemplateManageFragment.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.fragment 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import android.view.View 6 | import androidx.core.view.doOnPreDraw 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.clearFragmentResultListener 9 | import androidx.fragment.app.setFragmentResultListener 10 | import androidx.navigation.fragment.FragmentNavigatorExtras 11 | import androidx.recyclerview.widget.LinearLayoutManager 12 | import by.kirich1409.viewbindingdelegate.viewBinding 13 | import com.google.android.material.transition.MaterialContainerTransform 14 | import com.tsng.hidemyapplist.R 15 | import com.tsng.hidemyapplist.databinding.FragmentTemplateManageBinding 16 | import icu.nullptr.hidemyapplist.common.JsonConfig 17 | import icu.nullptr.hidemyapplist.service.ConfigManager 18 | import icu.nullptr.hidemyapplist.ui.adapter.TemplateAdapter 19 | import icu.nullptr.hidemyapplist.ui.util.navController 20 | import icu.nullptr.hidemyapplist.ui.util.setupToolbar 21 | 22 | class TemplateManageFragment : Fragment(R.layout.fragment_template_manage) { 23 | 24 | private val binding by viewBinding() 25 | private val adapter = TemplateAdapter(this::navigateToSettings) 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | sharedElementEnterTransition = MaterialContainerTransform().apply { 30 | drawingViewId = R.id.nav_host_fragment 31 | scrimColor = Color.TRANSPARENT 32 | } 33 | } 34 | 35 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 36 | setupToolbar( 37 | toolbar = binding.toolbar, 38 | title = getString(R.string.title_template_manage), 39 | navigationIcon = R.drawable.baseline_arrow_back_24, 40 | navigationOnClick = { navController.navigateUp() } 41 | ) 42 | postponeEnterTransition() 43 | view.doOnPreDraw { startPostponedEnterTransition() } 44 | 45 | binding.newBlacklistTemplate.setOnClickListener { 46 | navigateToSettings(ConfigManager.TemplateInfo(null, false)) 47 | } 48 | binding.newWhitelistTemplate.setOnClickListener { 49 | navigateToSettings(ConfigManager.TemplateInfo(null, true)) 50 | } 51 | binding.templateList.layoutManager = LinearLayoutManager(context) 52 | binding.templateList.adapter = adapter 53 | } 54 | 55 | private fun navigateToSettings(info: ConfigManager.TemplateInfo) { 56 | setFragmentResultListener("template_settings") { _, bundle -> 57 | fun deal() { 58 | var name = bundle.getString("name") 59 | val appliedList = bundle.getStringArrayList("appliedList")!! 60 | val targetList = bundle.getStringArrayList("targetList")!! 61 | if (info.name == null) { // New template 62 | if (name.isNullOrEmpty()) return 63 | ConfigManager.updateTemplate(name, JsonConfig.Template(info.isWhiteList, targetList.toSet())) 64 | ConfigManager.updateTemplateAppliedApps(name, appliedList) 65 | } else { // Existing template 66 | if (name == null) { 67 | ConfigManager.deleteTemplate(info.name) 68 | } else { 69 | if (name.isEmpty()) name = info.name 70 | if (name != info.name) ConfigManager.renameTemplate(info.name, name) 71 | ConfigManager.updateTemplate(name, JsonConfig.Template(info.isWhiteList, targetList.toSet())) 72 | ConfigManager.updateTemplateAppliedApps(name, appliedList) 73 | } 74 | } 75 | } 76 | deal() 77 | adapter.updateList() 78 | clearFragmentResultListener("template_settings") 79 | } 80 | 81 | val args = TemplateSettingsFragmentArgs(info.name, info.isWhiteList) 82 | val extras = FragmentNavigatorExtras(binding.hintCard to "transition_manage") 83 | navController.navigate(R.id.nav_template_settings, args.toBundle(), null, extras) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/receiver/AppChangeReceiver.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.receiver 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import android.util.Log 8 | import icu.nullptr.hidemyapplist.util.PackageHelper 9 | 10 | class AppChangeReceiver : BroadcastReceiver() { 11 | 12 | companion object { 13 | private const val TAG = "AppChangeReceiver" 14 | 15 | private val actions = setOf( 16 | Intent.ACTION_PACKAGE_ADDED, 17 | Intent.ACTION_PACKAGE_REMOVED, 18 | Intent.ACTION_PACKAGE_REPLACED 19 | ) 20 | 21 | fun register(context: Context) { 22 | val filter = IntentFilter().apply { 23 | actions.forEach(::addAction) 24 | addDataScheme("package") 25 | } 26 | context.registerReceiver(AppChangeReceiver(), filter) 27 | } 28 | } 29 | 30 | override fun onReceive(context: Context, intent: Intent) { 31 | if (intent.action in actions) { 32 | Log.i(TAG, "Received intent: $intent") 33 | PackageHelper.invalidateCache() 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Fragment.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.util 2 | 3 | import android.os.Build 4 | import android.view.Menu 5 | import android.view.MenuInflater 6 | import android.view.MenuItem 7 | import android.view.View 8 | import androidx.annotation.DrawableRes 9 | import androidx.annotation.MenuRes 10 | import androidx.appcompat.widget.Toolbar 11 | import androidx.core.view.MenuProvider 12 | import androidx.fragment.app.Fragment 13 | import androidx.navigation.fragment.NavHostFragment 14 | 15 | val Fragment.navController get() = NavHostFragment.findNavController(this) 16 | 17 | fun Fragment.setupToolbar( 18 | toolbar: Toolbar, 19 | title: String, 20 | @DrawableRes navigationIcon: Int? = null, 21 | navigationOnClick: View.OnClickListener? = null, 22 | @MenuRes menuRes: Int? = null, 23 | onMenuOptionSelected: ((MenuItem) -> Unit)? = null 24 | ) { 25 | navigationOnClick?.let { toolbar.setNavigationOnClickListener(it) } 26 | navigationIcon?.let { toolbar.setNavigationIcon(navigationIcon) } 27 | toolbar.title = title 28 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) toolbar.tooltipText = title 29 | if (menuRes != null) { 30 | val menuProvider = object : MenuProvider { 31 | override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { 32 | menuInflater.inflate(menuRes, menu) 33 | } 34 | 35 | override fun onMenuItemSelected(menuItem: MenuItem): Boolean { 36 | return onMenuOptionSelected?.let { 37 | it(menuItem); true 38 | } ?: false 39 | } 40 | } 41 | toolbar.inflateMenu(menuRes) 42 | toolbar.setOnMenuItemClickListener(menuProvider::onMenuItemSelected) 43 | requireActivity().addMenuProvider(menuProvider) 44 | menuProvider.onPrepareMenu(toolbar.menu) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/util/ThemeUtils.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.util 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import androidx.annotation.AttrRes 6 | import androidx.annotation.ColorInt 7 | import androidx.annotation.ColorRes 8 | import androidx.annotation.StyleRes 9 | import androidx.fragment.app.Fragment 10 | import com.google.android.material.color.DynamicColors 11 | import com.tsng.hidemyapplist.R 12 | import icu.nullptr.hidemyapplist.service.PrefManager 13 | import rikka.core.util.ResourceUtils 14 | 15 | object ThemeUtils { 16 | 17 | private val colorThemeMap = mapOf( 18 | "SAKURA" to R.style.ThemeOverlay_MaterialSakura, 19 | "MATERIAL_RED" to R.style.ThemeOverlay_MaterialRed, 20 | "MATERIAL_PINK" to R.style.ThemeOverlay_MaterialPink, 21 | "MATERIAL_PURPLE" to R.style.ThemeOverlay_MaterialPurple, 22 | "MATERIAL_DEEP_PURPLE" to R.style.ThemeOverlay_MaterialDeepPurple, 23 | "MATERIAL_INDIGO" to R.style.ThemeOverlay_MaterialIndigo, 24 | "MATERIAL_BLUE" to R.style.ThemeOverlay_MaterialBlue, 25 | "MATERIAL_LIGHT_BLUE" to R.style.ThemeOverlay_MaterialLightBlue, 26 | "MATERIAL_CYAN" to R.style.ThemeOverlay_MaterialCyan, 27 | "MATERIAL_TEAL" to R.style.ThemeOverlay_MaterialTeal, 28 | "MATERIAL_GREEN" to R.style.ThemeOverlay_MaterialGreen, 29 | "MATERIAL_LIGHT_GREEN" to R.style.ThemeOverlay_MaterialLightGreen, 30 | "MATERIAL_LIME" to R.style.ThemeOverlay_MaterialLime, 31 | "MATERIAL_YELLOW" to R.style.ThemeOverlay_MaterialYellow, 32 | "MATERIAL_AMBER" to R.style.ThemeOverlay_MaterialAmber, 33 | "MATERIAL_ORANGE" to R.style.ThemeOverlay_MaterialOrange, 34 | "MATERIAL_DEEP_ORANGE" to R.style.ThemeOverlay_MaterialDeepOrange, 35 | "MATERIAL_BROWN" to R.style.ThemeOverlay_MaterialBrown, 36 | "MATERIAL_BLUE_GREY" to R.style.ThemeOverlay_MaterialBlueGrey 37 | ) 38 | 39 | private const val THEME_DEFAULT = "DEFAULT" 40 | private const val THEME_BLACK = "BLACK" 41 | 42 | val isSystemAccent get() = DynamicColors.isDynamicColorAvailable() && PrefManager.followSystemAccent 43 | 44 | fun getNightTheme(context: Context): String { 45 | return if (PrefManager.blackDarkTheme && ResourceUtils.isNightMode(context.resources.configuration)) 46 | THEME_BLACK else THEME_DEFAULT 47 | } 48 | 49 | @StyleRes 50 | fun getNightThemeStyleRes(context: Context): Int { 51 | return if (PrefManager.blackDarkTheme && ResourceUtils.isNightMode(context.resources.configuration)) 52 | R.style.ThemeOverlay_Black else R.style.ThemeOverlay 53 | } 54 | 55 | val colorTheme get() = if (isSystemAccent) "SYSTEM" else PrefManager.themeColor 56 | 57 | val colorThemeStyleRes: Int 58 | @StyleRes get() = colorThemeMap[colorTheme] ?: R.style.ThemeOverlay_MaterialBlue 59 | 60 | /** 61 | * Retrieve a color from the current [android.content.res.Resources.Theme]. 62 | */ 63 | @ColorInt 64 | fun Context.themeColor( 65 | @AttrRes themeAttrId: Int 66 | ): Int { 67 | val style = obtainStyledAttributes(intArrayOf(themeAttrId)) 68 | val color = style.getColor(0, Color.MAGENTA) 69 | style.recycle() 70 | return color 71 | } 72 | 73 | @ColorInt 74 | fun Fragment.themeColor( 75 | @AttrRes themeAttrId: Int 76 | ) = requireContext().themeColor(themeAttrId) 77 | 78 | @ColorInt 79 | fun Fragment.getColor( 80 | @ColorRes colorId: Int 81 | ) = requireContext().getColor(colorId) 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Toast.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.util 2 | 3 | import android.widget.Toast 4 | import androidx.annotation.StringRes 5 | import icu.nullptr.hidemyapplist.hmaApp 6 | 7 | fun makeToast(@StringRes resId: Int) { 8 | Toast.makeText(hmaApp, resId, Toast.LENGTH_SHORT).show() 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/view/AppItemView.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.LinearLayout 6 | import by.kirich1409.viewbindingdelegate.CreateMethod 7 | import by.kirich1409.viewbindingdelegate.viewBinding 8 | import com.tsng.hidemyapplist.databinding.AppItemViewBinding 9 | import icu.nullptr.hidemyapplist.util.PackageHelper 10 | 11 | class AppItemView @JvmOverloads constructor( 12 | context: Context, 13 | attrs: AttributeSet? = null, 14 | defStyleAttr: Int = 0, 15 | defStyleRes: Int = 0 16 | ) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { 17 | 18 | private val binding by viewBinding(createMethod = CreateMethod.INFLATE) 19 | 20 | var showEnabled: Boolean 21 | get() = binding.enabled.visibility == VISIBLE 22 | set(value) { 23 | binding.enabled.visibility = if (value) VISIBLE else GONE 24 | } 25 | 26 | var isChecked: Boolean 27 | get() = binding.checkbox.isChecked 28 | set(value) { 29 | binding.checkbox.isChecked = value 30 | } 31 | 32 | constructor(context: Context, isCheckable: Boolean) : this(context) { 33 | binding.checkbox.visibility = if (isCheckable) VISIBLE else GONE 34 | } 35 | 36 | fun load(packageName: String) { 37 | binding.packageName.text = packageName 38 | binding.label.text = PackageHelper.loadAppLabel(packageName) 39 | binding.icon.setImageBitmap(PackageHelper.loadAppIcon(packageName)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/view/ListItemView.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.LinearLayout 6 | import androidx.annotation.DrawableRes 7 | import by.kirich1409.viewbindingdelegate.CreateMethod 8 | import by.kirich1409.viewbindingdelegate.viewBinding 9 | import com.tsng.hidemyapplist.R 10 | import com.tsng.hidemyapplist.databinding.ListItemViewBinding 11 | 12 | class ListItemView @JvmOverloads constructor( 13 | context: Context, 14 | attrs: AttributeSet? = null, 15 | defStyleAttr: Int = 0, 16 | defStyleRes: Int = 0 17 | ) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { 18 | 19 | private val binding by viewBinding(createMethod = CreateMethod.INFLATE) 20 | 21 | init { 22 | val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.ListItemView, defStyleAttr, defStyleRes) 23 | val icon = typedArray.getResourceId(R.styleable.ListItemView_icon, 0) 24 | val text = typedArray.getString(R.styleable.ListItemView_text) 25 | val buttonText = typedArray.getText(R.styleable.ListItemView_buttonText) 26 | typedArray.recycle() 27 | binding.icon.setImageResource(icon) 28 | binding.text.text = text 29 | if (buttonText != null) { 30 | binding.button.visibility = VISIBLE 31 | binding.button.text = buttonText 32 | } 33 | } 34 | 35 | var text: CharSequence? 36 | get() = binding.text.text 37 | set(value) { 38 | binding.text.text = value 39 | } 40 | 41 | fun setIcon(@DrawableRes icon: Int) { 42 | binding.icon.setImageResource(icon) 43 | } 44 | 45 | override fun setOnClickListener(l: OnClickListener?) { 46 | if (binding.button.visibility == VISIBLE) { 47 | binding.button.setOnClickListener(l) 48 | } else { 49 | super.setOnClickListener(l) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/viewmodel/AppSettingsViewModel.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import icu.nullptr.hidemyapplist.common.JsonConfig 6 | 7 | class AppSettingsViewModel(val pack: Pack) : ViewModel() { 8 | 9 | class Factory(private val pack: Pack) : ViewModelProvider.Factory { 10 | override fun create(modelClass: Class): T { 11 | if (modelClass.isAssignableFrom(AppSettingsViewModel::class.java)) { 12 | @Suppress("UNCHECKED_CAST") 13 | return AppSettingsViewModel(pack) as T 14 | } else throw IllegalArgumentException("Unknown ViewModel class") 15 | } 16 | } 17 | 18 | class Pack( 19 | val app: String, 20 | var enabled: Boolean, 21 | val config: JsonConfig.AppConfig 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/ui/viewmodel/TemplateSettingsViewModel.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.ui.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import icu.nullptr.hidemyapplist.service.ConfigManager 6 | import icu.nullptr.hidemyapplist.ui.fragment.TemplateSettingsFragmentArgs 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | 9 | class TemplateSettingsViewModel( 10 | val originalName: String?, 11 | val isWhiteList: Boolean, 12 | var name: String? 13 | ) : ViewModel() { 14 | 15 | class Factory(private val args: TemplateSettingsFragmentArgs) : ViewModelProvider.Factory { 16 | override fun create(modelClass: Class): T { 17 | if (modelClass.isAssignableFrom(TemplateSettingsViewModel::class.java)) { 18 | val viewModel = TemplateSettingsViewModel(args.name, args.isWhiteList, args.name) 19 | args.name?.let { 20 | viewModel.appliedAppList.value = ConfigManager.getTemplateAppliedAppList(it) 21 | viewModel.targetAppList.value = ConfigManager.getTemplateTargetAppList(it) 22 | } 23 | @Suppress("UNCHECKED_CAST") 24 | return viewModel as T 25 | } else throw IllegalArgumentException("Unknown ViewModel class") 26 | } 27 | } 28 | 29 | val appliedAppList = MutableStateFlow>(ArrayList()) 30 | val targetAppList = MutableStateFlow>(ArrayList()) 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/icu/nullptr/hidemyapplist/util/SuUtils.kt: -------------------------------------------------------------------------------- 1 | package icu.nullptr.hidemyapplist.util 2 | 3 | import com.topjohnwu.superuser.Shell 4 | 5 | object SuUtils { 6 | 7 | fun execPrivileged(cmd: String): Boolean { 8 | return Shell.cmd(cmd).exec().isSuccess && Shell.isAppGrantedRoot() == true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_add_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_apps_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_arrow_back_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_assignment_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_call_split_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_home_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_my_location_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_refresh_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_settings_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cont_author.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicianGuo/Hide-My-Applist/9fef3f069a79ea58cc44d44423bfae793d6a2093/app/src/main/res/drawable/cont_author.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/cont_aviraxp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicianGuo/Hide-My-Applist/9fef3f069a79ea58cc44d44423bfae793d6a2093/app/src/main/res/drawable/cont_aviraxp.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/cont_cpp_master.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicianGuo/Hide-My-Applist/9fef3f069a79ea58cc44d44423bfae793d6a2093/app/src/main/res/drawable/cont_cpp_master.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/cont_icon_designer.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicianGuo/Hide-My-Applist/9fef3f069a79ea58cc44d44423bfae793d6a2093/app/src/main/res/drawable/cont_icon_designer.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/cont_k.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicianGuo/Hide-My-Applist/9fef3f069a79ea58cc44d44423bfae793d6a2093/app/src/main/res/drawable/cont_k.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_checkable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logs_checkable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_layers_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_checkable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_android_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_assignment_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_backup_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_bug_report_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_cleaning_services_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_dark_mode_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_delete_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_discount_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_done_all_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_edit_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_extension_off_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_format_color_fill_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_hide_image_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_home_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_info_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_invert_colors_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_language_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_palette_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_save_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_sd_storage_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_settings_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_settings_backup_restore_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_shield_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_speed_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_stop_circle_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_storage_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_translate_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_update_disabled_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 24 | 25 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_item_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 23 | 24 | 30 | 31 | 40 | 41 | 47 | 48 | 49 | 50 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_app_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 20 | 21 | 22 | 27 | 28 | 37 | 38 | 44 | 45 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_logs.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 17 | 18 | 19 | 27 | 28 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 17 | 18 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_template_manage.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 18 | 19 | 20 | 25 | 26 | 34 | 35 | 42 | 43 | 47 | 48 | 54 | 55 | 56 | 57 | 64 | 65 | 71 | 72 | 73 | 74 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_template_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 19 | 20 | 21 | 26 | 27 | 35 | 36 | 40 | 41 | 42 | 48 | 49 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/line.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 26 | 27 |