├── .github ├── ISSUE_TEMPLATE │ ├── bug_report_en.yml │ ├── bug_report_zh.yml │ ├── config.yml │ ├── feature_request_en.yml │ └── feature_request_zh.yml └── workflows │ └── push_ci.yaml ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── deploymentTargetSelector.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── migrations.xml └── misc.xml ├── LICENSE ├── README.md ├── android-stub ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ ├── android │ ├── app │ │ ├── IActivityTaskManager.java │ │ └── ITaskStackListener.java │ ├── content │ │ └── pm │ │ │ ├── IPackageManagerHidden.java │ │ │ ├── PackageManagerHidden.java │ │ │ └── UserInfo.java │ ├── hardware │ │ └── input │ │ │ └── IInputManager.java │ ├── view │ │ ├── IRotationWatcher.java │ │ ├── IWindowManager.java │ │ ├── WindowLayoutParamsHidden.java │ │ └── WindowManagerHidden.java │ └── window │ │ └── TaskSnapshot.java │ └── com │ └── android │ └── internal │ └── statusbar │ └── IStatusBarService.java ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── io │ │ └── github │ │ └── duzhaokun123 │ │ └── yamf │ │ └── xposed │ │ ├── IOpenCountListener.aidl │ │ └── IYAMFManager.aidl │ ├── assets │ └── xposed_init │ ├── java │ └── io │ │ └── github │ │ ├── duzhaokun123 │ │ └── yamf │ │ │ ├── common │ │ │ ├── Utils.kt │ │ │ └── model │ │ │ │ ├── Config.kt │ │ │ │ └── StartCmd.kt │ │ │ ├── manager │ │ │ ├── Application.kt │ │ │ ├── bases │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ └── BaseSimpleAdapter.kt │ │ │ ├── providers │ │ │ │ └── ServiceProvider.kt │ │ │ ├── services │ │ │ │ ├── QSEnterWindow.kt │ │ │ │ ├── QSNewWindowService.kt │ │ │ │ ├── QSResetAllWindow.kt │ │ │ │ └── YAMFManagerProxy.kt │ │ │ ├── ui │ │ │ │ ├── SettingsActivity.kt │ │ │ │ └── main │ │ │ │ │ └── MainActivity.kt │ │ │ └── utils │ │ │ │ ├── TipUtil.kt │ │ │ │ └── Utils.kt │ │ │ └── xposed │ │ │ ├── hook │ │ │ ├── HookLauncher.kt │ │ │ └── HookSystem.kt │ │ │ ├── services │ │ │ ├── UserService.kt │ │ │ └── YAMFManager.kt │ │ │ ├── ui │ │ │ └── window │ │ │ │ ├── AppListWindow.kt │ │ │ │ └── AppWindow.kt │ │ │ └── utils │ │ │ ├── AppInfoCache.kt │ │ │ ├── Instances.kt │ │ │ ├── RunMainThreadQueue.kt │ │ │ ├── TipUtil.kt │ │ │ └── Utils.kt │ │ └── qauxv │ │ ├── ui │ │ └── CommonContextWrapper.java │ │ └── util │ │ ├── Initiator.java │ │ └── SavedInstanceStatePatchedClassReferencer.java │ └── res │ ├── drawable │ ├── a_bg.xml │ ├── ic_android_black_24dp.xml │ ├── ic_arrow_back_24.xml │ ├── ic_close_24.xml │ ├── ic_error_outline_24.xml │ ├── ic_fullscreen_24.xml │ ├── ic_home_24.xml │ ├── ic_info_24.xml │ ├── ic_keyboard.xml │ ├── ic_launcher_foreground.xml │ ├── ic_open_in_full_24.xml │ ├── ic_picture_in_picture_alt_24.xml │ ├── ic_round_check_circle_24.xml │ ├── ic_screen_rotation_alt_24.xml │ ├── ic_security_24.xml │ ├── ic_settings_24.xml │ ├── ic_warning_amber_24.xml │ └── vd_vector.xml │ ├── layout │ ├── activity_base_root_2.xml │ ├── activity_main.xml │ ├── activity_settings.xml │ ├── item_app.xml │ ├── window_app.xml │ └── window_app_list.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher.xml │ ├── values-night │ └── themes.xml │ ├── values-zh-rCN │ └── strings.xml │ └── values │ ├── arrays.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── strings.xml │ └── themes.xml ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── releaseKey.jks └── settings.gradle.kts /.github/ISSUE_TEMPLATE/bug_report_en.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Template for bug reports 3 | title: '[bug]' 4 | labels: 5 | - bug 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: Steps to reproduce 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: Expected behaviour 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Actual behaviour 20 | validations: 21 | required: true 22 | - type: input 23 | attributes: 24 | label: Xposed framework 25 | validations: 26 | required: true 27 | - type: input 28 | attributes: 29 | label: Android version 30 | validations: 31 | required: true 32 | - type: input 33 | attributes: 34 | label: YAMF version 35 | description: must be `version_name`, like `v0.5-git.c384ea7` 36 | validations: 37 | required: true 38 | - type: checkboxes 39 | attributes: 40 | label: Version requirement 41 | options: 42 | - label: I am using latest debug CI version of YAMF 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: Logs 47 | description: 'from `logcat` or LSPosed' 48 | validations: 49 | required: true 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_zh.yml: -------------------------------------------------------------------------------- 1 | name: bug 反馈 2 | description: bug 反馈模板 3 | title: '[bug]' 4 | labels: 5 | - bug 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: 复现步骤 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: 预期行为 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: 实际行为 20 | validations: 21 | required: true 22 | - type: input 23 | attributes: 24 | label: Xposed 框架 25 | validations: 26 | required: true 27 | - type: input 28 | attributes: 29 | label: Android 版本 30 | validations: 31 | required: true 32 | - type: input 33 | attributes: 34 | label: YAMF 版本 35 | description: 必须是 `版本名`, 如 `v0.5-git.c384ea7` 36 | validations: 37 | required: true 38 | - type: checkboxes 39 | attributes: 40 | label: 版本要求 41 | options: 42 | - label: 我正在使用最新 ci 的 dubug 的 YAMF 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: 日志 47 | description: '`logcat`或 LSPosed 的日志' 48 | validations: 49 | required: true 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: smart-question 4 | url: http://www.catb.org/~esr/faqs/smart-questions.html 5 | about: READ BEFORE YOU ASKING ANY QUESTION 6 | - name: 提问的智慧 7 | url: https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md 8 | about: 在提问前请先阅读 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_en.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: template for missing feature 3 | title: '[Feature Request]' 4 | labels: 5 | - enhancement 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: Description 10 | description: A clear and concise description of the problem or missing capability 11 | validations: 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: Describe the solution you'd like 16 | description: If you have a solution in mind, please describe it. 17 | - type: textarea 18 | attributes: 19 | label: Describe alternatives you've considered 20 | description: Have you considered any alternative solutions or workarounds? 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_zh.yml: -------------------------------------------------------------------------------- 1 | name: 功能请求 2 | description: 功能请求模板 3 | title: '[Feature Request]' 4 | labels: 5 | - enhancement 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: 功能描述 10 | description: 对问题或缺失功能的清晰简明描述 11 | validations: 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: 解决方案 16 | description: 如果您有解决方案 请描述 17 | - type: textarea 18 | attributes: 19 | label: 备选方案 20 | description: 您是否考虑过任何替代解决方案或变通方法? 21 | -------------------------------------------------------------------------------- /.github/workflows/push_ci.yaml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | if: ${{ !startsWith(github.event.head_commit.message, '[skip ci]') }} 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Setup JDK 17 14 | uses: actions/setup-java@v3 15 | with: 16 | distribution: 'temurin' 17 | java-version: 17 18 | cache: 'gradle' 19 | - name: Cache Gradle Dependencies 20 | uses: actions/cache@v3.2.3 21 | with: 22 | path: | 23 | ~/.gradle/caches 24 | ~/.gradle/wrapper 25 | !~/.gradle/caches/build-cache-* 26 | key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }} 27 | restore-keys: | 28 | gradle-deps 29 | - name: Cache Gradle Build 30 | uses: actions/cache@v3.2.3 31 | with: 32 | path: | 33 | ~/.gradle/caches/build-cache-* 34 | ~/.gradle/buildOutputCleanup/cache.properties 35 | key: gradle-builds-core-${{ github.sha }} 36 | restore-keys: | 37 | gradle-builds 38 | - name: Build with Gradle 39 | env: 40 | REL_KEY: ${{ secrets.REL_KEY }} 41 | # APP_SECRET: ${{ secrets.APP_SECRET }} 42 | run: | 43 | bash ./gradlew assembleRelease assembleDebug 44 | - name: Upload built apk 45 | if: success() 46 | uses: actions/upload-artifact@v3 47 | with: 48 | name: snapshot 49 | path: | 50 | ${{ github.workspace }}/app/build/outputs/apk 51 | # ${{ github.workspace }}/app/build/outputs/mapping 52 | 53 | upload-telegram: 54 | name: Upload Release 55 | if: ${{ success() && github.ref == 'refs/heads/main' }} 56 | runs-on: ubuntu-latest 57 | needs: 58 | - build 59 | - telegram-bot-api 60 | steps: 61 | - name: Donwload Artifacts 62 | uses: actions/download-artifact@v3 63 | with: 64 | path: artifacts 65 | - name: Download Telegram Bot API Binary 66 | uses: actions/download-artifact@master 67 | with: 68 | name: telegram-bot-api-binary 69 | path: . 70 | - name: Release 71 | env: 72 | COMMIT_MESSAGE: |+ 73 | New push to github\! 74 | ``` 75 | ${{ github.event.head_commit.message }} 76 | ```by `${{ github.event.head_commit.author.name }}` 77 | See commit detail [here](${{ github.event.head_commit.url }}) 78 | COMMIT_URL: ${{ github.event.head_commit.url }} 79 | run: | 80 | 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"])))'` 81 | export release=$(find artifacts -name "app-release.apk") 82 | export debug=$(find artifacts -name "app-debug.apk") 83 | chmod +x telegram-bot-api-binary 84 | ./telegram-bot-api-binary --api-id=21724 --api-hash=3e0cb5efcd52300aec5994fdfc5bdc16 --local 2>&1 > /dev/null & 85 | curl -v "http://127.0.0.1:8081/bot${{ secrets.TELEGRAM_TOKEN }}/sendMediaGroup?chat_id=-1001768185782&message_thread_id=97&media=%5B%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fdebug%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Frelease%22%2C%22parse_mode%22%3A%22MarkdownV2%22%2C%22caption%22:${ESCAPED}%7D%5D" \ 86 | -F debug="@$debug" \ 87 | -F release="@$release" 88 | pkill telegram-bot 89 | 90 | telegram-bot-api: 91 | name: Telegram Bot API 92 | runs-on: ubuntu-latest 93 | steps: 94 | - name: Checkout 95 | uses: actions/checkout@v3.3.0 96 | - name: Clone Telegram Bot API 97 | run: | 98 | git clone --recursive https://github.com/tdlib/telegram-bot-api.git 99 | git status telegram-bot-api >> telegram-bot-api-status 100 | - name: Cache Bot API Binary 101 | id: cache-bot-api 102 | uses: actions/cache@v3.2.4 103 | with: 104 | path: telegram-bot-api-binary 105 | key: CI-telegram-bot-api-${{ hashFiles('telegram-bot-api-status') }} 106 | - name: Compile Telegram Bot API 107 | if: steps.cache-bot-api.outputs.cache-hit != 'true' 108 | run: | 109 | sudo apt-get update 110 | sudo apt-get install make git zlib1g-dev libssl-dev gperf cmake g++ 111 | cd telegram-bot-api 112 | rm -rf build 113 | mkdir build 114 | cd build 115 | cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=.. .. 116 | cmake --build . --target install -j$(nproc) 117 | cd ../.. 118 | ls -l telegram-bot-api/bin/telegram-bot-api* 119 | cp telegram-bot-api/bin/telegram-bot-api telegram-bot-api-binary 120 | - name: Upload Binary 121 | uses: actions/upload-artifact@master 122 | with: 123 | name: telegram-bot-api-binary 124 | path: telegram-bot-api-binary 125 | -------------------------------------------------------------------------------- /.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 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 已弃用 2 | 3 | YAMF 从 Mi-FreeForm 来的 4 | 5 | 都有一个缺陷 将 Display 与 TaskStack 关联 并几乎与 package 关联 在大多数情况下这没有问题 应用只有一个 TaskStack 并被移动到 virtual display 以以小窗显示 6 | 7 | 但 我对大多数情况不感兴趣, YAMF 处理不好诸如 Chrome 多窗口的情况 8 | 9 | 就这样吧 这玩意是和人斗气写的 现在气消了 10 | 11 | 不过可以看看 [JuanArton/reYAMF](https://github.com/JuanArton/reYAMF) 12 | 13 | # YAMF 14 | 15 | [![GitHub license](https://img.shields.io/github/license/duzhaokun123/YAMF?style=flat-square)](https://github.com/duzhaokun123/YAFM/blob/main/LICENSE) 16 | ![Android SDK min 31](https://img.shields.io/badge/Android%20SDK-%3E%3D%2031-brightgreen?style=flat-square&logo=android) 17 | ![Android SDK target 34](https://img.shields.io/badge/Android%20SDK-target%2034-brightgreen?style=flat-square&logo=android) 18 | ![Xposed Module](https://img.shields.io/badge/Xposed-Module-blue?style=flat-square) 19 | [![Channel](https://img.shields.io/badge/Follow-Telegram-blue.svg?logo=telegram&style=flat-square)](https://t.me/YAMF_channel) 20 | 21 | Yet Another [Mi-FreeForm](https://github.com/sunshine0523/Mi-FreeForm) 22 | 23 | 因为 Mi-FreeForm 非常不好用 只好重写一个 24 | 25 | | | YAMF | Mi-FreeForm | 26 | |------------------|:-----------------------------------------:|:--------------------------------:| 27 | | Android 版本限制 | 13(api 33)
12L(api 32)
12(api 31) | >= 8.1(api 27) | 28 | | 需要权限 | Xposed(必须) | Shizuku(必须)
Xposed, 无障碍(可选) | 29 | | 免 root | ❌(不会支持) | ✅ | 30 | | 支持 FLAG_SECURE | ✅ | ❌ | 31 | | 系统级叠加层 | ✅ | ❌ | 32 | | 多实例的应用支持 | ✅ | ❌ | 33 | | 重写其他应用的通知以在小窗中打开 | ❌(或许会有) | ✅ | 34 | | 从最近任务启动小窗 | ✅ | ✅ | 35 | | 暂时隐藏小窗 | ❌(移到边上看不见就好了) | ✅ | 36 | | 挂起小窗 | ✅ | ✅ | 37 | | 记住位置 | ❌(不会支持) | ✅ | 38 | | 侧边启动栏 | ❌(TODO) | ✅ | 39 | | 手动调整方向 | ✅ | ✅ | 40 | | 自动调整方向 | ✅ | ✅ | 41 | | 缩放 (scale) | ❌(不会支持) | ✅ | 42 | | 调整大小 (resize) | ✅ | ✅ | 43 | | 快速设置磁贴 | ✅ | ✅ | 44 | | open api | 部分的 | ✅ | 45 | | 无需关心保活 | ✅(因为注入了系统进程) | ❌ | 46 | | 高刷新率 | ❓ | ❓ | 47 | | HDR | ❌ | ❌ | 48 | 49 | ## 下载 50 | 51 | ci https://github.com/duzhaokun123/YAMF/actions/workflows/push_ci.yaml?query=event%3Apush+branch%3Amain 52 | 53 | release https://github.com/Xposed-Modules-Repo/io.github.duzhaokun123.yamf 54 | 55 | ## open api 56 | 57 | 广播`io.github.duzhaokun123.yamf.action.CURRENT_TO_WINDOW`将当前活动的应用移动到小窗 58 | 59 | 广播`io.github.duzhaokun123.yamf.action.OPEN_APP_LIST`将打开应用列表 60 | 61 | ## 已知问题 62 | 63 | - 模块与注入的版本不同时系统会崩溃 64 | - 常见 xposed 模块问题 65 | - 某些应用似乎无法在小窗中启动 66 | - 某些应用在某些尺寸下缩放异常 67 | 68 | ## TODO 69 | 70 | - 好看的图标 71 | - 侧边启动栏 72 | - RtL 支持 73 | 74 | ## 捐赠 75 | 76 | 你的捐赠并不能直接加快开发 也不会给你带来特权 77 | 78 | https://duzhaokun123.github.io/donate.html 79 | 80 | ## Thanks 81 | 82 | ### 贡献者 83 | 84 | [Nitsuya](https://github.com/Nitsuya) 85 | 86 | ### 库 87 | 88 | [AOSP](https://source.android.com/) 89 | 90 | [EzXHelper](https://github.com/KyuubiRan/EzXHelper) 91 | 92 | [FlexboxLayout](https://github.com/google/flexbox-layout) 93 | 94 | [Hide-My-Applist](https://github.com/Dr-TSNG/Hide-My-Applist) 95 | 96 | [LSPosed](https://github.com/LSPosed/LSPosed) 97 | 98 | [Material](https://material.io/) 99 | 100 | [Mi-FreeForm](https://github.com/sunshine0523/Mi-FreeForm) 101 | 102 | [QAuxiliary](https://github.com/cinit/QAuxiliary) 103 | 104 | [ViewBindingUtil](https://github.com/matsudamper/ViewBindingUtil) 105 | 106 | [gson](https://github.com/google/gson) 107 | 108 | [xposed](https://forum.xda-developers.com/xposed) 109 | 110 | 111 | -------------------------------------------------------------------------------- /android-stub/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /android-stub/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | } 4 | 5 | android { 6 | compileSdk = 34 7 | 8 | defaultConfig { 9 | minSdk = 31 10 | targetSdk = 34 11 | 12 | consumerProguardFiles("consumer-rules.pro") 13 | } 14 | 15 | buildTypes { 16 | getByName("release") { 17 | isMinifyEnabled = false 18 | proguardFiles( 19 | getDefaultProguardFile("proguard-android-optimize.txt"), 20 | "proguard-rules.pro" 21 | ) 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility = JavaVersion.VERSION_11 26 | targetCompatibility = JavaVersion.VERSION_11 27 | } 28 | 29 | namespace = "io.github.duzhaokun123.android_stub" 30 | } 31 | 32 | dependencies { 33 | annotationProcessor("dev.rikka.tools.refine:annotation-processor:4.3.0") 34 | compileOnly("dev.rikka.tools.refine:annotation:4.3.0") 35 | compileOnly("androidx.annotation:annotation:1.7.1") 36 | compileOnly("dev.rikka.hidden:stub:4.2.0") 37 | } 38 | -------------------------------------------------------------------------------- /android-stub/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duzhaokun123/YAMF/838bca5802f82a45d60400b415a24461ebde0e21/android-stub/consumer-rules.pro -------------------------------------------------------------------------------- /android-stub/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /android-stub/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android-stub/src/main/java/android/app/IActivityTaskManager.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | import android.os.RemoteException; 7 | 8 | import java.util.List; 9 | 10 | public interface IActivityTaskManager extends IInterface { 11 | void moveRootTaskToDisplay(int taskId, int displayId) throws RemoteException; 12 | 13 | void registerTaskStackListener(ITaskStackListener listener) throws RemoteException; 14 | 15 | void unregisterTaskStackListener(ITaskStackListener listener) throws RemoteException; 16 | 17 | ActivityManager.TaskDescription getTaskDescription(int taskId) throws RemoteException; 18 | 19 | List getAllRootTaskInfosOnDisplay(int displayId) throws RemoteException; 20 | 21 | abstract class Stub extends Binder implements IActivityTaskManager { 22 | public static IActivityTaskManager asInterface(IBinder obj) { 23 | throw new UnsupportedOperationException(); 24 | } 25 | 26 | @Override 27 | public IBinder asBinder() { 28 | throw new UnsupportedOperationException(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android-stub/src/main/java/android/app/ITaskStackListener.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.content.ComponentName; 4 | import android.hardware.display.IDisplayManager; 5 | import android.os.Binder; 6 | import android.os.IBinder; 7 | import android.os.IInterface; 8 | import android.os.RemoteException; 9 | import android.window.TaskSnapshot; 10 | 11 | public interface ITaskStackListener extends IInterface { 12 | void onTaskStackChanged() throws RemoteException; 13 | void onActivityPinned(String packageName, int userId, int taskId, int stackId) throws RemoteException; 14 | void onActivityUnpinned() throws RemoteException; 15 | void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) throws RemoteException; 16 | void onActivityForcedResizable(String packageName, int taskId, int reason) throws RemoteException; 17 | void onActivityDismissingDockedTask() throws RemoteException; 18 | void onActivityLaunchOnSecondaryDisplayFailed(ActivityManager.RunningTaskInfo taskInfo, int requestedDisplayId) throws RemoteException; 19 | void onActivityLaunchOnSecondaryDisplayRerouted(ActivityManager.RunningTaskInfo taskInfo, int requestedDisplayId) throws RemoteException; 20 | void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException; 21 | void onTaskRemoved(int taskId) throws RemoteException; 22 | void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) throws RemoteException; 23 | void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) throws RemoteException; 24 | void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) throws RemoteException; 25 | void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo) throws RemoteException; 26 | void onTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) throws RemoteException; 27 | void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) throws RemoteException; 28 | void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) throws RemoteException; 29 | void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException; 30 | void onRecentTaskListUpdated() throws RemoteException; 31 | void onRecentTaskListFrozenChanged(boolean frozen) throws RemoteException; 32 | void onTaskFocusChanged(int taskId, boolean focused) throws RemoteException; 33 | void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation) throws RemoteException; 34 | void onActivityRotation(int displayId) throws RemoteException; 35 | void onTaskMovedToBack(ActivityManager.RunningTaskInfo taskInfo) throws RemoteException; 36 | void onLockTaskModeChanged(int mode) throws RemoteException; 37 | abstract class Stub extends Binder implements ITaskStackListener { 38 | 39 | public static IDisplayManager asInterface(IBinder binder) { 40 | throw new UnsupportedOperationException(); 41 | } 42 | 43 | @Override 44 | public IBinder asBinder() { 45 | throw new UnsupportedOperationException(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /android-stub/src/main/java/android/content/pm/IPackageManagerHidden.java: -------------------------------------------------------------------------------- 1 | package android.content.pm; 2 | 3 | import android.content.ComponentName; 4 | import android.os.RemoteException; 5 | 6 | import androidx.annotation.RequiresApi; 7 | 8 | import dev.rikka.tools.refine.RefineAs; 9 | 10 | @RefineAs(IPackageManager.class) 11 | public interface IPackageManagerHidden { 12 | @RequiresApi(33) 13 | ActivityInfo getActivityInfo(ComponentName className, long flags, int userId) 14 | throws RemoteException; 15 | 16 | ActivityInfo getActivityInfo(ComponentName className, int flags, int userId) 17 | throws RemoteException; 18 | } 19 | -------------------------------------------------------------------------------- /android-stub/src/main/java/android/content/pm/PackageManagerHidden.java: -------------------------------------------------------------------------------- 1 | package android.content.pm; 2 | 3 | import android.content.Intent; 4 | 5 | import java.util.List; 6 | 7 | import dev.rikka.tools.refine.RefineAs; 8 | 9 | @RefineAs(PackageManager.class) 10 | public abstract class PackageManagerHidden { 11 | public abstract List queryIntentActivitiesAsUser(Intent intent, int flags, int userId); 12 | 13 | public abstract PackageInfo getPackageInfoAsUser(String packageName, 14 | int flags, int userId) throws PackageManager.NameNotFoundException; 15 | 16 | public abstract ApplicationInfo getApplicationInfoAsUser(String packageName, 17 | int flags, int userId) throws PackageManager.NameNotFoundException; 18 | } 19 | -------------------------------------------------------------------------------- /android-stub/src/main/java/android/content/pm/UserInfo.java: -------------------------------------------------------------------------------- 1 | package android.content.pm; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import android.os.UserHandle; 6 | 7 | import androidx.annotation.RequiresApi; 8 | 9 | public class UserInfo { 10 | 11 | public static final int FLAG_MANAGED_PROFILE = 0x00000020; 12 | 13 | public int id; 14 | public String name; 15 | public int flags; 16 | public int serialNumber; 17 | 18 | @RequiresApi(30) 19 | public String userType; 20 | 21 | public boolean isPrimary() { 22 | throw new RuntimeException("STUB"); 23 | } 24 | 25 | public boolean isAdmin() { 26 | throw new RuntimeException("STUB"); 27 | } 28 | 29 | public boolean isGuest() { 30 | throw new RuntimeException("STUB"); 31 | } 32 | 33 | public boolean isRestricted() { 34 | throw new RuntimeException("STUB"); 35 | } 36 | 37 | public boolean isProfile() { 38 | throw new RuntimeException("STUB"); 39 | } 40 | 41 | public boolean isManagedProfile() { 42 | throw new RuntimeException("STUB"); 43 | } 44 | 45 | public boolean isEnabled() { 46 | throw new RuntimeException("STUB"); 47 | } 48 | 49 | public UserHandle getUserHandle() { 50 | throw new RuntimeException("STUB"); 51 | } 52 | 53 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 54 | 55 | public UserInfo createFromParcel(Parcel in) { 56 | throw new UnsupportedOperationException(); 57 | } 58 | 59 | public UserInfo[] newArray(int size) { 60 | throw new UnsupportedOperationException(); 61 | } 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /android-stub/src/main/java/android/hardware/input/IInputManager.java: -------------------------------------------------------------------------------- 1 | package android.hardware.input; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | import android.os.RemoteException; 7 | import android.view.InputEvent; 8 | 9 | public interface IInputManager extends IInterface { 10 | boolean injectInputEvent(InputEvent ev, int mode) throws RemoteException; 11 | 12 | abstract class Stub extends Binder implements IInputManager { 13 | public static IInputManager asInterface(IBinder obj) { 14 | throw new UnsupportedOperationException(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /android-stub/src/main/java/android/view/IRotationWatcher.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | import android.os.Binder; 4 | 5 | public interface IRotationWatcher { 6 | void onRotationChanged(int rotation); 7 | 8 | abstract class Stub extends Binder implements IRotationWatcher { 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /android-stub/src/main/java/android/view/IWindowManager.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | import android.os.RemoteException; 7 | 8 | public interface IWindowManager extends IInterface { 9 | 10 | int watchRotation(IRotationWatcher watcher, int displayId) throws RemoteException; 11 | 12 | void removeRotationWatcher(IRotationWatcher watcher) throws RemoteException; 13 | 14 | abstract class Stub extends Binder implements IWindowManager { 15 | public static IWindowManager asInterface(IBinder obj) { 16 | throw new UnsupportedOperationException(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /android-stub/src/main/java/android/view/WindowLayoutParamsHidden.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | import dev.rikka.tools.refine.RefineAs; 4 | 5 | @RefineAs(WindowManager.LayoutParams.class) 6 | public class WindowLayoutParamsHidden { 7 | public int privateFlags; 8 | 9 | public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000; 10 | } 11 | -------------------------------------------------------------------------------- /android-stub/src/main/java/android/view/WindowManagerHidden.java: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | import androidx.annotation.IntDef; 4 | 5 | import dev.rikka.tools.refine.RefineAs; 6 | 7 | @RefineAs(WindowManager.class) 8 | public interface WindowManagerHidden { 9 | /** 10 | * Display IME Policy: The IME should appear on the local display. 11 | * @hide 12 | */ 13 | int DISPLAY_IME_POLICY_LOCAL = 0; 14 | 15 | /** 16 | * Display IME Policy: The IME should appear on the fallback display. 17 | * @hide 18 | */ 19 | int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; 20 | 21 | /** 22 | * Display IME Policy: The IME should be hidden. 23 | * 24 | * Setting this policy will prevent the IME from making a connection. This 25 | * will prevent any IME from receiving metadata about input. 26 | * @hide 27 | */ 28 | int DISPLAY_IME_POLICY_HIDE = 2; 29 | 30 | /** 31 | * @hide 32 | */ 33 | @IntDef({ 34 | DISPLAY_IME_POLICY_LOCAL, 35 | DISPLAY_IME_POLICY_FALLBACK_DISPLAY, 36 | DISPLAY_IME_POLICY_HIDE, 37 | }) 38 | @interface DisplayImePolicy {} 39 | 40 | /** 41 | * Sets the policy for how the display should show IME. 42 | * 43 | * @param displayId Display ID. 44 | * @param imePolicy Indicates the policy for how the display should show IME. 45 | * @hide 46 | */ 47 | default void setDisplayImePolicy(int displayId, @DisplayImePolicy int imePolicy) { 48 | } 49 | 50 | /** 51 | * Indicates the policy for how the display should show IME. 52 | * 53 | * @param displayId The id of the display. 54 | * @return The policy for how the display should show IME. 55 | * @hide 56 | */ 57 | default @DisplayImePolicy int getDisplayImePolicy(int displayId) { 58 | return DISPLAY_IME_POLICY_FALLBACK_DISPLAY; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /android-stub/src/main/java/android/window/TaskSnapshot.java: -------------------------------------------------------------------------------- 1 | package android.window; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class TaskSnapshot implements Parcelable { 7 | protected TaskSnapshot(Parcel in) {} 8 | 9 | @Override 10 | public void writeToParcel(Parcel dest, int flags) {} 11 | 12 | @Override 13 | public int describeContents() { 14 | return 0; 15 | } 16 | 17 | public static final Creator CREATOR = new Creator() { 18 | @Override 19 | public TaskSnapshot createFromParcel(Parcel in) { 20 | return new TaskSnapshot(in); 21 | } 22 | 23 | @Override 24 | public TaskSnapshot[] newArray(int size) { 25 | return new TaskSnapshot[size]; 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /android-stub/src/main/java/com/android/internal/statusbar/IStatusBarService.java: -------------------------------------------------------------------------------- 1 | package com.android.internal.statusbar; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | 7 | public interface IStatusBarService extends IInterface { 8 | 9 | void collapsePanels(); 10 | 11 | abstract class Stub extends Binder implements IStatusBarService { 12 | public static IStatusBarService asInterface(IBinder obj) { 13 | throw new UnsupportedOperationException(); 14 | } 15 | 16 | @Override 17 | public IBinder asBinder() { 18 | throw new UnsupportedOperationException(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /debug -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties 2 | import java.io.ByteArrayOutputStream 3 | 4 | val localProperties = gradleLocalProperties(rootDir) 5 | 6 | plugins { 7 | id("com.android.application") 8 | kotlin("android") 9 | id("kotlin-android") 10 | id("dev.rikka.tools.refine") version "4.3.0" 11 | } 12 | 13 | android { 14 | val buildTime = System.currentTimeMillis() 15 | val baseVersionName = "0.7" 16 | 17 | compileSdk = 34 18 | 19 | defaultConfig { 20 | applicationId = "io.github.duzhaokun123.yamf" 21 | minSdk = 31 22 | targetSdk = 34 23 | versionCode = 7 24 | versionName = "$baseVersionName-git.$gitHash${if (isDirty) "-dirty" else ""}" 25 | 26 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 27 | 28 | buildConfigField("long", "BUILD_TIME", buildTime.toString()) 29 | } 30 | packaging { 31 | resources.excludes.addAll( 32 | arrayOf( 33 | "META-INF/**", 34 | "kotlin/**" 35 | ) 36 | ) 37 | } 38 | signingConfigs { 39 | create("release") { 40 | storeFile = file("../releaseKey.jks") 41 | storePassword = System.getenv("REL_KEY") 42 | keyAlias = "key0" 43 | keyPassword = System.getenv("REL_KEY") 44 | enableV1Signing = false 45 | enableV2Signing = false 46 | enableV3Signing = true 47 | enableV4Signing = true 48 | } 49 | } 50 | buildTypes { 51 | getByName("release") { 52 | isMinifyEnabled = true 53 | isShrinkResources = true 54 | proguardFiles( 55 | getDefaultProguardFile("proguard-android-optimize.txt"), 56 | "proguard-rules.pro" 57 | ) 58 | signingConfig = if (System.getenv("REL_KEY") != null) { 59 | signingConfigs.getByName("release") 60 | } else { 61 | signingConfigs.getByName("debug") 62 | } 63 | sourceSets.getByName("main").java.srcDir(File("build/generated/ksp/release/kotlin")) 64 | } 65 | getByName("debug") { 66 | val minifyEnabled = localProperties.getProperty("minify.enabled", "false") 67 | isMinifyEnabled = minifyEnabled.toBoolean() 68 | isShrinkResources = minifyEnabled.toBoolean() 69 | proguardFiles( 70 | getDefaultProguardFile("proguard-android-optimize.txt"), 71 | "proguard-rules.pro" 72 | ) 73 | signingConfig = if (System.getenv("REL_KEY") != null) { 74 | signingConfigs.getByName("release") 75 | } else { 76 | signingConfigs.getByName("debug") 77 | } 78 | // sourceSets.getByName("main").java.srcDir(File("build/generated/ksp/debug/kotlin")) 79 | } 80 | } 81 | compileOptions { 82 | sourceCompatibility = JavaVersion.VERSION_11 83 | targetCompatibility = JavaVersion.VERSION_11 84 | } 85 | kotlinOptions { 86 | jvmTarget = "11" 87 | languageVersion = "2.0" 88 | } 89 | buildFeatures { 90 | viewBinding = true 91 | aidl = true 92 | } 93 | lint { 94 | abortOnError = false 95 | } 96 | namespace = "io.github.duzhaokun123.yamf" 97 | } 98 | 99 | dependencies { 100 | implementation("androidx.core:core-ktx:1.12.0") 101 | implementation("androidx.activity:activity-ktx:1.8.2") 102 | implementation("androidx.fragment:fragment-ktx:1.6.2") 103 | implementation("androidx.appcompat:appcompat:1.6.1") 104 | implementation("com.google.android.material:material:1.11.0") 105 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") 106 | implementation("androidx.wear:wear:1.3.0") 107 | implementation("androidx.preference:preference-ktx:1.2.1") 108 | 109 | //kotlinx-coroutines 110 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.3") 111 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") 112 | 113 | compileOnly(project(":android-stub")) 114 | compileOnly("dev.rikka.hidden:stub:4.2.0") 115 | implementation("dev.rikka.hidden:compat:4.2.0") 116 | 117 | //never upgrade until new extension function 118 | //noinspection GradleDependency 119 | implementation("com.github.kyuubiran:EzXHelper:1.0.3") 120 | compileOnly("de.robv.android.xposed:api:82") 121 | 122 | //lifecycle 123 | val lifecycleVersion = "2.6.2" 124 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion") 125 | implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion") 126 | implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion") 127 | 128 | //ViewBindingUtil 129 | implementation("com.github.matsudamper:ViewBindingUtil:0.1") 130 | 131 | //FlexboxLayout 132 | implementation("com.google.android.flexbox:flexbox:3.0.0") 133 | 134 | //dynamicanimation 135 | implementation("androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03") 136 | 137 | //gson 138 | implementation("com.google.code.gson:gson:2.10.1") 139 | } 140 | 141 | val gitHash: String 142 | get() { 143 | val out = ByteArrayOutputStream() 144 | val cmd = exec { 145 | commandLine("git", "rev-parse", "--short", "HEAD") 146 | standardOutput = out 147 | isIgnoreExitValue = true 148 | } 149 | return if (cmd.exitValue == 0) 150 | out.toString().trim() 151 | else 152 | "(error)" 153 | } 154 | 155 | val isDirty: Boolean 156 | get() { 157 | val out = ByteArrayOutputStream() 158 | exec { 159 | commandLine("git", "diff", "--stat") 160 | standardOutput = out 161 | isIgnoreExitValue = true 162 | } 163 | return out.size() != 0 164 | } 165 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class io.github.duzhaokun123.yamf.** { *; } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 14 | 15 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 53 | 54 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 70 | 73 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/aidl/io/github/duzhaokun123/yamf/xposed/IOpenCountListener.aidl: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed; 2 | 3 | interface IOpenCountListener { 4 | void onUpdate(int count); 5 | } -------------------------------------------------------------------------------- /app/src/main/aidl/io/github/duzhaokun123/yamf/xposed/IYAMFManager.aidl: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed; 2 | 3 | import android.view.Surface; 4 | import io.github.duzhaokun123.yamf.xposed.IOpenCountListener; 5 | 6 | interface IYAMFManager { 7 | String getVersionName(); 8 | 9 | int getVersionCode(); 10 | 11 | int getUid(); 12 | 13 | void createWindow(); 14 | 15 | long getBuildTime(); 16 | 17 | String getConfigJson(); 18 | 19 | void updateConfig(String newConfig); 20 | 21 | void registerOpenCountListener(IOpenCountListener iOpenCountListener); 22 | 23 | void unregisterOpenCountListener(IOpenCountListener iOpenCountListener); 24 | 25 | void openAppList(); 26 | 27 | void currentToWindow(); 28 | 29 | void resetAllWindow(); 30 | } -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | io.github.duzhaokun123.yamf.xposed.hook.HookSystem 2 | io.github.duzhaokun123.yamf.xposed.hook.HookLauncher -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/common/Utils.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.common 2 | 3 | import android.content.res.Resources 4 | import android.util.TypedValue 5 | import androidx.annotation.AttrRes 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.google.gson.Gson 8 | import kotlinx.coroutines.CoroutineScope 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.GlobalScope 11 | import kotlinx.coroutines.launch 12 | 13 | val gson by lazy { Gson() } 14 | 15 | fun RecyclerView.resetAdapter() { 16 | this.adapter = adapter 17 | } 18 | 19 | inline fun Result.onException(action: (exception: Exception) -> Unit): Result = 20 | this.onFailure { t -> 21 | if (t is Error) throw t 22 | action(t as Exception) 23 | } 24 | 25 | fun runMain(block: suspend CoroutineScope.() -> Unit) = 26 | GlobalScope.launch(Dispatchers.Main, block = block) 27 | 28 | fun runIO(block: suspend CoroutineScope.() -> Unit) = 29 | GlobalScope.launch(Dispatchers.IO, block = block) 30 | 31 | fun Resources.Theme.getAttr(@AttrRes id: Int) = 32 | TypedValue().apply { resolveAttribute(id, this, true) } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/common/model/Config.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.common.model 2 | 3 | data class Config( 4 | var densityDpi: Int = 200, 5 | /* 6 | * VIRTUAL_DISPLAY_FLAG_SECURE 1 << 2 7 | * VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT 1 << 7 8 | * VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS 1 << 9 9 | * VIRTUAL_DISPLAY_FLAG_TRUSTED 1 << 10 10 | */ 11 | var flags: Int = 1668, 12 | var coloredController: Boolean = false, 13 | /* 14 | * 0: move task only 15 | * 1: start activity only 16 | * 2: move task, failback to start activity 17 | */ 18 | var windowfy: Int = 0, 19 | /* 20 | * 0: TextureView 21 | * 1: SurfaceView 22 | */ 23 | var surfaceView: Int = 0, 24 | var recentsBackHome: Boolean = false, 25 | var showImeInWindow: Boolean = false, 26 | var defaultWindowWidth: Int = 200, 27 | var defaultWindowHeight: Int = 300, 28 | var hookLauncher: HookLauncher = HookLauncher(), 29 | var showForceShowIME: Boolean = false, 30 | ) { 31 | data class HookLauncher( 32 | var hookRecents: Boolean = true, 33 | var hookTaskbar: Boolean = true, 34 | var hookPopup: Boolean = true, 35 | var hookTransientTaskbar: Boolean = false, 36 | ) 37 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/common/model/StartCmd.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.common.model 2 | 3 | import android.content.ComponentName 4 | 5 | data class StartCmd( 6 | val componentName: ComponentName? = null, 7 | val userId: Int? = null, 8 | val taskId: Int? = null 9 | ) { 10 | val canStartActivity 11 | get() = componentName != null && userId != null 12 | 13 | val canMoveTask 14 | get() = taskId != null && taskId != 0 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/Application.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager 2 | 3 | import com.google.android.material.color.DynamicColors 4 | 5 | lateinit var application: Application 6 | 7 | class Application: android.app.Application() { 8 | init { 9 | application = this 10 | } 11 | 12 | override fun onCreate() { 13 | super.onCreate() 14 | DynamicColors.applyToActivitiesIfAvailable(this) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/bases/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.bases 2 | 3 | import android.os.Bundle 4 | import android.view.MenuItem 5 | import android.view.View 6 | import android.view.WindowManager 7 | import android.widget.RelativeLayout 8 | import androidx.activity.viewModels 9 | import androidx.annotation.CallSuper 10 | import androidx.annotation.StringRes 11 | import androidx.annotation.StyleRes 12 | import androidx.appcompat.app.AppCompatActivity 13 | import androidx.coordinatorlayout.widget.CoordinatorLayout 14 | import androidx.core.view.ViewCompat 15 | import androidx.core.view.WindowCompat 16 | import androidx.core.view.WindowInsetsCompat 17 | import androidx.core.view.updateLayoutParams 18 | import androidx.core.view.updatePadding 19 | import androidx.lifecycle.MutableLiveData 20 | import androidx.lifecycle.ViewModel 21 | import androidx.viewbinding.ViewBinding 22 | import io.github.duzhaokun123.yamf.manager.utils.TipUtil 23 | import io.github.duzhaokun123.yamf.manager.utils.maxSystemBarsDisplayCutout 24 | import io.github.duzhaokun123.yamf.R 25 | import io.github.duzhaokun123.yamf.databinding.ActivityBaseRoot2Binding 26 | import net.matsudamper.viewbindingutil.ViewBindingUtil 27 | 28 | abstract class BaseActivity( 29 | private val baseBindingClass: Class, vararg val configs: Config, @StyleRes val themeId: Int = R.style.Theme_YAMF 30 | ) : AppCompatActivity() { 31 | enum class Config { 32 | NO_TOOL_BAR, 33 | LAYOUT_NO_TOOL_BAR, 34 | TRANSPARENT_TOOL_BAR, 35 | NO_BACK, 36 | LAYOUT_MATCH_HORI, 37 | } 38 | 39 | val className by lazy { this::class.simpleName } 40 | val startIntent by lazy { intent } 41 | 42 | lateinit var rootBinding: ActivityBaseRoot2Binding 43 | lateinit var baseBinding: BaseBinding 44 | private set 45 | var isFirstCreate = true 46 | 47 | private val windowInsetsCompatModel by viewModels() 48 | 49 | class WindowInsetsCompatModel : ViewModel() { 50 | val windowInsetsCompat = MutableLiveData() 51 | } 52 | 53 | override fun onCreate(savedInstanceState: Bundle?) { 54 | isFirstCreate = savedInstanceState == null 55 | setTheme(themeId) 56 | super.onCreate(savedInstanceState) 57 | 58 | WindowCompat.setDecorFitsSystemWindows(window, false) 59 | window.attributes.layoutInDisplayCutoutMode = 60 | WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS 61 | 62 | rootBinding = ViewBindingUtil.inflate(layoutInflater) 63 | setContentView(rootBinding.root) 64 | if (Config.NO_TOOL_BAR in configs) rootBinding.rootTb.visibility = View.GONE 65 | if (Config.LAYOUT_NO_TOOL_BAR in configs) 66 | rootBinding.rootFl.updateLayoutParams { 67 | removeRule(RelativeLayout.BELOW) 68 | } 69 | if (Config.TRANSPARENT_TOOL_BAR in configs) { 70 | rootBinding.rootAbl.outlineProvider = null 71 | rootBinding.rootAbl.background = null 72 | } 73 | ViewCompat.setOnApplyWindowInsetsListener(rootBinding.root) { _, insets -> 74 | windowInsetsCompatModel.windowInsetsCompat.value = insets 75 | insets 76 | } 77 | 78 | baseBinding = ViewBindingUtil.inflate(layoutInflater, rootBinding.rootFl, true, baseBindingClass) 79 | 80 | findViews() 81 | setSupportActionBar(initActionBar()) 82 | if (Config.NO_BACK !in configs) supportActionBar?.apply { 83 | setDisplayHomeAsUpEnabled(true) 84 | setDisplayShowHomeEnabled(true) 85 | setHomeAsUpIndicator(R.drawable.ic_arrow_back_24) 86 | } 87 | initViews() 88 | initEvents() 89 | initData() 90 | 91 | TipUtil.registerCoordinatorLayout(this, registerCoordinatorLayout()) 92 | windowInsetsCompatModel.windowInsetsCompat.observe(this, ::onApplyWindowInsetsCompat) 93 | } 94 | 95 | override fun onDestroy() { 96 | super.onDestroy() 97 | TipUtil.unregisterCoordinatorLayout(this) 98 | } 99 | 100 | override fun setTitle(title: CharSequence?) { 101 | supportActionBar?.title = title 102 | } 103 | 104 | fun setSubtitle(subtitle: CharSequence?) { 105 | supportActionBar?.subtitle = subtitle 106 | } 107 | 108 | fun setSubtitle(@StringRes subtitleId: Int) = setSubtitle(getText(subtitleId)) 109 | 110 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 111 | return if (item.itemId == android.R.id.home) { 112 | onBackPressed() 113 | true 114 | } else 115 | super.onOptionsItemSelected(item) 116 | } 117 | 118 | @CallSuper 119 | open fun onApplyWindowInsetsCompat(insets: WindowInsetsCompat) { 120 | with(insets.maxSystemBarsDisplayCutout) { 121 | if (Config.LAYOUT_MATCH_HORI !in configs) { 122 | rootBinding.rootAbl.updatePadding(left = left, right = right) 123 | rootBinding.rootFl.updatePadding(left = left, right = right) 124 | } 125 | if (Config.NO_TOOL_BAR !in configs) 126 | rootBinding.rootAbl.updatePadding(top = top) 127 | } 128 | } 129 | 130 | open fun registerCoordinatorLayout(): CoordinatorLayout? = rootBinding.rootCl 131 | 132 | open fun findViews() {} 133 | open fun initActionBar() = 134 | if (Config.NO_TOOL_BAR in configs) null else rootBinding.rootTb 135 | 136 | open fun initViews() {} 137 | open fun initEvents() {} 138 | open fun initData() {} 139 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/bases/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.bases 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.core.view.WindowInsetsCompat 8 | import androidx.fragment.app.Fragment 9 | import androidx.fragment.app.activityViewModels 10 | import androidx.viewbinding.ViewBinding 11 | import net.matsudamper.viewbindingutil.ViewBindingUtil 12 | 13 | abstract class BaseFragment(private val baseBindingClass: Class) : Fragment() { 14 | val className by lazy { this::class.simpleName } 15 | 16 | lateinit var baseBinding: BaseBinding 17 | private set 18 | var isFirstCreate = true 19 | private set 20 | 21 | private val windowInsetsCompatModel by activityViewModels() 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | isFirstCreate = savedInstanceState == null 25 | super.onCreate(savedInstanceState) 26 | setHasOptionsMenu(true) 27 | } 28 | 29 | override fun onCreateView( 30 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? 31 | ): View { 32 | baseBinding = ViewBindingUtil.inflate(layoutInflater, baseBindingClass) 33 | findViews() 34 | initViews() 35 | initEvents() 36 | initData() 37 | windowInsetsCompatModel.windowInsetsCompat.observe(viewLifecycleOwner,::onApplyWindowInsetsCompat) 38 | return baseBinding.root 39 | } 40 | 41 | val baseActivity 42 | get() = activity as? BaseActivity<*> 43 | 44 | fun requireBaseActivity() = 45 | baseActivity 46 | ?: throw IllegalStateException("Fragment $this not attached to an baseActivity.") 47 | 48 | 49 | open fun findViews() {} 50 | open fun initViews() {} 51 | open fun initEvents() {} 52 | open fun initData() {} 53 | open fun onApplyWindowInsetsCompat(insets: WindowInsetsCompat) {} 54 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/bases/BaseSimpleAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.bases 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import androidx.viewbinding.ViewBinding 8 | import net.matsudamper.viewbindingutil.ViewBindingUtil 9 | 10 | abstract class BaseSimpleAdapter( 11 | val context: Context, 12 | val baseBindingClass: Class 13 | ) : RecyclerView.Adapter>() { 14 | class BaseBindVH(val baseBinding: BaseBinding) : 15 | RecyclerView.ViewHolder(baseBinding.root) 16 | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseBindVH { 18 | val baseBind = ViewBindingUtil.inflate( 19 | LayoutInflater.from(context), null, false, baseBindingClass 20 | ) 21 | return BaseBindVH(baseBind) 22 | } 23 | 24 | override fun onBindViewHolder(holder: BaseBindVH, position: Int) { 25 | initViews(holder.baseBinding, position) 26 | initData(holder.baseBinding, position) 27 | } 28 | 29 | abstract fun initViews(baseBinding: BaseBinding, position: Int) 30 | abstract fun initData(baseBinding: BaseBinding, position: Int) 31 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/providers/ServiceProvider.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.providers 2 | 3 | import android.content.ContentProvider 4 | import android.content.ContentValues 5 | import android.net.Uri 6 | import android.os.Bundle 7 | import io.github.duzhaokun123.yamf.manager.services.YAMFManagerProxy 8 | 9 | class ServiceProvider: ContentProvider() { 10 | override fun onCreate() = false 11 | 12 | override fun query( 13 | uri: Uri, 14 | projection: Array?, 15 | selection: String?, 16 | selectionArgs: Array?, 17 | sortOrder: String? 18 | ) = null 19 | 20 | override fun getType(uri: Uri) = null 21 | 22 | override fun insert(uri: Uri, values: ContentValues?) = null 23 | 24 | override fun delete(uri: Uri, selection: String?, selectionArgs: Array?) = 0 25 | 26 | override fun update( 27 | uri: Uri, 28 | values: ContentValues?, 29 | selection: String?, 30 | selectionArgs: Array? 31 | ) = 0 32 | 33 | override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { 34 | if (callingPackage != "android" || extras == null) return null 35 | val binder = extras.getBinder("binder") ?: return null 36 | YAMFManagerProxy.linkService(binder) 37 | return Bundle() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/services/QSEnterWindow.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.services 2 | 3 | import android.service.quicksettings.TileService 4 | 5 | class QSEnterWindow: TileService() { 6 | override fun onClick() { 7 | super.onClick() 8 | YAMFManagerProxy.currentToWindow() 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/services/QSNewWindowService.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.services 2 | 3 | import android.service.quicksettings.TileService 4 | import androidx.preference.PreferenceManager 5 | 6 | class QSNewWindowService : TileService() { 7 | override fun onClick() { 8 | super.onClick() 9 | if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("useAppList", true)) 10 | YAMFManagerProxy.openAppList() 11 | else YAMFManagerProxy.createWindow() 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/services/QSResetAllWindow.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.services 2 | 3 | import android.service.quicksettings.TileService 4 | 5 | class QSResetAllWindow: TileService() { 6 | override fun onClick() { 7 | YAMFManagerProxy.resetAllWindow() 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/services/YAMFManagerProxy.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.services 2 | 3 | import android.os.IBinder 4 | import android.os.IBinder.DeathRecipient 5 | import android.util.Log 6 | import io.github.duzhaokun123.yamf.xposed.IOpenCountListener 7 | import io.github.duzhaokun123.yamf.xposed.IYAMFManager 8 | import java.lang.reflect.InvocationHandler 9 | import java.lang.reflect.Method 10 | import java.lang.reflect.Proxy 11 | 12 | object YAMFManagerProxy : IYAMFManager, DeathRecipient { 13 | private const val TAG = "YAMFManagerProxy" 14 | 15 | private class ServiceProxy(private val obj: IYAMFManager) : InvocationHandler { 16 | override fun invoke(proxy: Any?, method: Method, args: Array?): Any? { 17 | val result = method.invoke(obj, *args.orEmpty()) 18 | if (result == null) Log.i(TAG, "Call service method ${method.name}") 19 | else Log.i( 20 | TAG, 21 | "Call service method ${method.name} with result " + result.toString().take(20) 22 | ) 23 | return result 24 | } 25 | } 26 | 27 | @Volatile 28 | private var service: IYAMFManager? = null 29 | 30 | fun linkService(binder: IBinder) { 31 | service = Proxy.newProxyInstance( 32 | javaClass.classLoader, 33 | arrayOf(IYAMFManager::class.java), 34 | ServiceProxy(IYAMFManager.Stub.asInterface(binder)) 35 | ) as IYAMFManager 36 | binder.linkToDeath(this, 0) 37 | } 38 | 39 | override fun binderDied() { 40 | service = null 41 | Log.e(TAG, "Binder died") 42 | } 43 | 44 | override fun asBinder() = service?.asBinder() 45 | 46 | override fun getVersionName(): String? { 47 | return service?.versionName 48 | } 49 | 50 | override fun getVersionCode() = service?.versionCode ?: 0 51 | 52 | override fun getUid() = service?.uid ?: -1 53 | 54 | override fun createWindow() { 55 | service?.createWindow() 56 | } 57 | 58 | override fun getBuildTime(): Long { 59 | return service?.buildTime ?: 0 60 | } 61 | 62 | override fun getConfigJson(): String { 63 | return service?.configJson ?: "{}" 64 | } 65 | 66 | override fun updateConfig(newConfig: String) { 67 | service?.updateConfig(newConfig) 68 | } 69 | 70 | override fun registerOpenCountListener(iOpenCountListener: IOpenCountListener) { 71 | service?.registerOpenCountListener(iOpenCountListener) 72 | } 73 | 74 | override fun unregisterOpenCountListener(iOpenCountListener: IOpenCountListener) { 75 | service?.unregisterOpenCountListener(iOpenCountListener) 76 | } 77 | 78 | override fun openAppList() { 79 | service?.openAppList() 80 | } 81 | 82 | override fun currentToWindow() { 83 | service?.currentToWindow() 84 | } 85 | 86 | override fun resetAllWindow() { 87 | service?.resetAllWindow() 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/ui/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.ui 2 | 3 | import android.content.Intent 4 | import androidx.appcompat.widget.PopupMenu 5 | import androidx.core.net.toUri 6 | import androidx.preference.PreferenceManager 7 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 8 | import io.github.duzhaokun123.yamf.manager.bases.BaseActivity 9 | import io.github.duzhaokun123.yamf.common.gson 10 | import io.github.duzhaokun123.yamf.databinding.ActivitySettingsBinding 11 | import io.github.duzhaokun123.yamf.manager.services.YAMFManagerProxy 12 | import io.github.duzhaokun123.yamf.common.model.Config as YAMFConfig 13 | 14 | class SettingsActivity : 15 | BaseActivity(ActivitySettingsBinding::class.java) { 16 | companion object { 17 | val flags = listOf( 18 | "VIRTUAL_DISPLAY_FLAG_PUBLIC", // 1 << 0 19 | "VIRTUAL_DISPLAY_FLAG_PRESENTATION", // 1 << 1 20 | "VIRTUAL_DISPLAY_FLAG_SECURE", // 1 << 2 21 | "VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY", // 1 << 3 22 | "VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR", // 1 << 4 23 | "VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD", // 1 << 5 24 | "VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH", // 1 << 6 25 | "VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT", // 1 << 7 26 | "VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL", // 1 << 8 27 | "VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS", // 1 << 9 28 | "VIRTUAL_DISPLAY_FLAG_TRUSTED", // 1 << 10 29 | "VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP", // 1 << 11 30 | "VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED", // 1 << 12 31 | "VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED", // 1 << 13 32 | ) 33 | } 34 | 35 | lateinit var config: YAMFConfig 36 | val preference by lazy { PreferenceManager.getDefaultSharedPreferences(this) } 37 | 38 | override fun initData() { 39 | super.initData() 40 | config = gson.fromJson(YAMFManagerProxy.configJson, YAMFConfig::class.java) 41 | baseBinding.etDensityDpi.setText(config.densityDpi.toString()) 42 | baseBinding.btnFlags.text = config.flags.toString() 43 | baseBinding.sColoerd.isChecked = config.coloredController 44 | baseBinding.btnWindowsfy.text = config.windowfy.toString() 45 | baseBinding.btnSurface.text = config.surfaceView.toString() 46 | baseBinding.sBackHome.isChecked = config.recentsBackHome 47 | baseBinding.sShowIMEinWindow.isChecked = config.showImeInWindow 48 | baseBinding.etSizeH.setText(config.defaultWindowHeight.toString()) 49 | baseBinding.etSizeW.setText(config.defaultWindowWidth.toString()) 50 | baseBinding.sHookLauncherHookRecents.isChecked = config.hookLauncher.hookRecents 51 | baseBinding.sHookLauncherHookTaskbar.isChecked = config.hookLauncher.hookTaskbar 52 | baseBinding.sHookLauncherHookPopup.isChecked = config.hookLauncher.hookPopup 53 | baseBinding.sHookLauncherHookTransientTaskbar.isChecked = 54 | config.hookLauncher.hookTransientTaskbar 55 | baseBinding.sUseAppList.isChecked = preference.getBoolean("useAppList", true) 56 | baseBinding.sForceShowIME.isChecked = config.showForceShowIME 57 | 58 | baseBinding.btnFlags.setOnClickListener { 59 | val checks = BooleanArray(flags.size) { i -> 60 | config.flags and (1 shl i) != 0 61 | } 62 | MaterialAlertDialogBuilder(this) 63 | .setMultiChoiceItems(flags.toTypedArray(), checks) { _, i, c -> 64 | checks[i] = c 65 | baseBinding.btnFlags.text = checks.foldIndexed(0) { i, f, b -> 66 | if (b) 67 | f + (1 shl i) 68 | else 69 | f 70 | }.toString() 71 | } 72 | .setPositiveButton("about") { _, _ -> 73 | startActivity(Intent(Intent.ACTION_VIEW).apply { 74 | data = "https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/hardware/display/DisplayManager.java".toUri() 75 | }) 76 | } 77 | .show() 78 | } 79 | baseBinding.btnWindowsfy.setOnClickListener { 80 | PopupMenu(this, baseBinding.btnWindowsfy).apply { 81 | listOf("0", "1", "2").forEach { i -> 82 | menu.add(i).setOnMenuItemClickListener { 83 | baseBinding.btnWindowsfy.text = i 84 | true 85 | } 86 | } 87 | }.show() 88 | } 89 | baseBinding.btnSurface.setOnClickListener { 90 | PopupMenu(this, baseBinding.btnSurface).apply { 91 | listOf("0", "1").forEach { i -> 92 | menu.add(i).setOnMenuItemClickListener { 93 | baseBinding.btnSurface.text = i 94 | true 95 | } 96 | } 97 | }.show() 98 | } 99 | } 100 | 101 | override fun onDestroy() { 102 | super.onDestroy() 103 | config.densityDpi = baseBinding.etDensityDpi.text.toString().toIntOrNull() ?: config.densityDpi 104 | config.flags = baseBinding.btnFlags.text.toString().toIntOrNull() ?: config.flags 105 | config.coloredController = baseBinding.sColoerd.isChecked 106 | config.windowfy = baseBinding.btnWindowsfy.text.toString().toIntOrNull() ?: config.windowfy 107 | config.surfaceView = baseBinding.btnSurface.text.toString().toIntOrNull() ?: config.surfaceView 108 | config.recentsBackHome = baseBinding.sBackHome.isChecked 109 | config.showImeInWindow = baseBinding.sShowIMEinWindow.isChecked 110 | config.defaultWindowHeight = baseBinding.etSizeH.text.toString().toIntOrNull() ?: config.defaultWindowHeight 111 | config.defaultWindowWidth = baseBinding.etSizeW.text.toString().toIntOrNull() ?: config.defaultWindowWidth 112 | config.hookLauncher.hookRecents = baseBinding.sHookLauncherHookRecents.isChecked 113 | config.hookLauncher.hookTaskbar = baseBinding.sHookLauncherHookTaskbar.isChecked 114 | config.hookLauncher.hookPopup = baseBinding.sHookLauncherHookPopup.isChecked 115 | config.hookLauncher.hookTransientTaskbar = baseBinding.sHookLauncherHookTransientTaskbar.isChecked 116 | config.showForceShowIME = baseBinding.sForceShowIME.isChecked 117 | YAMFManagerProxy.updateConfig(gson.toJson(config)) 118 | preference.edit().putBoolean("useAppList", baseBinding.sUseAppList.isChecked).apply() 119 | } 120 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/ui/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.ui.main 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Intent 5 | import android.graphics.Color 6 | import android.os.Build 7 | import android.os.Bundle 8 | import android.util.Log 9 | import android.view.Menu 10 | import android.view.MenuInflater 11 | import android.view.MenuItem 12 | import android.view.View 13 | import android.widget.RelativeLayout 14 | import androidx.core.net.toUri 15 | import androidx.core.view.MenuProvider 16 | import com.google.android.material.color.MaterialColors 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 18 | import io.github.duzhaokun123.yamf.BuildConfig 19 | import io.github.duzhaokun123.yamf.R 20 | import io.github.duzhaokun123.yamf.common.getAttr 21 | import io.github.duzhaokun123.yamf.common.runMain 22 | import io.github.duzhaokun123.yamf.databinding.ActivityMainBinding 23 | import io.github.duzhaokun123.yamf.manager.bases.BaseActivity 24 | import io.github.duzhaokun123.yamf.manager.services.YAMFManagerProxy 25 | import io.github.duzhaokun123.yamf.manager.ui.SettingsActivity 26 | import io.github.duzhaokun123.yamf.manager.utils.TipUtil 27 | import io.github.duzhaokun123.yamf.xposed.IOpenCountListener 28 | 29 | 30 | class MainActivity: BaseActivity(ActivityMainBinding::class.java, Config.NO_BACK), 31 | MenuProvider { 32 | companion object { 33 | const val TAG = "YAMF_MainActivity" 34 | } 35 | 36 | private val openCountListener = object : IOpenCountListener.Stub() { 37 | override fun onUpdate(count: Int) { 38 | runMain { 39 | baseBinding.tvOpenCount.text = count.toString() 40 | } 41 | } 42 | } 43 | 44 | override fun onCreate(savedInstanceState: Bundle?) { 45 | super.onCreate(savedInstanceState) 46 | addMenuProvider(this, this) 47 | YAMFManagerProxy.registerOpenCountListener(openCountListener) 48 | } 49 | 50 | override fun initViews() { 51 | super.initViews() 52 | baseBinding.ll.findViewById(R.id.rl_cardRoot).addView( 53 | View(this).apply { 54 | setBackgroundColor(Color.BLACK) 55 | id = R.id.surface 56 | }, 0, 57 | RelativeLayout.LayoutParams(baseBinding.ll.findViewById(R.id.v_sizePreviewer).layoutParams) 58 | .apply { 59 | addRule(RelativeLayout.BELOW, R.id.rl_top) 60 | } 61 | ) 62 | } 63 | @SuppressLint("SetTextI18n") 64 | override fun initData() { 65 | val buildTime = YAMFManagerProxy.buildTime 66 | Log.d(TAG, "buildtime: $buildTime ${BuildConfig.BUILD_TIME}") 67 | when(buildTime) { 68 | 0L -> { 69 | baseBinding.ivIcon.setImageResource(R.drawable.ic_error_outline_24) 70 | baseBinding.tvActive.setText(R.string.not_activated) 71 | baseBinding.tvVersion.text = "" 72 | val colorError = theme.getAttr(com.google.android.material.R.attr.colorError).data 73 | val colorOnError = theme.getAttr(com.google.android.material.R.attr.colorOnError).data 74 | baseBinding.mcvStatus.setCardBackgroundColor(colorError) 75 | baseBinding.mcvStatus.outlineAmbientShadowColor = colorError 76 | baseBinding.mcvStatus.outlineSpotShadowColor = colorError 77 | baseBinding.tvActive.setTextColor(colorOnError) 78 | baseBinding.tvVersion.setTextColor(colorOnError) 79 | baseBinding.mcvInfo.visibility = View.GONE 80 | } 81 | BuildConfig.BUILD_TIME -> { 82 | baseBinding.ivIcon.setImageResource(R.drawable.ic_round_check_circle_24) 83 | baseBinding.tvActive.setText(R.string.activated) 84 | baseBinding.tvVersion.text = "${YAMFManagerProxy.versionName} (${YAMFManagerProxy.versionCode})" 85 | } 86 | else -> { 87 | baseBinding.ivIcon.setImageResource(R.drawable.ic_warning_amber_24) 88 | baseBinding.tvActive.setText(R.string.need_reboot) 89 | baseBinding.tvVersion.text = "system: ${YAMFManagerProxy.versionName} (${YAMFManagerProxy.versionCode})\n" + 90 | "module: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" 91 | baseBinding.mcvStatus.setCardBackgroundColor(MaterialColors.harmonizeWithPrimary(this, getColor(R.color.color_warning))) 92 | baseBinding.mcvStatus.setOnClickListener { 93 | MaterialAlertDialogBuilder(this) 94 | .setTitle(R.string.need_reboot) 95 | .setMessage(R.string.need_reboot_message) 96 | .setPositiveButton(R.string.reboot) { _, _-> 97 | TipUtil.showTip(this, "do it yourself") 98 | } 99 | .show() 100 | } 101 | } 102 | } 103 | if (Build.VERSION.PREVIEW_SDK_INT != 0) { 104 | baseBinding.systemVersion.text = "${Build.VERSION.CODENAME} Preview (API ${Build.VERSION.SDK_INT})" 105 | } else { 106 | baseBinding.systemVersion.text = "${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})" 107 | } 108 | baseBinding.tvBuildType.text = BuildConfig.BUILD_TYPE 109 | } 110 | 111 | override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { 112 | menuInflater.inflate(R.menu.menu_main, menu) 113 | } 114 | 115 | override fun onMenuItemSelected(menuItem: MenuItem): Boolean { 116 | return when(menuItem.itemId) { 117 | R.id.new_window -> { 118 | YAMFManagerProxy.createWindow() 119 | true 120 | } 121 | R.id.channel -> { 122 | startActivity(Intent(Intent.ACTION_VIEW).apply { 123 | data = "https://t.me/YAMF_channel".toUri() 124 | }) 125 | true 126 | } 127 | R.id.open_app_list -> { 128 | YAMFManagerProxy.openAppList() 129 | true 130 | } 131 | R.id.settings -> { 132 | startActivity(Intent(this, SettingsActivity::class.java)) 133 | true 134 | } 135 | R.id.github -> { 136 | startActivity(Intent(Intent.ACTION_VIEW).apply { 137 | data = "https://github.com/duzhaokun123/YAMF".toUri() 138 | }) 139 | true 140 | } 141 | R.id.donate -> { 142 | startActivity(Intent(Intent.ACTION_VIEW).apply { 143 | data = "https://duzhaokun123.github.io/donate.html".toUri() 144 | }) 145 | true 146 | } 147 | R.id.current_to_window -> { 148 | YAMFManagerProxy.currentToWindow() 149 | true 150 | } 151 | else -> false 152 | } 153 | } 154 | 155 | override fun onDestroy() { 156 | super.onDestroy() 157 | YAMFManagerProxy.unregisterOpenCountListener(openCountListener) 158 | } 159 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/utils/TipUtil.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.utils 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.widget.TextView 6 | import android.widget.Toast 7 | import androidx.annotation.StringRes 8 | import androidx.coordinatorlayout.widget.CoordinatorLayout 9 | import androidx.core.view.isVisible 10 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 11 | import com.google.android.material.snackbar.BaseTransientBottomBar 12 | import com.google.android.material.snackbar.Snackbar 13 | import io.github.duzhaokun123.yamf.R 14 | import io.github.duzhaokun123.yamf.common.runMain 15 | import io.github.duzhaokun123.yamf.manager.application 16 | 17 | object TipUtil { 18 | private val map = mutableMapOf() 19 | 20 | fun registerCoordinatorLayout(context: Context, coordinatorLayout: CoordinatorLayout?) { 21 | coordinatorLayout?.let { map[context.hashCode()] = it } 22 | } 23 | 24 | fun unregisterCoordinatorLayout(context: Context) { 25 | map.remove(context.hashCode()) 26 | } 27 | 28 | fun showToast(msg: CharSequence?) { 29 | runMain { 30 | Toast.makeText(application, "$msg", Toast.LENGTH_LONG).show() 31 | } 32 | } 33 | 34 | fun showToast(@StringRes resId: Int) = 35 | showToast(application.getText(resId)) 36 | 37 | fun showSnackbar(coordinatorLayout: CoordinatorLayout, msg: CharSequence?) { 38 | runMain { 39 | Snackbar.make(coordinatorLayout, "$msg", BaseTransientBottomBar.LENGTH_LONG).show() 40 | } 41 | } 42 | 43 | fun showSnackbar(coordinatorLayout: CoordinatorLayout, @StringRes resId: Int) = 44 | showSnackbar(coordinatorLayout, application.getText(resId)) 45 | 46 | fun showSnackbar(coordinatorLayout: CoordinatorLayout, t: Throwable) { 47 | runMain { 48 | val msg = t.localizedMessage ?: t.message ?: application.getString(R.string.unknown_error) 49 | Snackbar.make(coordinatorLayout, msg, BaseTransientBottomBar.LENGTH_LONG) 50 | .setAction(R.string.details) { 51 | MaterialAlertDialogBuilder(coordinatorLayout.context) 52 | .setTitle(msg) 53 | .setMessage("${t.message}\n${t.stackTraceToString()}") 54 | .show() 55 | .findViewById(android.R.id.message) 56 | ?.setTextIsSelectable(true) 57 | }.show() 58 | } 59 | } 60 | 61 | fun showTip(context: Context?, t: Throwable) { 62 | val msg = t.localizedMessage ?: t.message ?: "未知错误" 63 | if (context == null || (context is Activity && context.window.decorView.isVisible.not())) { 64 | showToast(msg) 65 | return 66 | } 67 | map[context.hashCode()]?.let { 68 | showSnackbar(it, t) 69 | return 70 | } 71 | showToast(msg) 72 | } 73 | 74 | fun showTip(context: Context?, msg: CharSequence?) { 75 | if (context == null || (context is Activity && context.window.decorView.isVisible.not())) { 76 | showToast(msg) 77 | return 78 | } 79 | map[context.hashCode()]?.let { 80 | showSnackbar(it, msg) 81 | return 82 | } 83 | showToast(msg) 84 | } 85 | 86 | fun showTip(context: Context?, @StringRes resId: Int) = 87 | showTip(context, application.getText(resId)) 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/manager/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.manager.utils 2 | 3 | import androidx.core.view.WindowInsetsCompat 4 | 5 | val WindowInsetsCompat.maxSystemBarsDisplayCutout 6 | get() = getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()) 7 | 8 | val WindowInsetsCompat.maxSystemBarsDisplayCutoutIme 9 | get() = getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() or WindowInsetsCompat.Type.ime()) 10 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/xposed/hook/HookLauncher.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed.hook 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.AndroidAppHelper 5 | import android.app.Application 6 | import android.app.PendingIntent 7 | import android.app.RemoteAction 8 | import android.content.ComponentName 9 | import android.content.Intent 10 | import android.graphics.drawable.Icon 11 | import android.os.UserHandle 12 | import android.view.View 13 | import android.widget.ImageView 14 | import android.widget.TextView 15 | import androidx.core.graphics.drawable.toBitmap 16 | import com.github.kyuubiran.ezxhelper.init.EzXHelperInit 17 | import com.github.kyuubiran.ezxhelper.init.InitFields.moduleRes 18 | import com.github.kyuubiran.ezxhelper.utils.argTypes 19 | import com.github.kyuubiran.ezxhelper.utils.args 20 | import com.github.kyuubiran.ezxhelper.utils.findAllMethods 21 | import com.github.kyuubiran.ezxhelper.utils.findConstructor 22 | import com.github.kyuubiran.ezxhelper.utils.findField 23 | import com.github.kyuubiran.ezxhelper.utils.findMethod 24 | import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull 25 | import com.github.kyuubiran.ezxhelper.utils.getObject 26 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 27 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 28 | import com.github.kyuubiran.ezxhelper.utils.hookReplace 29 | import com.github.kyuubiran.ezxhelper.utils.hookReturnConstant 30 | import com.github.kyuubiran.ezxhelper.utils.invokeMethod 31 | import com.github.kyuubiran.ezxhelper.utils.invokeMethodAuto 32 | import com.github.kyuubiran.ezxhelper.utils.invokeMethodAutoAs 33 | import com.github.kyuubiran.ezxhelper.utils.loadClass 34 | import com.github.kyuubiran.ezxhelper.utils.loadClassOrNull 35 | import com.github.kyuubiran.ezxhelper.utils.newInstance 36 | import com.github.kyuubiran.ezxhelper.utils.paramCount 37 | import de.robv.android.xposed.IXposedHookLoadPackage 38 | import de.robv.android.xposed.IXposedHookZygoteInit 39 | import de.robv.android.xposed.XC_MethodHook 40 | import de.robv.android.xposed.XposedBridge 41 | import de.robv.android.xposed.XposedHelpers 42 | import de.robv.android.xposed.callbacks.XC_LoadPackage 43 | import io.github.duzhaokun123.yamf.BuildConfig 44 | import io.github.duzhaokun123.yamf.R 45 | import io.github.duzhaokun123.yamf.xposed.services.YAMFManager 46 | import io.github.duzhaokun123.yamf.xposed.utils.log 47 | import io.github.duzhaokun123.yamf.xposed.utils.registerReceiver 48 | import java.lang.reflect.Proxy 49 | import java.util.stream.Stream 50 | 51 | 52 | class HookLauncher : IXposedHookLoadPackage, IXposedHookZygoteInit { 53 | companion object { 54 | const val TAG = "YAMF_HookLauncher" 55 | const val ACTION_RECEIVE_LAUNCHER_CONFIG = 56 | "io.github.duzhaokun123.yamf.ACTION_RECEIVE_LAUNCHER_CONFIG" 57 | 58 | const val EXTRA_HOOK_RECENTS = "hookRecents" 59 | const val EXTRA_HOOK_TASKBAR = "hookTaskbar" 60 | const val EXTRA_HOOK_POPUP = "hookPopup" 61 | const val EXTRA_HOOK_TRANSIENT_TASKBAR = "hookTransientTaskbar" 62 | } 63 | 64 | override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) { 65 | EzXHelperInit.initZygote(startupParam) 66 | } 67 | 68 | @SuppressLint("UnspecifiedRegisterReceiverFlag") 69 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { 70 | EzXHelperInit.initHandleLoadPackage(lpparam) 71 | loadClassOrNull("com.android.launcher3.Launcher") ?: return 72 | Application::class.java.findMethod { 73 | name == "onCreate" 74 | }.hookAfter { 75 | val application = it.thisObject as Application 76 | application.registerReceiver(ACTION_RECEIVE_LAUNCHER_CONFIG) { _, intent -> 77 | val hookRecents = intent.getBooleanExtra(EXTRA_HOOK_RECENTS, false) 78 | val hookTaskbar = intent.getBooleanExtra(EXTRA_HOOK_TASKBAR, false) 79 | val hookPopup = intent.getBooleanExtra(EXTRA_HOOK_POPUP, false) 80 | val hookTransientTaskbar = 81 | intent.getBooleanExtra(EXTRA_HOOK_TRANSIENT_TASKBAR, false) 82 | log( 83 | TAG, 84 | "receive config hookRecents=$hookRecents hookTaskbar=$hookTaskbar hookPopup=$hookPopup hookTranslucentTaskbar=$hookTransientTaskbar" 85 | ) 86 | if (hookRecents) runCatching { hookRecents(lpparam) }.onFailure { e -> 87 | log(TAG, "hook recents failed", e) } 88 | if (hookTaskbar) runCatching { hookTaskbar(lpparam) }.onFailure { e -> 89 | log(TAG, "hook taskbar failed", e) } 90 | if (hookPopup) runCatching { hookPopup(lpparam) }.onFailure { e -> 91 | log(TAG, "hook popup failed", e) } 92 | // if (hookTransientTaskbar) hookTransientTaskbar(lpparam) 93 | application.unregisterReceiver(this) 94 | } 95 | application.sendBroadcast(Intent(YAMFManager.ACTION_GET_LAUNCHER_CONFIG).apply { 96 | `package` = "android" 97 | putExtra("sender", application.packageName) 98 | }) 99 | } 100 | 101 | hookTransientTaskbar(lpparam) 102 | } 103 | 104 | private fun hookRecents(lpparam: XC_LoadPackage.LoadPackageParam) { 105 | log(TAG, "hooking recents ${lpparam.packageName}") 106 | XposedBridge.hookAllMethods( 107 | XposedHelpers.findClass( 108 | "com.android.quickstep.TaskOverlayFactory", 109 | lpparam.classLoader 110 | ), "getEnabledShortcuts", object : XC_MethodHook() { 111 | @SuppressLint("UseCompatLoadingForDrawables") 112 | override fun afterHookedMethod(param: MethodHookParam) { 113 | val taskView = param.args[0] as View 114 | val shortcuts = param.result as MutableList 115 | var itemInfo = XposedHelpers.getObjectField(shortcuts[0], "mItemInfo") 116 | itemInfo = 117 | itemInfo.javaClass.newInstance(args(itemInfo), argTypes(itemInfo.javaClass)) 118 | val activity = taskView.context 119 | val task = XposedHelpers.callMethod(taskView, "getTask") 120 | val key = XposedHelpers.getObjectField(task, "key") 121 | val taskId = XposedHelpers.getIntField(key, "id") 122 | val topComponent = 123 | XposedHelpers.callMethod(itemInfo, "getTargetComponent") as ComponentName 124 | val userId = XposedHelpers.getIntField(key, "userId") 125 | 126 | val class_RemoteActionShortcut = XposedHelpers.findClass( 127 | "com.android.launcher3.popup.RemoteActionShortcut", 128 | lpparam.classLoader 129 | ) 130 | val intent = Intent(YAMFManager.ACTION_OPEN_IN_YAMF).apply { 131 | setPackage("android") 132 | putExtra(YAMFManager.EXTRA_TASK_ID, taskId) 133 | putExtra(YAMFManager.EXTRA_COMPONENT_NAME, topComponent) 134 | putExtra(YAMFManager.EXTRA_USER_ID, userId) 135 | putExtra(YAMFManager.EXTRA_SOURCE, YAMFManager.SOURCE_RECENTS) 136 | } 137 | val action = RemoteAction( 138 | Icon.createWithBitmap( 139 | moduleRes.getDrawable(R.drawable.ic_picture_in_picture_alt_24, null) 140 | .toBitmap() 141 | ), 142 | moduleRes.getString(R.string.open_with_yamf) + if (BuildConfig.DEBUG) " ($taskId)" else "", 143 | "", 144 | PendingIntent.getBroadcast( 145 | AndroidAppHelper.currentApplication(), 146 | 1345, 147 | intent, 148 | PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE 149 | ) 150 | ) 151 | val c = class_RemoteActionShortcut.constructors[0] 152 | val shortcut = when (c.parameterCount) { 153 | 4 -> c.newInstance(action, activity, itemInfo, null) 154 | 3 -> c.newInstance(action, activity, itemInfo) 155 | else -> { 156 | log( 157 | TAG, 158 | "unknown RemoteActionShortcut constructor: ${c.toGenericString()}" 159 | ) 160 | null 161 | } 162 | } 163 | 164 | if (shortcut != null) { 165 | shortcuts.add(shortcut) 166 | } 167 | } 168 | }) 169 | } 170 | 171 | private fun hookTaskbar(lpparam: XC_LoadPackage.LoadPackageParam) { 172 | log(TAG, "hooking taskbar ${lpparam.packageName}") 173 | loadClass("com.android.launcher3.taskbar.TaskbarActivityContext").apply { 174 | findMethodOrNull { name == "startItemInfoActivity" } 175 | ?.hookReplace { 176 | val infoIntent = it.args[0].invokeMethodAutoAs("getIntent")!! 177 | val intent = Intent(YAMFManager.ACTION_OPEN_IN_YAMF).apply { 178 | setPackage("android") 179 | putExtra(YAMFManager.EXTRA_COMPONENT_NAME, infoIntent.component) 180 | putExtra(YAMFManager.EXTRA_SOURCE, YAMFManager.SOURCE_TASKBAR) 181 | } 182 | AndroidAppHelper.currentApplication().sendBroadcast(intent) 183 | } 184 | val class_WorkspaceItemInfo = 185 | loadClass("com.android.launcher3.model.data.WorkspaceItemInfo") 186 | findMethod { name == "onTaskbarIconClicked" } 187 | .hookBefore { 188 | val tag = it.args[0].invokeMethodAuto("getTag")!! 189 | if (class_WorkspaceItemInfo.isInstance(tag)) { 190 | val infoIntent = tag.invokeMethodAutoAs("getIntent")!! 191 | val intent = Intent(YAMFManager.ACTION_OPEN_IN_YAMF).apply { 192 | setPackage("android") 193 | putExtra(YAMFManager.EXTRA_COMPONENT_NAME, infoIntent.component) 194 | putExtra(YAMFManager.EXTRA_SOURCE, YAMFManager.SOURCE_TASKBAR) 195 | } 196 | AndroidAppHelper.currentApplication().sendBroadcast(intent) 197 | it.result = Unit 198 | } 199 | } 200 | } 201 | 202 | } 203 | 204 | var proxyClass: Any? = null 205 | 206 | @SuppressLint("UseCompatLoadingForDrawables") 207 | private fun handleProxyMethod(param: XC_MethodHook.MethodHookParam) { 208 | val methodName = param.method.name 209 | val thiz = param.thisObject 210 | when (methodName) { 211 | "onClick" -> { 212 | val mItemInfo = thiz.getObject("mItemInfo") 213 | val componentName = mItemInfo.invokeMethod("getTargetComponent") as ComponentName 214 | val userId = (mItemInfo.getObject("user") as UserHandle) 215 | AndroidAppHelper.currentApplication() 216 | .sendBroadcast(Intent(YAMFManager.ACTION_OPEN_APP).apply { 217 | setPackage("android") 218 | putExtra(YAMFManager.EXTRA_COMPONENT_NAME, componentName) 219 | putExtra(YAMFManager.EXTRA_USER_ID, userId) 220 | putExtra(YAMFManager.EXTRA_SOURCE, YAMFManager.SOURCE_POPUP) 221 | }) 222 | thiz.invokeMethodAuto("dismissTaskMenuView", thiz.getObject("mTarget")) 223 | param.result = Unit 224 | } 225 | 226 | "setIconAndContentDescriptionFor" -> { 227 | val view = param.args[0] as ImageView 228 | view.setImageDrawable( 229 | moduleRes.getDrawable( 230 | R.drawable.ic_picture_in_picture_alt_24, 231 | null 232 | ) 233 | ) 234 | view.contentDescription = moduleRes.getString(R.string.open_with_yamf) 235 | param.result = Unit 236 | } 237 | 238 | "setIconAndLabelFor" -> { 239 | val iconView = param.args[0] as View 240 | val labelView = param.args[1] as TextView 241 | iconView.background = 242 | moduleRes.getDrawable(R.drawable.ic_picture_in_picture_alt_24, null) 243 | labelView.text = moduleRes.getString(R.string.open_with_yamf) 244 | param.result = Unit 245 | } 246 | } 247 | } 248 | 249 | private fun hookPopup(lpparam: XC_LoadPackage.LoadPackageParam) { 250 | log(TAG, "hooking popup ${lpparam.packageName}") 251 | // loadClass("com.android.launcher3.Launcher") 252 | // .findMethod { name == "getSupportedShortcuts" } 253 | // .hookAfter { 254 | // val r = (it.result as Stream<*>).toArray() 255 | // it.result = Stream.of(*r, getOpenInYAMFSystemShortcutFactory(lpparam.classLoader)) 256 | // } 257 | loadClass("com.android.launcher3.popup.SystemShortcut") 258 | .findField { name == "INSTALL" } 259 | .set(null, getOpenInYAMFSystemShortcutFactory(lpparam.classLoader)) 260 | loadClass("com.android.launcher3.popup.SystemShortcut") 261 | .findAllMethods { true } 262 | .hookBefore { 263 | val thiz = it.thisObject 264 | if (thiz !== proxyClass) return@hookBefore 265 | handleProxyMethod(it) 266 | } 267 | loadClass("com.android.launcher3.popup.SystemShortcut\$Install") 268 | .findAllMethods { true } 269 | .hookBefore { 270 | val thiz = it.thisObject 271 | if (thiz !== proxyClass) return@hookBefore 272 | handleProxyMethod(it) 273 | } 274 | } 275 | 276 | fun getOpenInYAMFSystemShortcutFactory(classLoader: ClassLoader): Any { 277 | return Proxy.newProxyInstance( 278 | classLoader, arrayOf(loadClass("com.android.launcher3.popup.SystemShortcut\$Factory")) 279 | ) { _, method, args -> 280 | if (method.name != "getShortcut") return@newProxyInstance Unit 281 | return@newProxyInstance loadClass("com.android.launcher3.popup.SystemShortcut\$Install") 282 | .findConstructor { paramCount == 3 } 283 | .newInstance(args[0], args[1], args[2]) 284 | .also { proxyClass = it } 285 | } 286 | } 287 | 288 | private fun hookTransientTaskbar(lpparam: XC_LoadPackage.LoadPackageParam) { 289 | log(TAG, "hook transientTaskbar ${lpparam.packageName}") 290 | loadClass("com.android.launcher3.util.DisplayController") 291 | .findMethod { name == "isTransientTaskbar" } 292 | .hookReturnConstant(true) 293 | } 294 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/xposed/hook/HookSystem.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed.hook 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.pm.IPackageManager 6 | import com.github.kyuubiran.ezxhelper.init.EzXHelperInit 7 | import com.github.kyuubiran.ezxhelper.utils.findAllConstructors 8 | import com.github.kyuubiran.ezxhelper.utils.findMethod 9 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 10 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 11 | import de.robv.android.xposed.IXposedHookLoadPackage 12 | import de.robv.android.xposed.IXposedHookZygoteInit 13 | import de.robv.android.xposed.XC_MethodHook 14 | import de.robv.android.xposed.callbacks.XC_LoadPackage 15 | import io.github.duzhaokun123.yamf.BuildConfig 16 | import io.github.duzhaokun123.yamf.xposed.services.YAMFManager 17 | import io.github.duzhaokun123.yamf.xposed.services.UserService 18 | import io.github.duzhaokun123.yamf.xposed.utils.log 19 | import io.github.qauxv.util.Initiator 20 | import kotlin.concurrent.thread 21 | 22 | class HookSystem : IXposedHookZygoteInit, IXposedHookLoadPackage { 23 | companion object { 24 | private const val TAG = "YAMF_HookSystem" 25 | } 26 | 27 | override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) { 28 | EzXHelperInit.initZygote(startupParam) 29 | } 30 | 31 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { 32 | if (lpparam.packageName != "android") return 33 | log(TAG, "xposed init") 34 | log(TAG, "buildtype: ${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE}) ${BuildConfig.BUILD_TYPE}") 35 | EzXHelperInit.initHandleLoadPackage(lpparam) 36 | Initiator.init(lpparam.classLoader) 37 | 38 | var serviceManagerHook: XC_MethodHook.Unhook? = null 39 | serviceManagerHook = findMethod("android.os.ServiceManager") { 40 | name == "addService" 41 | }.hookBefore { param -> 42 | if (param.args[0] == "package") { 43 | serviceManagerHook?.unhook() 44 | val pms = param.args[1] as IPackageManager 45 | log(TAG, "Got pms: $pms") 46 | thread { 47 | runCatching { 48 | UserService.register(pms) 49 | log(TAG, "UserService started") 50 | }.onFailure { 51 | log(TAG, "UserService failed to start", it) 52 | } 53 | } 54 | } 55 | } 56 | 57 | var activityManagerServiceSystemReadyHook: XC_MethodHook.Unhook? = null 58 | activityManagerServiceSystemReadyHook = findMethod("com.android.server.am.ActivityManagerService") { 59 | name == "systemReady" 60 | }.hookAfter { 61 | activityManagerServiceSystemReadyHook?.unhook() 62 | YAMFManager.activityManagerService = it.thisObject 63 | YAMFManager.systemReady() 64 | log(TAG, "system ready") 65 | } 66 | 67 | findMethod("com.android.server.am.ActivityManagerService") { 68 | name == "checkBroadcastFromSystem" 69 | }.hookBefore { 70 | val intent = it.args[0] as Intent 71 | if (intent.action == HookLauncher.ACTION_RECEIVE_LAUNCHER_CONFIG) 72 | it.result = Unit // bypass check 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/xposed/services/UserService.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed.services 2 | 3 | import android.app.ActivityManagerHidden 4 | import android.content.AttributionSource 5 | import android.content.pm.IPackageManager 6 | import android.os.Build 7 | import android.os.Bundle 8 | import android.os.ServiceManager 9 | import io.github.duzhaokun123.yamf.BuildConfig 10 | import io.github.duzhaokun123.yamf.xposed.utils.log 11 | import rikka.hidden.compat.ActivityManagerApis 12 | import rikka.hidden.compat.adapter.UidObserverAdapter 13 | 14 | object UserService { 15 | const val TAG = "YAMFUserService" 16 | const val PROVIDER_AUTHORITY = "io.github.duzhaokun123.yamf.ServiceProvider" 17 | 18 | private var appUid = -1 19 | 20 | private val uidObserver = object : UidObserverAdapter() { 21 | override fun onUidActive(uid: Int) { 22 | if (uid != appUid) return 23 | try { 24 | val provider = ActivityManagerApis.getContentProviderExternal( 25 | PROVIDER_AUTHORITY, 26 | 0, 27 | null, 28 | null 29 | ) 30 | if (provider == null) { 31 | log(TAG, "Failed to get content provider") 32 | return 33 | } 34 | val extras = Bundle() 35 | extras.putBinder("binder", YAMFManager) 36 | val attr = AttributionSource.Builder(1000).setPackageName("android").build() 37 | val reply = provider.call(attr, PROVIDER_AUTHORITY, "", null, extras) 38 | if (reply == null) { 39 | log(TAG, "Failed to send binder to app") 40 | return 41 | } 42 | log(TAG, "Send binder to app") 43 | } catch (e: Throwable) { 44 | log(TAG, "Failed to send binder to app", e) 45 | } 46 | } 47 | } 48 | 49 | fun register(pms: IPackageManager) { 50 | log(TAG, "Init YAMFService") 51 | appUid = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 52 | pms.getPackageUid(BuildConfig.APPLICATION_ID, 0L, 0); 53 | } else { 54 | pms.getPackageUid(BuildConfig.APPLICATION_ID, 0, 0); 55 | } 56 | log(TAG, "App uid: $appUid") 57 | log(TAG, "Register uid observer") 58 | 59 | waitSystemService("activity") 60 | ActivityManagerApis.registerUidObserver( 61 | uidObserver, 62 | ActivityManagerHidden.UID_OBSERVER_ACTIVE, 63 | ActivityManagerHidden.PROCESS_STATE_UNKNOWN, 64 | null 65 | ) 66 | } 67 | 68 | private fun waitSystemService(name: String) { 69 | while (ServiceManager.getService(name) == null) { 70 | Thread.sleep(1000) 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/xposed/services/YAMFManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed.services 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.BroadcastReceiver 5 | import android.content.ComponentName 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.os.Process 9 | import android.os.SystemClock 10 | import android.util.Log 11 | import android.view.InputDevice 12 | import android.view.KeyEvent 13 | import com.github.kyuubiran.ezxhelper.utils.argTypes 14 | import com.github.kyuubiran.ezxhelper.utils.args 15 | import com.github.kyuubiran.ezxhelper.utils.invokeMethod 16 | import io.github.duzhaokun123.yamf.BuildConfig 17 | import io.github.duzhaokun123.yamf.common.gson 18 | import io.github.duzhaokun123.yamf.common.model.Config 19 | import io.github.duzhaokun123.yamf.common.model.StartCmd 20 | import io.github.duzhaokun123.yamf.common.runMain 21 | import io.github.duzhaokun123.yamf.xposed.IOpenCountListener 22 | import io.github.duzhaokun123.yamf.xposed.IYAMFManager 23 | import io.github.duzhaokun123.yamf.xposed.hook.HookLauncher 24 | import io.github.duzhaokun123.yamf.xposed.ui.window.AppListWindow 25 | import io.github.duzhaokun123.yamf.xposed.ui.window.AppWindow 26 | import io.github.duzhaokun123.yamf.xposed.utils.Instances 27 | import io.github.duzhaokun123.yamf.xposed.utils.Instances.systemContext 28 | import io.github.duzhaokun123.yamf.xposed.utils.Instances.systemUiContext 29 | import io.github.duzhaokun123.yamf.xposed.utils.createContext 30 | import io.github.duzhaokun123.yamf.xposed.utils.getTopRootTask 31 | import io.github.duzhaokun123.yamf.xposed.utils.log 32 | import io.github.duzhaokun123.yamf.xposed.utils.registerReceiver 33 | import io.github.duzhaokun123.yamf.xposed.utils.startAuto 34 | import io.github.qauxv.ui.CommonContextWrapper 35 | import rikka.hidden.compat.ActivityManagerApis 36 | import java.io.File 37 | 38 | 39 | object YAMFManager : IYAMFManager.Stub() { 40 | const val TAG = "YAMFManager" 41 | 42 | const val ACTION_GET_LAUNCHER_CONFIG = "io.github.duzhaokun123.yamf.ACTION_GET_LAUNCHER_CONFIG" 43 | const val ACTION_OPEN_APP = "io.github.duzhaokun123.yamf.action.OPEN_APP" 44 | const val ACTION_CURRENT_TO_WINDOW = "io.github.duzhaokun123.yamf.action.CURRENT_TO_WINDOW" 45 | const val ACTION_OPEN_APP_LIST = "io.github.duzhaokun123.yamf.action.OPEN_APP_LIST" 46 | const val ACTION_OPEN_IN_YAMF = "io.github.duzhaokun123.yamf.action.ACTION_OPEN_IN_YAMF" 47 | 48 | const val EXTRA_COMPONENT_NAME = "componentName" 49 | const val EXTRA_USER_ID = "userId" 50 | const val EXTRA_TASK_ID = "taskId" 51 | const val EXTRA_SOURCE = "source" 52 | 53 | const val SOURCE_UNSPECIFIED = 0 54 | const val SOURCE_RECENTS = 1 55 | const val SOURCE_TASKBAR = 2 56 | const val SOURCE_POPUP = 3 57 | 58 | val windowList = mutableListOf() 59 | lateinit var config: Config 60 | val configFile = File("/data/system/yamf.json") 61 | var openWindowCount = 0 62 | val iOpenCountListenerSet = mutableSetOf() 63 | lateinit var activityManagerService: Any 64 | 65 | @SuppressLint("UnspecifiedRegisterReceiverFlag") 66 | fun systemReady() { 67 | Instances.init(activityManagerService) 68 | systemContext.registerReceiver(ACTION_OPEN_IN_YAMF, OpenInYAMFBroadcastReceiver) 69 | systemContext.registerReceiver(ACTION_CURRENT_TO_WINDOW) { _, _ -> 70 | currentToWindow() 71 | } 72 | systemContext.registerReceiver(ACTION_OPEN_APP_LIST) { _, _ -> 73 | AppListWindow( 74 | CommonContextWrapper.createAppCompatContext(systemUiContext.createContext()), 75 | null 76 | ) 77 | } 78 | systemContext.registerReceiver(ACTION_OPEN_APP) { _, intent -> 79 | val componentName = intent.getParcelableExtra(EXTRA_COMPONENT_NAME) 80 | ?: return@registerReceiver 81 | val userId = intent.getIntExtra(EXTRA_USER_ID, 0) 82 | createWindow(StartCmd(componentName = componentName, userId = userId)) 83 | } 84 | systemContext.registerReceiver(ACTION_GET_LAUNCHER_CONFIG) { _, intent -> 85 | ActivityManagerApis.broadcastIntent(Intent(HookLauncher.ACTION_RECEIVE_LAUNCHER_CONFIG).apply { 86 | log(TAG, "send config: ${config.hookLauncher}") 87 | putExtra(HookLauncher.EXTRA_HOOK_RECENTS, config.hookLauncher.hookRecents) 88 | putExtra(HookLauncher.EXTRA_HOOK_TASKBAR, config.hookLauncher.hookTaskbar) 89 | putExtra(HookLauncher.EXTRA_HOOK_POPUP, config.hookLauncher.hookPopup) 90 | putExtra(HookLauncher.EXTRA_HOOK_TRANSIENT_TASKBAR, config.hookLauncher.hookTransientTaskbar) 91 | `package` = intent.getStringExtra("sender") 92 | }, 0) 93 | } 94 | configFile.createNewFile() 95 | config = runCatching { 96 | gson.fromJson(configFile.readText(), Config::class.java) 97 | }.getOrNull() ?: Config() 98 | log(TAG, "config: $config") 99 | } 100 | 101 | fun addWindow(id: Int) { 102 | windowList.add(0, id) 103 | openWindowCount++ 104 | val toRemove = mutableSetOf() 105 | iOpenCountListenerSet.forEach { 106 | runCatching { 107 | it.onUpdate(openWindowCount) 108 | }.onFailure { _ -> 109 | toRemove.add(it) 110 | } 111 | } 112 | iOpenCountListenerSet.removeAll(toRemove) 113 | } 114 | 115 | fun removeWindow(id: Int) { 116 | windowList.remove(id) 117 | } 118 | 119 | fun isTop(id: Int) = windowList[0] == id 120 | 121 | fun moveToTop(id: Int) { 122 | windowList.remove(id) 123 | windowList.add(0, id) 124 | } 125 | 126 | fun createWindow(startCmd: StartCmd?) { 127 | Instances.iStatusBarService.collapsePanels() 128 | AppWindow( 129 | CommonContextWrapper.createAppCompatContext(systemUiContext.createContext()), 130 | config.densityDpi, 131 | config.flags 132 | ) { displayId -> 133 | addWindow(displayId) 134 | startCmd?.startAuto(displayId) 135 | } 136 | } 137 | 138 | init { 139 | log(TAG, "YAMF service initialized") 140 | } 141 | 142 | override fun getVersionName(): String { 143 | return BuildConfig.VERSION_NAME 144 | } 145 | 146 | override fun getVersionCode(): Int { 147 | return BuildConfig.VERSION_CODE 148 | } 149 | 150 | override fun getUid(): Int { 151 | return Process.myUid() 152 | } 153 | 154 | override fun createWindow() { 155 | runMain { 156 | createWindow(null) 157 | } 158 | } 159 | 160 | override fun getBuildTime(): Long { 161 | return BuildConfig.BUILD_TIME 162 | } 163 | 164 | override fun getConfigJson(): String { 165 | return gson.toJson(config) 166 | } 167 | 168 | override fun updateConfig(newConfig: String) { 169 | config = gson.fromJson(newConfig, Config::class.java) 170 | runMain { 171 | configFile.writeText(newConfig) 172 | Log.d(TAG, "updateConfig: $config") 173 | } 174 | } 175 | 176 | override fun registerOpenCountListener(iOpenCountListener: IOpenCountListener) { 177 | iOpenCountListenerSet.add(iOpenCountListener) 178 | iOpenCountListener.onUpdate(openWindowCount) 179 | } 180 | 181 | override fun unregisterOpenCountListener(iOpenCountListener: IOpenCountListener?) { 182 | iOpenCountListenerSet.remove(iOpenCountListener) 183 | } 184 | 185 | override fun openAppList() { 186 | runMain { 187 | Instances.iStatusBarService.collapsePanels() 188 | AppListWindow( 189 | CommonContextWrapper.createAppCompatContext(systemUiContext.createContext()), 190 | null 191 | ) 192 | } 193 | } 194 | 195 | override fun currentToWindow() { 196 | runMain { 197 | val task = getTopRootTask(0) ?: return@runMain 198 | createWindow(StartCmd(taskId = task.taskId)) 199 | } 200 | } 201 | 202 | override fun resetAllWindow() { 203 | runMain { 204 | Instances.iStatusBarService.collapsePanels() 205 | systemContext.sendBroadcast(Intent(AppWindow.ACTION_RESET_ALL_WINDOW)) 206 | } 207 | } 208 | 209 | private val OpenInYAMFBroadcastReceiver: BroadcastReceiver.(Context, Intent) -> Unit = 210 | { _: Context, intent: Intent -> 211 | val taskId = intent.getIntExtra(EXTRA_TASK_ID, 0) 212 | val componentName = intent.getParcelableExtra(EXTRA_COMPONENT_NAME) 213 | val userId = intent.getIntExtra(EXTRA_USER_ID, 0) 214 | val source = intent.getIntExtra(EXTRA_SOURCE, SOURCE_UNSPECIFIED) 215 | createWindow(StartCmd(componentName, userId, taskId)) 216 | 217 | // TODO: better way to close recents 218 | if (source == SOURCE_RECENTS && config.recentsBackHome) { 219 | val down = KeyEvent( 220 | SystemClock.uptimeMillis(), 221 | SystemClock.uptimeMillis(), 222 | KeyEvent.ACTION_DOWN, 223 | KeyEvent.KEYCODE_HOME, 224 | 0 225 | ).apply { 226 | this.source = InputDevice.SOURCE_KEYBOARD 227 | this.invokeMethod("setDisplayId", args(0), argTypes(Integer.TYPE)) 228 | } 229 | Instances.inputManager.injectInputEvent(down, 0) 230 | val up = KeyEvent( 231 | SystemClock.uptimeMillis(), 232 | SystemClock.uptimeMillis(), 233 | KeyEvent.ACTION_UP, 234 | KeyEvent.KEYCODE_HOME, 235 | 0 236 | ).apply { 237 | this.source = InputDevice.SOURCE_KEYBOARD 238 | this.invokeMethod("setDisplayId", args(0), argTypes(Integer.TYPE)) 239 | } 240 | Instances.inputManager.injectInputEvent(up, 0) 241 | } 242 | } 243 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/xposed/ui/window/AppListWindow.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed.ui.window 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.ComponentName 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.content.pm.ActivityInfo 8 | import android.content.pm.IPackageManagerHidden 9 | import android.content.pm.PackageManagerHidden 10 | import android.content.pm.UserInfo 11 | import android.graphics.PixelFormat 12 | import android.util.Log 13 | import android.view.Gravity 14 | import android.view.LayoutInflater 15 | import android.view.View 16 | import android.view.WindowManager 17 | import androidx.appcompat.widget.PopupMenu 18 | import androidx.core.widget.doOnTextChanged 19 | import com.github.kyuubiran.ezxhelper.utils.argTypes 20 | import com.github.kyuubiran.ezxhelper.utils.args 21 | import com.github.kyuubiran.ezxhelper.utils.invokeMethodAs 22 | import com.google.android.flexbox.FlexDirection 23 | import com.google.android.flexbox.FlexboxLayoutManager 24 | import com.google.android.flexbox.JustifyContent 25 | import io.github.duzhaokun123.yamf.manager.bases.BaseSimpleAdapter 26 | import io.github.duzhaokun123.yamf.databinding.ItemAppBinding 27 | import io.github.duzhaokun123.yamf.databinding.WindowAppListBinding 28 | import io.github.duzhaokun123.yamf.common.model.StartCmd 29 | import io.github.duzhaokun123.yamf.common.onException 30 | import io.github.duzhaokun123.yamf.common.resetAdapter 31 | import io.github.duzhaokun123.yamf.common.runIO 32 | import io.github.duzhaokun123.yamf.common.runMain 33 | import io.github.duzhaokun123.yamf.xposed.utils.AppInfoCache 34 | import io.github.duzhaokun123.yamf.xposed.services.YAMFManager 35 | import io.github.duzhaokun123.yamf.xposed.utils.Instances 36 | import io.github.duzhaokun123.yamf.xposed.utils.TipUtil 37 | import io.github.duzhaokun123.yamf.xposed.utils.componentName 38 | import io.github.duzhaokun123.yamf.xposed.utils.getActivityInfoCompat 39 | import io.github.duzhaokun123.yamf.xposed.utils.startActivity 40 | 41 | @SuppressLint("ClickableViewAccessibility") 42 | class AppListWindow(val context: Context, val displayId: Int? = null) { 43 | companion object { 44 | const val TAG = "YAMF_AppListWindow" 45 | } 46 | 47 | private lateinit var binding: WindowAppListBinding 48 | val users = mutableMapOf() 49 | var userId = 0 50 | var apps = emptyList() 51 | var showApps = emptyList() 52 | 53 | init { 54 | runCatching { 55 | binding = WindowAppListBinding.inflate(LayoutInflater.from(context)) 56 | }.onException { e -> 57 | Log.e(TAG, "new app list failed: ", e) 58 | TipUtil.showToast("new app list failed\nmay you forget reboot") 59 | }.onSuccess { 60 | doInit() 61 | } 62 | } 63 | 64 | fun doInit() { 65 | val params = WindowManager.LayoutParams( 66 | WindowManager.LayoutParams.MATCH_PARENT, 67 | WindowManager.LayoutParams.MATCH_PARENT, 68 | WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, 69 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, 70 | PixelFormat.TRANSLUCENT 71 | ).apply { 72 | gravity = Gravity.START or Gravity.TOP 73 | x = 0 74 | y = 0 75 | } 76 | binding.root.let { layout -> 77 | Instances.windowManager.addView(layout, params) 78 | } 79 | binding.root.setOnClickListener { 80 | close() 81 | } 82 | binding.mcv.setOnTouchListener { _, _ -> true } 83 | Instances.userManager.invokeMethodAs>( 84 | "getUsers", 85 | args(true, true, true), 86 | argTypes(java.lang.Boolean.TYPE, java.lang.Boolean.TYPE, java.lang.Boolean.TYPE) 87 | )!! 88 | .filter { it.isProfile || it.isPrimary } 89 | .forEach { 90 | users[it.id] = it.name 91 | } 92 | binding.btnUser.setOnClickListener { 93 | PopupMenu(context, binding.btnUser).apply { 94 | users.forEach { (t, u) -> 95 | menu.add(u).setOnMenuItemClickListener { 96 | onSelectUser(t) 97 | true 98 | } 99 | } 100 | }.show() 101 | } 102 | onSelectUser(0) 103 | binding.rv.layoutManager = FlexboxLayoutManager(context).apply { 104 | flexDirection = FlexDirection.ROW 105 | justifyContent = JustifyContent.FLEX_START 106 | } 107 | binding.rv.adapter = Adapter() 108 | 109 | binding.etSearch.doOnTextChanged { text, _, _, _ -> 110 | text ?: return@doOnTextChanged 111 | showApps = apps.filter { activityInfo -> 112 | text in activityInfo.packageName || 113 | AppInfoCache.getIconLabel(activityInfo).second.contains(text, true) 114 | } 115 | binding.rv.resetAdapter() 116 | } 117 | } 118 | 119 | private fun onSelectUser(userId: Int) { 120 | binding.pv.visibility = View.VISIBLE 121 | this.userId = userId 122 | binding.btnUser.text = users[userId] 123 | 124 | runIO { 125 | apps = (Instances.packageManager as PackageManagerHidden).queryIntentActivitiesAsUser( 126 | Intent(Intent.ACTION_MAIN).apply { 127 | addCategory(Intent.CATEGORY_LAUNCHER) 128 | }, 0, userId 129 | ).map { 130 | (Instances.iPackageManager as IPackageManagerHidden).getActivityInfoCompat( 131 | ComponentName(it.activityInfo.packageName, it.activityInfo.name), 132 | 0, userId 133 | ) 134 | } 135 | apps.forEach { activityInfo -> 136 | AppInfoCache.getIconLabel(activityInfo) 137 | } 138 | runMain { 139 | showApps = apps 140 | binding.etSearch.text.clear() 141 | binding.rv.resetAdapter() 142 | binding.pv.visibility = View.GONE 143 | } 144 | } 145 | } 146 | 147 | private fun close() { 148 | Instances.windowManager.removeView(binding.root) 149 | } 150 | 151 | inner class Adapter : BaseSimpleAdapter(context, ItemAppBinding::class.java) { 152 | override fun initViews(baseBinding: ItemAppBinding, position: Int) { 153 | val activityInfo = showApps[position] 154 | baseBinding.ll.setOnClickListener { 155 | if (displayId == null) 156 | YAMFManager.createWindow(StartCmd(activityInfo.componentName, userId)) 157 | else 158 | startActivity(context, activityInfo.componentName, userId, displayId) 159 | close() 160 | } 161 | } 162 | 163 | override fun initData(baseBinding: ItemAppBinding, position: Int) { 164 | val activityInfo = showApps[position] 165 | val (icon, label) = AppInfoCache.getIconLabel(activityInfo) 166 | baseBinding.ivIcon.setImageDrawable(icon) 167 | baseBinding.tvLabel.text = label 168 | } 169 | 170 | override fun getItemCount() = showApps.size 171 | } 172 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/xposed/utils/AppInfoCache.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed.utils 2 | 3 | import android.content.ComponentName 4 | import android.content.pm.ActivityInfo 5 | import android.graphics.drawable.Drawable 6 | import androidx.wear.widget.RoundedDrawable 7 | 8 | object AppInfoCache { 9 | private val map = mutableMapOf>() 10 | 11 | fun getIconLabel(info: ActivityInfo): Pair { 12 | return map.getOrPut(info.componentName) { 13 | RoundedDrawable().apply { 14 | isClipEnabled = true 15 | radius = 100 16 | drawable = info.loadIcon(Instances.packageManager) 17 | } to info.loadLabel(Instances.packageManager) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/xposed/utils/Instances.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.ActivityManager 5 | import android.app.IActivityTaskManager 6 | import android.content.Context 7 | import android.content.pm.IPackageManager 8 | import android.content.pm.PackageManager 9 | import android.hardware.display.DisplayManager 10 | import android.hardware.input.IInputManager 11 | import android.os.ServiceManager 12 | import android.os.UserManager 13 | import android.view.IWindowManager 14 | import android.view.WindowManager 15 | import com.android.internal.statusbar.IStatusBarService 16 | import com.github.kyuubiran.ezxhelper.utils.getObjectAs 17 | 18 | @SuppressLint("StaticFieldLeak") 19 | object Instances { 20 | lateinit var windowManager: WindowManager 21 | private set 22 | lateinit var iWindowManager: IWindowManager 23 | private set 24 | lateinit var inputManager: IInputManager 25 | private set 26 | lateinit var displayManager: DisplayManager 27 | private set 28 | lateinit var activityTaskManager: IActivityTaskManager 29 | private set 30 | lateinit var packageManager: PackageManager 31 | private set 32 | lateinit var activityManager: ActivityManager 33 | private set 34 | lateinit var userManager: UserManager 35 | private set 36 | lateinit var iPackageManager: IPackageManager 37 | private set 38 | lateinit var iStatusBarService: IStatusBarService 39 | private set 40 | lateinit var activityManagerService: Any 41 | private set 42 | lateinit var systemContext: Context 43 | private set 44 | val systemUiContext: Context 45 | get() = activityManagerService.getObjectAs("mUiContext") 46 | 47 | 48 | fun init(activityManagerService: Any) { 49 | this.activityManagerService = activityManagerService 50 | systemContext = activityManagerService.getObjectAs("mContext") 51 | windowManager = systemContext.getSystemService(WindowManager::class.java) 52 | iWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window")) 53 | inputManager = IInputManager.Stub.asInterface(ServiceManager.getService("input")) 54 | displayManager = systemContext.getSystemService(DisplayManager::class.java) 55 | activityTaskManager = 56 | IActivityTaskManager.Stub.asInterface(ServiceManager.getService("activity_task")) 57 | packageManager = systemContext.packageManager 58 | activityManager = systemContext.getSystemService(ActivityManager::class.java) 59 | userManager = systemContext.getSystemService(UserManager::class.java) 60 | iPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package")) 61 | iStatusBarService = 62 | IStatusBarService.Stub.asInterface(ServiceManager.getService("statusbar")) 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/xposed/utils/RunMainThreadQueue.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed.utils 2 | 3 | import io.github.duzhaokun123.yamf.common.runMain 4 | import kotlinx.coroutines.CoroutineScope 5 | import java.util.LinkedList 6 | 7 | object RunMainThreadQueue { 8 | val queue = LinkedList Unit>() 9 | 10 | @Synchronized 11 | fun add(run: suspend CoroutineScope.() -> Unit) { 12 | queue.offer(run) 13 | if (queue.size == 1) { 14 | runMain { 15 | while (queue.isNotEmpty()) { 16 | queue.poll()?.invoke(this) 17 | } 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/xposed/utils/TipUtil.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.widget.Toast 5 | 6 | @SuppressLint("StaticFieldLeak") 7 | object TipUtil { 8 | fun showToast(msg: String) { 9 | Toast.makeText(Instances.systemContext, "[YAMF] $msg", Toast.LENGTH_LONG).show() 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/duzhaokun123/yamf/xposed/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package io.github.duzhaokun123.yamf.xposed.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.ActivityOptions 5 | import android.app.ActivityTaskManager 6 | import android.content.BroadcastReceiver 7 | import android.content.ComponentName 8 | import android.content.Context 9 | import android.content.ContextParams 10 | import android.content.Intent 11 | import android.content.pm.ActivityInfo 12 | import android.content.pm.IPackageManagerHidden 13 | import android.content.res.Resources 14 | import android.os.Build 15 | import android.os.Bundle 16 | import android.os.UserHandle 17 | import android.util.TypedValue 18 | import com.github.kyuubiran.ezxhelper.utils.argTypes 19 | import com.github.kyuubiran.ezxhelper.utils.args 20 | import com.github.kyuubiran.ezxhelper.utils.invokeMethod 21 | import com.github.kyuubiran.ezxhelper.utils.newInstance 22 | import de.robv.android.xposed.XposedBridge 23 | import io.github.duzhaokun123.yamf.common.model.StartCmd 24 | import io.github.duzhaokun123.yamf.common.onException 25 | import io.github.duzhaokun123.yamf.xposed.services.YAMFManager 26 | 27 | fun log(tag: String, message: String) { 28 | XposedBridge.log("[$tag] $message") 29 | } 30 | 31 | fun log(tag: String, message: String, t: Throwable) { 32 | XposedBridge.log("[$tag] $message") 33 | XposedBridge.log(t) 34 | } 35 | 36 | @SuppressLint("MissingPermission") 37 | fun moveTask(taskId: Int, displayId: Int) { 38 | Instances.activityTaskManager.moveRootTaskToDisplay(taskId, displayId) 39 | Instances.activityManager.moveTaskToFront(taskId, 0) 40 | } 41 | 42 | fun Number.dpToPx() = 43 | TypedValue.applyDimension( 44 | TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics 45 | ) 46 | 47 | val emptyContextParams = ContextParams.Builder().build() 48 | 49 | fun Context.createContext() = createContext(emptyContextParams) 50 | 51 | fun startActivity(context: Context, componentName: ComponentName, userId: Int, displayId: Int) { 52 | context.invokeMethod( 53 | "startActivityAsUser", 54 | args( 55 | Intent().apply { 56 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 57 | component = componentName 58 | `package` = component!!.packageName 59 | action = Intent.ACTION_VIEW 60 | }, 61 | ActivityOptions.makeBasic().apply { 62 | launchDisplayId = displayId 63 | this.invokeMethod("setCallerDisplayId", args(displayId), argTypes(Integer.TYPE)) 64 | }.toBundle(), 65 | UserHandle::class.java.newInstance( 66 | args(userId), 67 | argTypes(Integer.TYPE) 68 | ) 69 | ), argTypes(Intent::class.java, Bundle::class.java, UserHandle::class.java) 70 | ) 71 | } 72 | 73 | fun moveToDisplay(context: Context, taskId: Int, componentName: ComponentName, userId: Int, displayId: Int) { 74 | when (YAMFManager.config.windowfy) { 75 | 0 -> { 76 | runCatching { 77 | moveTask(taskId, displayId) 78 | }.onException { 79 | TipUtil.showToast("can't move task $taskId") 80 | } 81 | } 82 | 1 -> { 83 | runCatching { 84 | startActivity(context, componentName, userId, displayId) 85 | }.onException { 86 | TipUtil.showToast("can't start activity $componentName") 87 | } 88 | } 89 | 2 -> { 90 | runCatching { 91 | moveTask(taskId, displayId) 92 | }.onException { 93 | TipUtil.showToast("can't move task $taskId") 94 | runCatching { 95 | startActivity(context, componentName, userId, displayId) 96 | }.onException { 97 | TipUtil.showToast("can't start activity $componentName") 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | fun StartCmd.startAuto(displayId: Int) { 105 | when { 106 | canStartActivity && canMoveTask -> 107 | moveToDisplay(Instances.systemContext, taskId!!, componentName!!, userId!!, displayId) 108 | canMoveTask -> { 109 | runCatching { 110 | moveTask(taskId!!, displayId) 111 | }.onException { 112 | TipUtil.showToast("can't move task $taskId") 113 | } 114 | } 115 | canStartActivity -> { 116 | runCatching { 117 | startActivity(Instances.systemContext, componentName!!, userId!!, displayId) 118 | }.onException { 119 | TipUtil.showToast("can't start activity $componentName") 120 | } 121 | } 122 | } 123 | } 124 | 125 | fun getTopRootTask(displayId: Int): ActivityTaskManager.RootTaskInfo? { 126 | Instances.activityTaskManager.getAllRootTaskInfosOnDisplay(displayId).forEach { task -> 127 | if (task.visible) 128 | return task 129 | } 130 | return null 131 | } 132 | 133 | fun Context.registerReceiver(action: String, onReceive: BroadcastReceiver.(Context, Intent) -> Unit) { 134 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 135 | registerReceiver(object : BroadcastReceiver() { 136 | override fun onReceive(context: Context, intent: Intent) { 137 | onReceive(this, context, intent) 138 | } 139 | }, android.content.IntentFilter(action), Context.RECEIVER_EXPORTED) 140 | } else { 141 | registerReceiver(object : BroadcastReceiver() { 142 | override fun onReceive(context: Context, intent: Intent) { 143 | onReceive(this, context, intent) 144 | } 145 | }, android.content.IntentFilter(action)) 146 | } 147 | } 148 | 149 | 150 | val ActivityInfo.componentName: ComponentName 151 | get() = ComponentName(packageName, name) 152 | 153 | fun IPackageManagerHidden.getActivityInfoCompat(className: ComponentName, flags: Int, userId: Int): ActivityInfo = 154 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 155 | getActivityInfo(className, flags.toLong(), userId) 156 | } else { 157 | getActivityInfo(className, flags, userId) 158 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/qauxv/ui/CommonContextWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * QAuxiliary - An Xposed module for QQ/TIM 3 | * Copyright (C) 2019-2022 qwq233@qwq2333.top 4 | * https://github.com/cinit/QAuxiliary 5 | * 6 | * This software is non-free but opensource software: you can redistribute it 7 | * and/or modify it under the terms of the GNU Affero General Public License 8 | * as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version and our eula as published 10 | * by QAuxiliary contributors. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * and eula along with this software. If not, see 19 | * 20 | * . 21 | */ 22 | 23 | package io.github.qauxv.ui; 24 | 25 | import android.annotation.SuppressLint; 26 | import android.content.Context; 27 | import android.content.res.Configuration; 28 | import android.content.res.Resources; 29 | import android.content.res.TypedArray; 30 | import android.view.ContextThemeWrapper; 31 | 32 | import androidx.annotation.NonNull; 33 | import androidx.annotation.Nullable; 34 | import androidx.appcompat.app.AppCompatActivity; 35 | 36 | import com.github.kyuubiran.ezxhelper.init.EzXHelperInit; 37 | import com.google.android.material.color.DynamicColors; 38 | 39 | import java.util.Objects; 40 | 41 | import io.github.duzhaokun123.yamf.R; 42 | import io.github.qauxv.util.SavedInstanceStatePatchedClassReferencer; 43 | 44 | /** 45 | * If you just want to create a MaterialDialog or AppCompatDialog, see {@link #createMaterialDesignContext(Context)} and 46 | * {@link #createAppCompatContext(Context)} 47 | **/ 48 | public class CommonContextWrapper extends ContextThemeWrapper { 49 | 50 | /** 51 | * Creates a new context wrapper with the specified theme with correct module ClassLoader. 52 | * 53 | * @param base the base context 54 | * @param theme the resource ID of the theme to be applied on top of the base context's theme 55 | */ 56 | public CommonContextWrapper(@NonNull Context base, int theme) { 57 | this(base, theme, null); 58 | } 59 | 60 | /** 61 | * Creates a new context wrapper with the specified theme with correct module ClassLoader. 62 | * 63 | * @param base the base context 64 | * @param theme the resource ID of the theme to be applied on top of the base context's theme 65 | * @param configuration the configuration to override the base one 66 | */ 67 | public CommonContextWrapper(@NonNull Context base, int theme, 68 | @Nullable Configuration configuration) { 69 | super(base, theme); 70 | if (configuration != null) { 71 | mOverrideResources = base.createConfigurationContext(configuration).getResources(); 72 | } 73 | EzXHelperInit.INSTANCE.addModuleAssetPath(getResources()); 74 | } 75 | 76 | private ClassLoader mXref = null; 77 | private Resources mOverrideResources; 78 | 79 | @NonNull 80 | @Override 81 | public ClassLoader getClassLoader() { 82 | if (mXref == null) { 83 | mXref = new SavedInstanceStatePatchedClassReferencer( 84 | CommonContextWrapper.class.getClassLoader()); 85 | } 86 | return mXref; 87 | } 88 | 89 | @Nullable 90 | private static Configuration recreateNighModeConfig(@NonNull Context base, int uiNightMode) { 91 | Objects.requireNonNull(base, "base is null"); 92 | Configuration baseConfig = base.getResources().getConfiguration(); 93 | if ((baseConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) == uiNightMode) { 94 | // config for base context is already what we want, 95 | // just return null to avoid unnecessary override 96 | return null; 97 | } 98 | Configuration conf = new Configuration(); 99 | conf.uiMode = uiNightMode | (baseConfig.uiMode & ~Configuration.UI_MODE_NIGHT_MASK); 100 | return conf; 101 | } 102 | 103 | @NonNull 104 | @Override 105 | public Resources getResources() { 106 | if (mOverrideResources == null) { 107 | return super.getResources(); 108 | } else { 109 | return mOverrideResources; 110 | } 111 | } 112 | 113 | public static boolean isAppCompatContext(@NonNull Context context) { 114 | if (!checkContextClassLoader(context)) { 115 | return false; 116 | } 117 | TypedArray a = context.obtainStyledAttributes(androidx.appcompat.R.styleable.AppCompatTheme); 118 | try { 119 | return a.hasValue(androidx.appcompat.R.styleable.AppCompatTheme_windowActionBar); 120 | } finally { 121 | a.recycle(); 122 | } 123 | } 124 | 125 | private static final int[] MATERIAL_CHECK_ATTRS = {com.google.android.material.R.attr.colorPrimaryVariant}; 126 | 127 | public static boolean isMaterialDesignContext(@NonNull Context context) { 128 | if (!isAppCompatContext(context)) { 129 | return false; 130 | } 131 | @SuppressLint("ResourceType") TypedArray a = context.obtainStyledAttributes(MATERIAL_CHECK_ATTRS); 132 | try { 133 | return a.hasValue(0); 134 | } finally { 135 | a.recycle(); 136 | } 137 | } 138 | 139 | public static boolean checkContextClassLoader(@NonNull Context context) { 140 | try { 141 | ClassLoader cl = context.getClassLoader(); 142 | if (cl == null) { 143 | return false; 144 | } 145 | return cl.loadClass(AppCompatActivity.class.getName()) == AppCompatActivity.class; 146 | } catch (ClassNotFoundException e) { 147 | return false; 148 | } 149 | } 150 | 151 | @NonNull 152 | public static Context createAppCompatContext(@NonNull Context base) { 153 | if (isAppCompatContext(base)) { 154 | return base; 155 | } 156 | return new CommonContextWrapper(base, R.style.Theme_YAMF_Window, 157 | recreateNighModeConfig(base, base.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)); 158 | } 159 | 160 | @NonNull 161 | public static Context createMaterialDesignContext(@NonNull Context base) { 162 | if (isMaterialDesignContext(base)) { 163 | return base; 164 | } 165 | // currently all themes by createAppCompatContext are material themes 166 | // change this if you have a AppCompat theme that is not material theme 167 | return createAppCompatContext(base); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/qauxv/util/Initiator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * QAuxiliary - An Xposed module for QQ/TIM 3 | * Copyright (C) 2019-2022 qwq233@qwq2333.top 4 | * https://github.com/cinit/QAuxiliary 5 | * 6 | * This software is non-free but opensource software: you can redistribute it 7 | * and/or modify it under the terms of the GNU Affero General Public License 8 | * as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version and our eula as published 10 | * by QAuxiliary contributors. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * and eula along with this software. If not, see 19 | * 20 | * . 21 | */ 22 | package io.github.qauxv.util; 23 | 24 | public class Initiator { 25 | private static ClassLoader sHostClassLoader; 26 | 27 | private Initiator() { 28 | throw new AssertionError("No instance for you!"); 29 | } 30 | 31 | public static void init(ClassLoader classLoader) { 32 | sHostClassLoader = classLoader; 33 | } 34 | 35 | public static ClassLoader getPluginClassLoader() { 36 | return Initiator.class.getClassLoader(); 37 | } 38 | 39 | public static ClassLoader getHostClassLoader() { 40 | return sHostClassLoader; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/qauxv/util/SavedInstanceStatePatchedClassReferencer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * QAuxiliary - An Xposed module for QQ/TIM 3 | * Copyright (C) 2019-2022 qwq233@qwq2333.top 4 | * https://github.com/cinit/QAuxiliary 5 | * 6 | * This software is non-free but opensource software: you can redistribute it 7 | * and/or modify it under the terms of the GNU Affero General Public License 8 | * as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version and our eula as published 10 | * by QAuxiliary contributors. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * and eula along with this software. If not, see 19 | * 20 | * . 21 | */ 22 | 23 | package io.github.qauxv.util; 24 | 25 | import android.content.Context; 26 | import java.util.Objects; 27 | 28 | public class SavedInstanceStatePatchedClassReferencer extends ClassLoader { 29 | 30 | private static final ClassLoader mBootstrap = Context.class.getClassLoader(); 31 | private final ClassLoader mBaseReferencer; 32 | private final ClassLoader mHostReferencer; 33 | 34 | public SavedInstanceStatePatchedClassReferencer(ClassLoader referencer) { 35 | super(mBootstrap); 36 | mBaseReferencer = Objects.requireNonNull(referencer); 37 | mHostReferencer = Initiator.getHostClassLoader(); 38 | } 39 | 40 | @Override 41 | protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 42 | try { 43 | return mBootstrap.loadClass(name); 44 | } catch (ClassNotFoundException ignored) {} 45 | if (mHostReferencer != null) { 46 | try { 47 | //start: overloaded 48 | if ("androidx.lifecycle.ReportFragment".equals(name)) { 49 | return mHostReferencer.loadClass(name); 50 | } 51 | } catch (ClassNotFoundException ignored) {} 52 | } 53 | //with ClassNotFoundException 54 | return mBaseReferencer.loadClass(name); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/a_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_android_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fullscreen_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_keyboard.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_open_in_full_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_picture_in_picture_alt_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_check_circle_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_screen_rotation_alt_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_security_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_warning_amber_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_base_root_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 17 | 18 | 22 | 23 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 29 | 30 | 34 | 35 | 43 | 44 | 55 | 56 | 67 | 68 | 69 | 70 | 71 | 76 | 77 | 82 | 83 | 88 | 89 | 95 | 96 | 101 | 102 | 107 | 108 | 113 | 114 | 119 | 120 | 121 | 122 | 123 | 124 | 128 | 129 | 130 | 131 | 135 | 136 | 142 | 143 | 149 | 150 | 156 | 157 | 163 | 164 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 14 | 15 | 19 | 20 | 25 | 26 | 30 | 31 | 37 | 38 | 42 | 43 | 49 | 50 | 51 | 55 | 56 | 60 | 61 | 67 | 68 | 69 | 74 | 75 | 79 | 80 | 84 | 85 | 90 | 91 | 92 | 96 | 97 | 101 | 102 | 107 | 108 | 109 | 114 | 115 | 120 | 121 | 126 | 127 | 132 | 133 | 138 | 139 | 144 | 145 | 149 | 150 | 155 | 156 | 161 | 162 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_app.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/window_app.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 51 | 52 | 62 | 63 | 73 | 74 | 84 | 85 | 93 | 94 | 95 | 100 | 101 | 112 | 113 | 124 | 125 | 136 | 137 | 146 | 147 | 148 | 149 | 150 | 158 | 159 | 166 | 167 | 168 | 169 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /app/src/main/res/layout/window_app_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 21 | 22 | 27 | 28 | 33 | 34 | 39 | 40 | 41 | 47 | 48 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 14 | 18 | 22 | 26 | 31 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | YAMF 4 | 详情 5 | 未知错误 6 | 已激活 7 | 需要重启 8 | 未激活 9 | 已有的窗口不会出现问题\n但新建窗口可能导致系统崩溃 10 | 重启 11 | 设置 12 | 开窗计数 13 | 开窗 14 | 打开应用列表 15 | Telegram 频道 16 | 捐赠 17 | 点击: 返回\n长按: 主页 18 | 点击: 转屏\n长按: 锁定/解锁 自动转屏 19 | 点击: 全屏\n长按: 挂起, 双击恢复 20 | 点击: 关闭\n长按: 应用列表 21 | 为类原生系统设计的小窗 22 | 系统版本 23 | 使用 YAMF 打开 24 | 进窗 25 | 构建类型 26 | 重置所有窗口 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | android 5 | com.motorola.launcher3 6 | com.android.launcher3 7 | com.google.android.apps.nexuslauncher 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FBC02D 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | YAMF 3 | 4 | details 5 | unknown error 6 | activated 7 | need reboot 8 | not activated 9 | Existing windows are fine\nBut new window may make system crash 10 | reboot 11 | settings 12 | open count 13 | new window 14 | open app list 15 | Telegram channel 16 | donate 17 | click: back\nlong click: home 18 | click: rotate\nlong click: lock/unlock auto rotaote 19 | click: fullscreen\nlong click: hang up, double click to resume 20 | click: close\nlong click: app list 21 | free window for AOSP-like systems 22 | System version 23 | Open with YAMF 24 | enter window 25 | build type 26 | reset all window 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 17 | 22 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("com.android.tools.build:gradle:8.1.4") 9 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0") 10 | classpath(kotlin("gradle-plugin")) 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | maven("https://jitpack.io") 22 | maven("https://api.xposed.info") 23 | } 24 | } 25 | 26 | tasks.register("clean", Delete::class) { 27 | delete(rootProject.buildDir) 28 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | android.defaults.buildfeatures.buildconfig=true 25 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duzhaokun123/YAMF/838bca5802f82a45d60400b415a24461ebde0e21/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /releaseKey.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duzhaokun123/YAMF/838bca5802f82a45d60400b415a24461ebde0e21/releaseKey.jks -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | include(":app", ":android-stub") 8 | rootProject.name = "YAMF" 9 | --------------------------------------------------------------------------------