├── .gitignore ├── Functions-log.md ├── LICENSE ├── LOG-CN.md ├── LOG-EN.md ├── README-CN.md ├── README.md ├── app ├── .gitignore ├── and-res-guard.gradle ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── xposed_init │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── tianma │ │ └── tweaks │ │ └── miui │ │ ├── app │ │ ├── App.kt │ │ ├── MainActivity.kt │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ └── BasePreferenceFragment.kt │ │ ├── fragment │ │ │ ├── BaseSettingsFragment.kt │ │ │ ├── DropDownStatusBarSettingsFragment.kt │ │ │ ├── GeneralSettingsFragment.kt │ │ │ ├── KeyguardSettingsFragment.kt │ │ │ ├── SettingsFragmentPagerAdapter.kt │ │ │ └── StatusBarSettingsFragment.kt │ │ └── widget │ │ │ ├── dialog │ │ │ └── OneSentenceSettingsDialogWrapper.kt │ │ │ └── tag │ │ │ ├── ItemClickCallback.kt │ │ │ ├── TagAdapter.kt │ │ │ └── TagBean.java │ │ ├── cons │ │ ├── AppConst.kt │ │ └── PrefConst.kt │ │ ├── data │ │ ├── http │ │ │ ├── APIConst.kt │ │ │ ├── entity │ │ │ │ ├── Hitokoto.kt │ │ │ │ └── Poem.kt │ │ │ ├── repository │ │ │ │ └── DataRepository.kt │ │ │ └── service │ │ │ │ ├── HitokotoService.kt │ │ │ │ ├── PoemService.kt │ │ │ │ └── ServiceGenerator.kt │ │ └── sp │ │ │ ├── PreferenceContainer.kt │ │ │ └── XPrefContainer.kt │ │ ├── utils │ │ ├── ContextUtils.kt │ │ ├── DebugHelper.kt │ │ ├── ModuleUtils.kt │ │ ├── PackageUtils.kt │ │ ├── PreferencesUtils.kt │ │ ├── ReflectionExt.kt │ │ ├── ReflectionUtils.java │ │ ├── ResolutionUtils.kt │ │ ├── RootUtils.kt │ │ ├── SPUtils.kt │ │ ├── StorageUtils.kt │ │ ├── Utils.kt │ │ ├── XLog.kt │ │ ├── XSPUtils.java │ │ ├── prefs │ │ │ ├── PreferenceDelegate.kt │ │ │ └── XPreferenceDelegate.kt │ │ └── rom │ │ │ ├── MiuiUtils.java │ │ │ ├── MiuiVersion.java │ │ │ └── RomUtils.java │ │ └── xp │ │ ├── HookEntry.kt │ │ ├── hook │ │ ├── BaseHook.kt │ │ ├── BaseSubHook.kt │ │ ├── IHook.kt │ │ ├── launcher │ │ │ ├── MiuiLauncherHook.kt │ │ │ └── WorkSpaceHook.kt │ │ ├── self │ │ │ └── ModuleUtilsHook.kt │ │ └── systemui │ │ │ ├── SystemUIHook.java │ │ │ ├── helper │ │ │ └── ResHelpers.java │ │ │ ├── hitokoto │ │ │ └── OneSentenceManager.kt │ │ │ ├── keyguard │ │ │ ├── def │ │ │ │ ├── Ease.java │ │ │ │ ├── KeyguardClockContainerHook.kt │ │ │ │ └── MiuiKeyguardClockHook.java │ │ │ ├── v20190507 │ │ │ │ ├── ChooseKeyguardClockActivityHook.kt │ │ │ │ ├── MiuiKeyguardBaseClockHook.java │ │ │ │ ├── MiuiKeyguardLeftTopClockHook.java │ │ │ │ └── MiuiKeyguardVerticalClockHook.java │ │ │ └── v20191213 │ │ │ │ ├── MiuiBaseClockHook.kt │ │ │ │ ├── MiuiCenterHorizontalClockHook.kt │ │ │ │ ├── MiuiLeftTopClockHook.kt │ │ │ │ ├── MiuiLeftTopLargeClockHook.kt │ │ │ │ └── MiuiVerticalClockHook.kt │ │ │ ├── screen │ │ │ ├── IntentAction.kt │ │ │ ├── ScreenBroadcastManager.kt │ │ │ ├── ScreenListener.kt │ │ │ └── SimpleScreenListener.kt │ │ │ ├── statusbar │ │ │ ├── def │ │ │ │ ├── BatteryMeterViewHook.java │ │ │ │ ├── CollapsedStatusBarFragmentHook.java │ │ │ │ ├── HeaderViewHook.java │ │ │ │ ├── PhoneStatusBarViewHook.java │ │ │ │ ├── SignalClusterViewHook.java │ │ │ │ └── StatusBarClockHook.java │ │ │ └── v20201109 │ │ │ │ ├── CollapsedStatusBarFragmentHook20201109.kt │ │ │ │ ├── MiuiQSHeaderViewHook20201109.kt │ │ │ │ ├── StatusBarClockHook20201109.java │ │ │ │ ├── StatusBarMobileViewHook20201109.kt │ │ │ │ └── StatusBarSignalPolicyHook20201109.kt │ │ │ ├── tick │ │ │ ├── TickObserver.java │ │ │ └── TimeTicker.java │ │ │ └── weather │ │ │ ├── WeatherMonitor.java │ │ │ └── WeatherObserver.java │ │ ├── utils │ │ └── appinfo │ │ │ ├── AppInfo.kt │ │ │ ├── AppInfoHelper.kt │ │ │ └── AppVersionConst.java │ │ └── wrapper │ │ ├── MethodHookWrapper.java │ │ └── XposedWrapper.java │ └── res │ ├── color │ └── tag_view_text_color_selector.xml │ ├── drawable │ └── tag_view_bg.xml │ ├── layout │ ├── activity_main.xml │ ├── dialog_hitokoto_settings.xml │ ├── preference_category.xml │ ├── tag_view.xml │ └── toolbar.xml │ ├── menu │ └── main_menu.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values-sw360dp-v13 │ └── values-preference.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values │ ├── attrs.xml │ ├── colors.xml │ ├── common_strings.xml │ ├── dimens.xml │ ├── ic_launcher_background.xml │ ├── ids.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── dropdown_statusbar_settings.xml │ ├── keyguard_settings.xml │ ├── main_settings.xml │ └── statusbar_settings.xml ├── art ├── cn │ ├── 01.png │ └── 02.png └── en │ ├── 01.png │ └── 02.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── notes.md └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | #*.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | 67 | 68 | *.iml 69 | .gradle 70 | /local.properties 71 | /.idea/caches 72 | /.idea/libraries 73 | /.idea/modules.xml 74 | /.idea/workspace.xml 75 | /.idea/navEditor.xml 76 | /.idea/assetWizardSettings.xml 77 | /.idea/* 78 | .DS_Store 79 | /build 80 | /captures 81 | .externalNativeBuild 82 | 83 | # eclipse 84 | .settings/ 85 | *.project 86 | */.classpath 87 | 88 | # vscode 89 | .vscode/* 90 | -------------------------------------------------------------------------------- /Functions-log.md: -------------------------------------------------------------------------------- 1 | # MIUI 12 , 2020.4.30 for mix2s - SystemUI(versionCode=201912130) 2 | - 状态栏 3 | - 时间显秒: 1 4 | - 时间对齐方式: 1 5 | - 自定义时间颜色: 1 6 | - 自定义时间格式: 1 7 | - 始终显示状态栏时间: 1 8 | - 信号居左: 0 - 信号居左之后,锁屏状态栏信号与运营商重叠 9 | - 信号双层显示: 10 | - 隐藏vpn图标:1 11 | - 隐藏HD图标:1 12 | - 电量百分比符号变小:无需此功能 13 | - 自定义移动网络类型:1 14 | - 下拉状态栏 15 | - 时间显秒: 1 16 | - 自定义时间颜色:1 17 | - 自定义日期颜色: 1 18 | - 显示天气信息: 0 - 功能失效 19 | - 天气颜色: 20 | - 天气字体大小 21 | - 锁屏界面 22 | - 水平时钟显秒: 0 - 功能失效 - 已修复 23 | - 垂直时钟显秒: 0 - 功能失效 - 已修复 24 | - 锁屏一言: 0 - 功能失效 25 | 26 | # SystemUI(versionCode=202011090) - Miui(12.5 21.1.28) - Android 11 27 | - 状态栏 28 | - 时间显秒: 有效 29 | - 时间对齐方式: 失效 30 | - 自定义时间颜色: 有效 31 | - 自定义时间格式: 有效 32 | - 始终显示状态栏时间: 失效 33 | - 信号居左: 失效 34 | - 信号双层显示:失效 35 | - 隐藏vpn图标:有效 36 | - 隐藏HD图标:失效 37 | - 电量百分比符号变小:无需 38 | - 自定义移动网络类型:有效 39 | - 下拉状态栏 40 | - 时间显秒: 有效 41 | - 自定义时间颜色:有效 42 | - 自定义日期颜色: 有效 43 | - 显示天气信息: 有效 44 | - 天气颜色: 有效 45 | - 天气字体大小: 有效 46 | - 锁屏界面 47 | - 水平时钟显秒: 有效 48 | - 垂直时钟显秒: 有效 49 | - 锁屏一言: 有效 50 | -------------------------------------------------------------------------------- /LOG-CN.md: -------------------------------------------------------------------------------- 1 | # 更新日志 2 | - 21.11.02 1.4.0 3 | 1. 适配 MIUI 12.5 (21.10.13版本) 4 | 2. 修复: 天气显示问题 5 | 3. 修复: 始终显示状态栏时间问题 6 | - 21.03.04 1.3.0 7 | 1. 适配 MIUI 12.5+ 8 | - 20.05.11 1.2.3 9 | 1. 修复: 锁屏居中时钟样式,一言居中问题 10 | - 20.05.10 1.2.2 11 | 1. 适配 MIUI 12: 锁屏时钟显秒 12 | 2. 适配 MIUI 12: 锁屏一言 13 | - 20.04.19 1.2.1 14 | 1. 新增可选项:自定义一言文字大小 和 文字颜色 15 | 2. 修复:尝试修复信号居左失效问题 16 | - 20.04.18 1.2.0 17 | 1. 新增可选项:锁屏一言 18 | - 19.06.03 1.1.0 19 | 1. 新增可选项:隐藏 HD 图标 20 | 2. 新增可选项:自定义下拉天气字体颜色和大小 21 | 3. 新增可选项:显示小号的电量百分比符号 22 | - 19.05.31 1.0.9 23 | 1. 新增可选项:下拉显示天气 24 | 2. 修复:信号左移时位置偏上的问题 25 | - 19.05.27 1.0.8 26 | 1. 修复:9.5.7 之前信号居左失效问题 27 | 2. 修复:9.5.7 之前信号双层显示失效问题 28 | - 19.05.27 1.0.7 29 | 1. 添加QQ群反馈交流入口 30 | - 19.05.26 1.0.6 31 | 1. 修复:EdXposed重启后需要重启SystemUI的问题 32 | 2. 修复:切换锁屏时钟样式后,显秒失效问题 33 | 3. 新增可选项:信号居左 34 | 4. 新增可选项:信号双层显示 35 | 5. 新增可选项:隐藏状态栏VPN图标 36 | 6. 新增可选项:自定义状态栏显示的移动网络类型 37 | 7. 优化:状态栏时间常显实现方式 38 | - 19.05.15 1.0.5 39 | 1. 修复:部分条件下,分钟显示不准确的问题 40 | 2. 新增可选项:系统桌面始终显示状态栏时间(即便桌面上有时钟小部件) 41 | - 19.05.14 1.0.4 42 | 1. 适配 MIUI10 9.5.7 版本,包括锁屏显秒,状态栏显秒,时间居中等 43 | 2. 修复跳秒问题 44 | 3. 优化显秒机制,提高模块性能 45 | - 19.04.29 1.0.3 46 | 1. 新增:状态栏自定义时间颜色 47 | 2. 新增:下拉状态栏自定义时间颜色 48 | 3. 优化:优化显秒机制 49 | 3. 新增:支持英文 50 | - 19.04.22 1.0.2 51 | 1. Bug修复:锁屏秒数适配亮暗模式 52 | 2. 新增:自定义状态栏时间对齐方式 53 | 3. 新增:自定义状态栏时间格式 54 | 4. 新增: 提供一个主开关 55 | - 19.04.19 1.0.1 56 | 1. 优化:状态栏显秒性能优化 57 | 2. 修复:处理显秒时的秒数重复问题 58 | 3. 修复:横屏状态下下拉通知栏显秒问题 59 | 4. 新增:添加太极用户提示入口,添加支付宝捐赠入口 60 | - 19.04.17 1.0.0 61 | 1. 第一个版本 -------------------------------------------------------------------------------- /LOG-EN.md: -------------------------------------------------------------------------------- 1 | # Update Logs 2 | - 21.11.02 1.4.0 3 | 1. Adapt to MIUI 12.5 (21.10.13) 4 | 2. Fix: weather info showing issues. 5 | 3. Fix: always show status bar clock in status bar. 6 | - 21.03.04 1.3.0 7 | 1. Adapt MIUI 12.5+ 8 | - 20.05.11 1.2.3 9 | 1. Fix: the issue of Hitokoto alignment 10 | - 20.05.10 1.2.2 11 | 1. Adapt MIUI 12: Show seconds in lock-screen clock 12 | 2. Adapt MIUI 12: Hitokoto for lock-screen 13 | - 20.04.19 1.2.1 14 | 1. New option: custom hitokoto text size and color 15 | 2. Fix: signal align left invalid issue 16 | - 20.04.18 1.2.0 17 | 1. New option: Hitokoto on lock screen. 18 | - 19.06.03 1.1.0 19 | 1. New option: hide HD icon. 20 | 2. New option: custom weather info text color and size. 21 | 3. New option: show small battery percent sign. 22 | - 19.05.31 1.0.9 23 | 1. New option: show weather info in dropdown status bar. 24 | 2. Bug fixes: align top & left issue about mobile signal icons. 25 | - 19.05.27 1.0.8 26 | 1. Bug fixes: signal align left invalid issue before MIUI 10 9.5.7 27 | 2. Bug fixes: dual mobile signal invalid issue before MIUI 10 9.5.7 28 | - 19.05.27 1.0.7 29 | 1. New entry: join QQ group for communication and bug report. 30 | - 19.05.26 1.0.6 31 | 1. Bug fixes: invalid issue after rebooting. 32 | 2. Bug fixes: showing seconds invalid issue after choosing an new clock style in lock screen. 33 | 3. New option: align signal cluster to the left of status bar. 34 | 4. New option: show dual mobile signal in status bar. 35 | 5. New option: hide VPN icon in status bar. 36 | 6. New option: custom displayed mobile network type. 37 | 7. Optimization: no longer hook MIUI Launcher. 38 | - 19.05.15 1.0.5 39 | 1. Bug fixes: wrong minute display issue 40 | 2. New option: always show status bar clock in MIUI System Launcher 41 | - 19.05.14 1.0.4 42 | 1. Adapt to MIUI 9.5.7+. 43 | 2. Fix the leap seconds issue. 44 | 3. Optimize the mechanism of showing seconds. Improve the performance. 45 | - 19.04.29 1.0.3 46 | 1. New option: custom status bar clock color 47 | 2. New option: custom dropdown status bar clock color 48 | 3. Optimization: optimize the mechanism of showing seconds 49 | 4. New: support English 50 | - 19.04.22 1.0.2 51 | 1. Bug fixes: seconds view adapt to dark mode issue 52 | 2. New: show status bar clock by its alignment. 53 | 3. New: custom statuc bar clock time format. 54 | 4. New: provide an main switch. 55 | - 19.04.19 1.0.1 56 | 1. Optimization: improve the performance of showing seconds. 57 | 2. Bug fixes: duplicated seonds showing issue 58 | 3. Bug fixes: show seconds on notification panel on horizontal screen. 59 | 4. Add: the entry for alipay donation and taichi users notice. 60 | - 19.04.17 1.0.0 61 | 1. First version -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # XMiTools 2 | MIUI 10 / MIUI 11 / MIUI 12 / MIUI 12.5 系统界面模块 3 | 4 | [English README](/README-EN.md) 5 | # 效果截图 6 | 7 | 8 | # 下载 9 | 可在以下地方下载: 10 | - [releases](/releases) 11 | - [酷安](https://www.coolapk.com/apk/com.tianma.tweaks.miui) 12 | - [Xposed仓库](https://repo.xposed.info/module/com.tianma.tweaks.miui) 13 | 14 | # 注意 15 | - 仅适用于MIUI,其他版本的系统,请慎用。 16 | - 支持 Xposed,EdXposed,LSPosed 和 太极阳。 17 | 18 | # 功能 19 | - 状态栏 20 | 1. 状态栏显示秒数 21 | 2. 状态栏时间对齐方式(居左,居中,居右) 22 | 3. 自定义时间格式 23 | 4. 自定义时间颜色 24 | 5. 信号居左 25 | 6. 信号双层显示 26 | 7. 自定义显示的移动网络类型 27 | - 下拉状态栏 28 | 1. 时间显示秒数 29 | 2. 自定义时间,日期颜色 30 | 3. 显示天气信息 31 | - 锁屏界面 32 | 1. 水平时钟显示秒数 33 | 2. 垂直时钟显示秒数 34 | - 等等... 35 | 36 | # 感谢 37 | - [custoMIUIzer](https://code.highspec.ru/Mikanoshi/CustoMIUIzer/) 38 | - [GravityBox](https://github.com/GravityBox/GravityBox) 39 | - [Xposed](https://github.com/rovo89/Xposed) 40 | - [Material Dialogs](https://github.com/afollestad/material-dialogs) 41 | - [Android Shell](https://github.com/jaredrummler/AndroidShell) 42 | 43 | # 协议 44 | 本源码遵循 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.txt) 协议 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XMiTools 2 | An xposed module for MIUI 10 / MIUI 11 / MIUI 12 / MIUI 12.5 SystemUI. 3 | 4 | [中文说明](/README-CN.md) 5 | # Screenshots 6 | 7 | 8 | # Download 9 | You can download in the following sites: 10 | - [GitHub releases](/releases) 11 | - [CoolApk](https://www.coolapk.com/apk/com.tianma.tweaks.miui) 12 | - [Xposed Repository](https://repo.xposed.info/module/com.tianma.tweaks.miui) 13 | 14 | # Attention 15 | - Only compatible for MIUI. Other ROM may not suitable. 16 | - Support Xposed, EdXposed, LSPosed and TaiChi. 17 | 18 | # Features 19 | - Status Bar 20 | 1. Show seconds in status bar clock 21 | 2. Custom clock alignment (left, center, right) 22 | 3. Custom clock time format 23 | 4. Custom clock time color 24 | 5. Signal icons align left 25 | 6. Display dual mobile signals 26 | 7. Custom displayed mobile network type 27 | - Dropdown Status Bar 28 | 1. Show seconds in dropdown status bar clock 29 | 2. Custom clock time and date color 30 | 3. Show weather info 31 | - Lock Screen 32 | 1. Show seconds in horizontal clock 33 | 2. Show seconds in vertical clock 34 | - And so on... 35 | 36 | 37 | # Credits 38 | - [custoMIUIzer](https://code.highspec.ru/Mikanoshi/CustoMIUIzer/) 39 | - [GravityBox](https://github.com/GravityBox/GravityBox) 40 | - [Xposed](https://github.com/rovo89/Xposed) 41 | - [Material Dialogs](https://github.com/afollestad/material-dialogs) 42 | - [Android Shell](https://github.com/jaredrummler/AndroidShell) 43 | 44 | # License 45 | All code is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.txt) 46 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | 3 | /signature.properties 4 | -------------------------------------------------------------------------------- /app/and-res-guard.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'AndResGuard' 2 | 3 | andResGuard { 4 | // mappingFile = file("./resource_mapping.txt") 5 | mappingFile = null 6 | use7zip = true 7 | useSign = true 8 | // 打开这个开关,会keep住所有资源的原始路径,只混淆资源的名字 9 | keepRoot = false 10 | whiteList = [ 11 | // for your icon 12 | "R.mipmap.ic_launcher", 13 | "R.mipmap.ic_launcher_round", 14 | "R.mipmap.ic_launcher_foreground", 15 | 16 | // Umeng sdk 17 | "R.anim.umeng*", 18 | "R.string.umeng*", 19 | "R.string.UM*", 20 | "R.string.tb_*", 21 | "R.layout.umeng*", 22 | "R.layout.socialize_*", 23 | "R.layout.*messager*", 24 | "R.layout.tb_*", 25 | "R.color.umeng*", 26 | "R.color.tb_*", 27 | "R.style.*UM*", 28 | "R.style.umeng*", 29 | "R.drawable.umeng*", 30 | "R.drawable.tb_*", 31 | "R.drawable.sina*", 32 | "R.drawable.qq_*", 33 | "R.drawable.tb_*", 34 | "R.id.umeng*", 35 | "R.id.*messager*", 36 | "R.id.progress_bar_parent", 37 | "R.id.socialize_*", 38 | "R.id.webView", 39 | 40 | // Google service 41 | "R.string.google_app_id", 42 | "R.string.gcm_defaultSenderId", 43 | "R.string.default_web_client_id", 44 | "R.string.ga_trackingId", 45 | "R.string.firebase_database_url", 46 | "R.string.google_api_key", 47 | "R.string.google_crash_reporting_api_key", 48 | 49 | // getui 50 | "R.drawable.push", 51 | "R.drawable.push_small", 52 | "R.layout.getui_notification", 53 | 54 | // JPush 55 | "R.drawable.jpush_notification_icon", 56 | 57 | // GrowingIO 58 | "R.string.growingio_project_id", 59 | "R.string.growingio_url_scheme", 60 | "R.string.growingio_channel", 61 | ] 62 | compressFilePattern = [ 63 | "*.png", 64 | "*.jpg", 65 | "*.jpeg", 66 | "*.gif", 67 | ] 68 | sevenzip { 69 | artifact = 'com.tencent.mm:SevenZip:1.2.17' 70 | //path = "/usr/local/bin/7za" 71 | } 72 | 73 | /** 74 | * 可选: 如果不设置则会默认覆盖assemble输出的apk 75 | **/ 76 | // finalApkBackupPath = "${project.rootDir}/final.apk" 77 | 78 | /** 79 | * 可选: 指定v1签名时生成jar文件的摘要算法 80 | * 默认值为“SHA-1” 81 | **/ 82 | // digestalg = "SHA-256" 83 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply from: 'and-res-guard.gradle' 5 | 6 | def keyFile = file(findProperty("tianma.keystore.path") as String) 7 | def propFile = file(findProperty("tianma.signature.path") as String) 8 | 9 | def keyProps = new Properties() 10 | if (propFile.exists()) { 11 | keyProps.load(new FileInputStream(propFile)) 12 | } 13 | 14 | def static releaseTime() { 15 | return new Date().format("yyMMdd", TimeZone.default) 16 | } 17 | 18 | ext { 19 | VERSION_CODE = 17 20 | VERSION_NAME = "1.4.0" 21 | } 22 | 23 | android { 24 | compileSdkVersion 28 25 | defaultConfig { 26 | applicationId "com.tianma.tweaks.miui" 27 | minSdkVersion 21 28 | targetSdkVersion 27 29 | versionCode VERSION_CODE 30 | versionName VERSION_NAME 31 | 32 | buildConfigField("String", "LOG_TAG", "\"XMiTools\"") 33 | buildConfigField("boolean", "LOG_TO_XPOSED", "true") 34 | buildConfigField("boolean", "LOG_TO_EDXPOSED", "true") 35 | buildConfigField("int", "MODULE_VERSION", "" + VERSION_CODE) 36 | } 37 | 38 | lintOptions { 39 | disable 'MissingTranslation' 40 | } 41 | 42 | signingConfigs { 43 | release { 44 | storeFile keyFile 45 | storePassword keyProps['STORE_PASSWORD'] 46 | keyAlias keyProps['KEY_ALIAS'] 47 | keyPassword keyProps['KEY_PASSWORD'] 48 | } 49 | } 50 | 51 | buildTypes { 52 | debug { 53 | buildConfigField("int", "LOG_LEVEL", "2") 54 | 55 | if (keyFile.exists()) { 56 | println "using keystore" 57 | signingConfig signingConfigs.release 58 | } 59 | 60 | debuggable false 61 | } 62 | 63 | release { 64 | buildConfigField("int", "LOG_LEVEL", "4") 65 | 66 | minifyEnabled true 67 | shrinkResources true 68 | zipAlignEnabled true 69 | 70 | if (keyFile.exists()) { 71 | println "using keystore" 72 | signingConfig signingConfigs.release 73 | } 74 | 75 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 76 | } 77 | } 78 | 79 | compileOptions { 80 | sourceCompatibility JavaVersion.VERSION_1_8 81 | targetCompatibility JavaVersion.VERSION_1_8 82 | } 83 | kotlinOptions { 84 | jvmTarget = '1.8' 85 | } 86 | 87 | applicationVariants.all { variant -> 88 | variant.outputs.all { output -> 89 | output.outputFileName = "XMiTools_${variant.versionName.replaceAll('\\s+', '_')}_${releaseTime()}_${variant.buildType.name.charAt(0)}.apk" 90 | } 91 | } 92 | } 93 | 94 | ext { 95 | gsonVersion = "2.8.6" // gson 96 | 97 | rxJavaVersion = "2.2.17" // RxJava 2 98 | rxAndroidVersion = "2.1.1" // RxAndroid 2 99 | 100 | okHttpVersion = "4.4.0" // OkHttp 101 | retrofitVersion = "2.5.0" // retrofit 102 | 103 | materialDialogsVersion = "3.3.0" 104 | } 105 | 106 | dependencies { 107 | implementation fileTree(dir: 'libs', include: ['*.jar']) 108 | implementation 'androidx.appcompat:appcompat:1.2.0' 109 | implementation 'com.google.android.material:material:1.3.0' // material design support 110 | implementation 'androidx.preference:preference-ktx:1.1.1' // preference-v7 111 | implementation 'androidx.browser:browser:1.3.0' // custom tabs 112 | implementation "androidx.constraintlayout:constraintlayout:2.0.4" // constraint layout 113 | 114 | // Xposed 115 | compileOnly 'de.robv.android.xposed:api:82' 116 | compileOnly 'de.robv.android.xposed:api:82:sources' 117 | 118 | // ButterKnife 119 | // implementation 'com.jakewharton:butterknife:10.2.0' 120 | // annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0' 121 | 122 | // Android Shell 123 | implementation 'com.jaredrummler:android-shell:1.0.0' 124 | 125 | // Material Dialogs 126 | implementation "com.afollestad.material-dialogs:core:$materialDialogsVersion" 127 | 128 | // color picker preference 129 | implementation 'com.jaredrummler:colorpicker:1.1.0' 130 | 131 | // Gson 132 | implementation "com.google.code.gson:gson:$gsonVersion" 133 | 134 | // RxJava 2 135 | implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion" 136 | // RxAndroid 2 137 | implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" 138 | 139 | // OkHttp 140 | implementation "com.squareup.okhttp3:okhttp:$okHttpVersion" 141 | // OkHttp logging interceptor 142 | implementation "com.squareup.okhttp3:logging-interceptor:$okHttpVersion" 143 | 144 | // Retrofit 145 | implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" 146 | // Retrofit Gson converter 147 | implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" 148 | // Retrofit scalars converter 149 | implementation "com.squareup.retrofit2:converter-scalars:$retrofitVersion" 150 | // Retrofit RxJava Adapter 151 | implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion" 152 | 153 | // Kotlin 154 | implementation "androidx.core:core-ktx:1.3.0" 155 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 156 | 157 | // flex-box layout 158 | implementation 'com.google.android:flexbox:2.0.1' 159 | } 160 | repositories { 161 | mavenCentral() 162 | } 163 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # 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 22 | 23 | -keepclasseswithmembers class * implements de.robv.android.xposed.IXposedHookLoadPackage { 24 | public void handleLoadPackage(...); 25 | } 26 | 27 | -keepclasseswithmembers class * implements de.robv.android.xposed.IXposedHookZygoteInit { 28 | public void initZygote(...); 29 | } 30 | 31 | -keep class com.tianma.tweaks.miui.utils.ModuleUtils { 32 | int getModuleVersion(); 33 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 15 | 16 | 19 | 22 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.tianma.tweaks.miui.xp.HookEntry -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/App.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app 2 | 3 | import android.app.Application 4 | 5 | /** 6 | * desc: Application instance 7 | * date: 2021/10/7 8 | */ 9 | class App : Application() { 10 | 11 | companion object { 12 | lateinit var appContext: Application 13 | private set 14 | } 15 | 16 | override fun onCreate() { 17 | super.onCreate() 18 | 19 | appContext = this 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import android.os.Looper 6 | import android.view.Menu 7 | import android.view.MenuItem 8 | import com.afollestad.materialdialogs.MaterialDialog 9 | import com.tianma.tweaks.miui.R 10 | import com.tianma.tweaks.miui.app.base.BaseActivity 11 | import com.tianma.tweaks.miui.app.fragment.* 12 | import com.tianma.tweaks.miui.utils.* 13 | import kotlinx.android.synthetic.main.activity_main.* 14 | import kotlinx.android.synthetic.main.toolbar.* 15 | 16 | /** 17 | * HomeActivity 18 | */ 19 | class MainActivity : BaseActivity() { 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_main) 24 | setupToolbar() 25 | initFragments() 26 | showModuleStatus() 27 | } 28 | 29 | private fun setupToolbar() { 30 | setSupportActionBar(toolbar) 31 | } 32 | 33 | private fun initFragments() { 34 | val fragments = mutableListOf().apply { 35 | add(GeneralSettingsFragment(getString(R.string.pref_general_title))) 36 | add(StatusBarSettingsFragment(getString(R.string.pref_status_bar_title))) 37 | add(DropDownStatusBarSettingsFragment(getString(R.string.pref_dropdown_status_bar_title))) 38 | add(KeyguardSettingsFragment(getString(R.string.pref_keyguard_title))) 39 | } 40 | 41 | viewPager.adapter = SettingsFragmentPagerAdapter(supportFragmentManager, fragments) 42 | tabLayout.setupWithViewPager(viewPager) 43 | } 44 | 45 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 46 | menuInflater.inflate(R.menu.main_menu, menu) 47 | return true 48 | } 49 | 50 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 51 | when (item.itemId) { 52 | R.id.action_reboot_system -> performRebootSystem() 53 | R.id.action_soft_reboot_system -> preformSoftRebootSystem() 54 | R.id.action_restart_host_apps -> performRestartHostApps() 55 | R.id.action_taichi_users_notice -> showTaiChiUsersNotice() 56 | R.id.action_edxposed_users_notice -> showEdXposedUsersNotice() 57 | else -> return super.onOptionsItemSelected(item) 58 | } 59 | return true 60 | } 61 | 62 | private fun performRebootSystem() { 63 | MaterialDialog(this).show { 64 | title(R.string.action_reboot_system) 65 | message(R.string.prompt_reboot_system_message) 66 | positiveButton(R.string.confirm) { 67 | RootUtils.reboot() 68 | } 69 | negativeButton(R.string.cancel) 70 | } 71 | } 72 | 73 | private fun preformSoftRebootSystem() { 74 | MaterialDialog(this).show { 75 | title(R.string.action_soft_reboot_system) 76 | message(R.string.prompt_soft_reboot_message) 77 | positiveButton(R.string.confirm) { 78 | RootUtils.softReboot() 79 | } 80 | negativeButton(R.string.cancel) 81 | } 82 | } 83 | 84 | private fun performRestartHostApps() { 85 | MaterialDialog(this).show { 86 | title(R.string.action_restart_host_apps) 87 | message(R.string.prompt_restart_host_apps_message) 88 | positiveButton(R.string.confirm) { 89 | RootUtils.restartSystemUI() 90 | } 91 | negativeButton(R.string.cancel) 92 | } 93 | } 94 | 95 | private fun showTaiChiUsersNotice() { 96 | MaterialDialog(this).show { 97 | title(R.string.action_taichi_users_notice) 98 | message(R.string.prompt_taichi_users_notice_message) 99 | positiveButton(R.string.check_module) { 100 | PackageUtils.startCheckModuleInTaiChi(this@MainActivity) 101 | } 102 | negativeButton(R.string.add_applications) { 103 | PackageUtils.startAddAppsInTaiChi(this@MainActivity) 104 | } 105 | } 106 | } 107 | 108 | private fun showEdXposedUsersNotice() { 109 | MaterialDialog(this).show { 110 | title(R.string.action_edxposed_users_notice) 111 | message(R.string.edxposed_users_notice_content) 112 | positiveButton(R.string.confirm) 113 | } 114 | } 115 | 116 | private fun showModuleStatus() { 117 | val handler = Handler(Looper.getMainLooper()) 118 | handler.postDelayed({ 119 | if (isFinishing) { 120 | return@postDelayed 121 | } 122 | val format = "%s (%s)" 123 | val appName = getString(R.string.app_name) 124 | val appTitle = if (ModuleUtils.isModuleActive()) { 125 | String.format(format, appName, getString(R.string.module_status_active)) 126 | } else { 127 | String.format(format, appName, getString(R.string.module_status_inactive)) 128 | } 129 | title = appTitle 130 | }, 1000L) 131 | } 132 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.base 2 | 3 | import android.content.Context 4 | import android.view.MenuItem 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.tianma.tweaks.miui.utils.ContextUtils 7 | 8 | abstract class BaseActivity : AppCompatActivity() { 9 | 10 | override fun attachBaseContext(newBase: Context?) { 11 | val context = if (newBase != null) { 12 | ContextUtils.getProtectedContextIfNecessary(newBase) 13 | } else { 14 | newBase 15 | } 16 | super.attachBaseContext(context) 17 | } 18 | 19 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 20 | val itemId = item.itemId 21 | if (itemId == android.R.id.home) { 22 | onBackPressed() 23 | return true 24 | } 25 | return super.onOptionsItemSelected(item) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/base/BasePreferenceFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.base 2 | 3 | import android.os.Bundle 4 | import androidx.preference.PreferenceFragmentCompat 5 | import com.tianma.tweaks.miui.cons.AppConst 6 | 7 | abstract class BasePreferenceFragment : PreferenceFragmentCompat() { 8 | 9 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 10 | val pm = preferenceManager 11 | pm.sharedPreferencesName = AppConst.XMI_TOOLS_PREFS_NAME 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/fragment/BaseSettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.fragment 2 | 3 | import com.tianma.tweaks.miui.app.base.BasePreferenceFragment 4 | import com.tianma.tweaks.miui.cons.AppConst 5 | import com.tianma.tweaks.miui.utils.ContextUtils 6 | import com.tianma.tweaks.miui.utils.StorageUtils 7 | 8 | /** 9 | * Base Fragment for settings. 10 | */ 11 | abstract class BaseSettingsFragment @JvmOverloads constructor(val title: CharSequence? = "") : 12 | BasePreferenceFragment() { 13 | 14 | override fun onPause() { 15 | super.onPause() 16 | setPreferenceWorldWritable() 17 | } 18 | 19 | private fun setPreferenceWorldWritable() { 20 | val activity = activity ?: return 21 | val context = ContextUtils.getProtectedContextIfNecessary(activity.applicationContext) 22 | 23 | val prefsFile = StorageUtils.getSharedPreferencesFile(context, AppConst.XMI_TOOLS_PREFS_NAME) 24 | StorageUtils.setFileWorldWritable(prefsFile, 2) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/fragment/DropDownStatusBarSettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.fragment 2 | 3 | import android.os.Bundle 4 | import android.text.InputType 5 | import androidx.preference.EditTextPreference 6 | import androidx.preference.Preference 7 | import com.tianma.tweaks.miui.R 8 | import com.tianma.tweaks.miui.cons.PrefConst 9 | 10 | /** 11 | * dSettings fragment for System DropDown StatusBar 12 | */ 13 | class DropDownStatusBarSettingsFragment(title: CharSequence? = ""): BaseSettingsFragment(title), Preference.OnPreferenceChangeListener { 14 | 15 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 16 | super.onCreatePreferences(savedInstanceState, rootKey) 17 | addPreferencesFromResource(R.xml.dropdown_statusbar_settings) 18 | 19 | val weatherTextSizePref = findPreference(PrefConst.DROPDOWN_STATUS_BAR_WEATHER_TEXT_SIZE) 20 | weatherTextSizePref?.let { 21 | weatherTextSizePref.setOnBindEditTextListener { editText -> 22 | editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL 23 | editText.setSelection(editText.text.length) 24 | } 25 | weatherTextSizePref.onPreferenceChangeListener = this 26 | } 27 | } 28 | 29 | override fun onResume() { 30 | super.onResume() 31 | 32 | val weatherTextSizePref = findPreference(PrefConst.DROPDOWN_STATUS_BAR_WEATHER_TEXT_SIZE) 33 | weatherTextSizePref?.let { 34 | showWeatherTextSize(it, it.text) 35 | } 36 | } 37 | 38 | override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean { 39 | val key = preference?.key 40 | if (PrefConst.DROPDOWN_STATUS_BAR_WEATHER_TEXT_SIZE == key) { 41 | val value = newValue as String? 42 | if (value.isNullOrEmpty()) { 43 | return false 44 | } else { 45 | showWeatherTextSize(preference, value) 46 | } 47 | } else { 48 | return false 49 | } 50 | return true 51 | } 52 | 53 | private fun showWeatherTextSize(preference: Preference, newValue: String) { 54 | preference.summary = newValue 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/fragment/GeneralSettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.fragment 2 | 3 | import android.app.Activity 4 | import android.content.ComponentName 5 | import android.content.pm.PackageManager 6 | import android.os.Bundle 7 | import androidx.preference.Preference 8 | import com.tianma.tweaks.miui.BuildConfig 9 | import com.tianma.tweaks.miui.R 10 | import com.tianma.tweaks.miui.cons.AppConst 11 | import com.tianma.tweaks.miui.cons.PrefConst 12 | import com.tianma.tweaks.miui.utils.PackageUtils 13 | import com.tianma.tweaks.miui.utils.Utils 14 | 15 | class GeneralSettingsFragment(title: CharSequence? = "") : BaseSettingsFragment(title), Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener { 16 | 17 | private lateinit var mActivity: Activity 18 | 19 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 20 | super.onCreatePreferences(savedInstanceState, rootKey) 21 | addPreferencesFromResource(R.xml.main_settings) 22 | 23 | findPreference(PrefConst.HIDE_LAUNCHER_ICON)?.onPreferenceChangeListener = this 24 | 25 | findPreference(PrefConst.SOURCE_CODE)?.onPreferenceClickListener = this 26 | findPreference(PrefConst.KEY_JOIN_QQ_GROUP)?.onPreferenceClickListener = this 27 | findPreference(PrefConst.DONATE_BY_ALIPAY)?.onPreferenceClickListener = this 28 | } 29 | 30 | override fun onActivityCreated(savedInstanceState: Bundle?) { 31 | super.onActivityCreated(savedInstanceState) 32 | mActivity = requireActivity() 33 | } 34 | 35 | override fun onResume() { 36 | super.onResume() 37 | 38 | showVersionInfo() 39 | } 40 | 41 | override fun onPreferenceClick(preference: Preference?): Boolean { 42 | when (preference?.key) { 43 | PrefConst.SOURCE_CODE -> { 44 | showSourceCode() 45 | } 46 | PrefConst.KEY_JOIN_QQ_GROUP -> { 47 | joinQQGroup() 48 | } 49 | PrefConst.DONATE_BY_ALIPAY -> { 50 | donateByAlipay() 51 | } 52 | else -> { 53 | return false 54 | } 55 | } 56 | return true 57 | } 58 | 59 | override fun onPreferenceChange(preference: Preference?, newValue: Any): Boolean { 60 | val key = preference?.key 61 | if (PrefConst.HIDE_LAUNCHER_ICON == key) { 62 | hideOrShowLauncherIcon(newValue as Boolean) 63 | } else { 64 | return false 65 | } 66 | return true 67 | } 68 | 69 | private fun hideOrShowLauncherIcon(hide: Boolean) { 70 | val pm = mActivity.packageManager 71 | val launcherCN = ComponentName(mActivity, AppConst.MAIN_ACTIVITY_ALIAS) 72 | val state = if (hide) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED 73 | if (pm.getComponentEnabledSetting(launcherCN) != state) { 74 | pm.setComponentEnabledSetting(launcherCN, state, PackageManager.DONT_KILL_APP) 75 | } 76 | } 77 | 78 | private fun showVersionInfo() { 79 | findPreference(PrefConst.APP_VERSION)?.summary = BuildConfig.VERSION_NAME 80 | } 81 | 82 | private fun showSourceCode() { 83 | Utils.showWebPage(activity, AppConst.PROJECT_SOURCE_CODE_URL) 84 | } 85 | 86 | private fun joinQQGroup() { 87 | PackageUtils.joinQQGroup(context) 88 | } 89 | 90 | private fun donateByAlipay() { 91 | PackageUtils.startAlipayDonatePage(context) 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/fragment/KeyguardSettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.fragment 2 | 3 | import android.os.Bundle 4 | import android.text.InputType 5 | import androidx.preference.EditTextPreference 6 | import androidx.preference.Preference 7 | import com.tianma.tweaks.miui.R 8 | import com.tianma.tweaks.miui.app.widget.dialog.OneSentenceSettingsDialogWrapper 9 | import com.tianma.tweaks.miui.cons.PrefConst 10 | 11 | /** 12 | * Settings fragment for LockScreen 13 | */ 14 | class KeyguardSettingsFragment(title: CharSequence? = "") : BaseSettingsFragment(title), Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { 15 | 16 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 17 | super.onCreatePreferences(savedInstanceState, rootKey) 18 | 19 | addPreferencesFromResource(R.xml.keyguard_settings) 20 | 21 | findPreference(PrefConst.ONE_SENTENCE_SETTINGS)?.onPreferenceClickListener = this 22 | 23 | val oneSentencePref = findPreference(PrefConst.ONE_SENTENCE_TEXT_SIZE) 24 | oneSentencePref?.let { 25 | oneSentencePref.setOnBindEditTextListener { editText -> 26 | editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL 27 | editText.setSelection(editText.text.length) 28 | } 29 | oneSentencePref.onPreferenceChangeListener = this 30 | } 31 | } 32 | 33 | override fun onResume() { 34 | super.onResume() 35 | 36 | val oneSentencePref = findPreference(PrefConst.ONE_SENTENCE_TEXT_SIZE) 37 | oneSentencePref?.let { 38 | showOneSentenceTextSize(it, it.text) 39 | } 40 | } 41 | 42 | override fun onPreferenceClick(preference: Preference?): Boolean { 43 | val key = preference?.key 44 | if (key == PrefConst.ONE_SENTENCE_SETTINGS) { 45 | onOneSentenceSettingsClicked() 46 | } else { 47 | return false 48 | } 49 | return true 50 | } 51 | 52 | override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean { 53 | val key = preference?.key 54 | if (PrefConst.ONE_SENTENCE_TEXT_SIZE == key) { 55 | val value = newValue as String? 56 | if (value.isNullOrEmpty()) { 57 | return false 58 | } else { 59 | showOneSentenceTextSize(preference, value) 60 | } 61 | } 62 | return true 63 | } 64 | 65 | private fun onOneSentenceSettingsClicked() { 66 | context?.let { 67 | OneSentenceSettingsDialogWrapper(it).show() 68 | } 69 | } 70 | 71 | private fun showOneSentenceTextSize(preference: Preference, newValue: String) { 72 | preference.summary = newValue 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/fragment/SettingsFragmentPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.fragment 2 | 3 | import androidx.fragment.app.FragmentManager 4 | import androidx.fragment.app.FragmentPagerAdapter 5 | 6 | class SettingsFragmentPagerAdapter( 7 | fm: FragmentManager, 8 | private var fragments: List 9 | ) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { 10 | 11 | override fun getItem(position: Int) = fragments[position] 12 | 13 | override fun getCount() = fragments.size 14 | 15 | override fun getPageTitle(position: Int): CharSequence? { 16 | return fragments[position].title 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/fragment/StatusBarSettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.fragment 2 | 3 | import android.os.Bundle 4 | import androidx.preference.Preference 5 | import com.tianma.tweaks.miui.R 6 | import com.tianma.tweaks.miui.cons.PrefConst 7 | 8 | /** 9 | * Settings fragment for System StatusBar 10 | */ 11 | class StatusBarSettingsFragment(title: CharSequence? = "") : BaseSettingsFragment(title), Preference.OnPreferenceChangeListener { 12 | 13 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 14 | super.onCreatePreferences(savedInstanceState, rootKey) 15 | addPreferencesFromResource(R.xml.statusbar_settings) 16 | 17 | findPreference(PrefConst.STATUS_BAR_CLOCK_FORMAT)?.onPreferenceChangeListener = this 18 | findPreference(PrefConst.CUSTOM_MOBILE_NETWORK_TYPE)?.onPreferenceChangeListener = this 19 | } 20 | 21 | override fun onResume() { 22 | super.onResume() 23 | 24 | val sp = preferenceManager.sharedPreferences 25 | 26 | val timeFormatPref = findPreference(PrefConst.STATUS_BAR_CLOCK_FORMAT) 27 | timeFormatPref?.let { 28 | val timeFormat = sp.getString(PrefConst.STATUS_BAR_CLOCK_FORMAT, PrefConst.STATUS_BAR_CLOCK_FORMAT_DEFAULT) 29 | showStatusBarClockFormat(timeFormatPref, timeFormat) 30 | } 31 | 32 | val networkTypePref = findPreference(PrefConst.CUSTOM_MOBILE_NETWORK_TYPE) 33 | networkTypePref?.let { 34 | val networkType = sp.getString(PrefConst.CUSTOM_MOBILE_NETWORK_TYPE, PrefConst.CUSTOM_MOBILE_NETWORK_TYPE_DEFAULT) 35 | showCustomMobileNetworkType(networkTypePref, networkType) 36 | } 37 | } 38 | 39 | override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean { 40 | val key = preference?.key 41 | when { 42 | PrefConst.STATUS_BAR_CLOCK_FORMAT == key -> { 43 | showStatusBarClockFormat(preference, newValue as String?) 44 | } 45 | PrefConst.CUSTOM_MOBILE_NETWORK_TYPE == key -> { 46 | showCustomMobileNetworkType(preference, newValue as String?) 47 | } 48 | else -> { 49 | return false 50 | } 51 | } 52 | return true 53 | } 54 | 55 | private fun showStatusBarClockFormat(preference: Preference, newValue: String?) { 56 | preference.summary = newValue 57 | } 58 | 59 | private fun showCustomMobileNetworkType(preference: Preference, newValue: String?) { 60 | preference.summary = newValue 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/widget/tag/ItemClickCallback.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.widget.tag 2 | 3 | import android.view.View 4 | 5 | interface ItemClickCallback { 6 | 7 | fun onItemClicked(itemView: View?, item: E?, position: Int) 8 | 9 | fun onItemLongClicked(itemView: View?, item: E?, position: Int): Boolean 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/widget/tag/TagAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.widget.tag 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.tianma.tweaks.miui.R 10 | 11 | class TagAdapter(private var context: Context, private var dataList: MutableList) : RecyclerView.Adapter() { 12 | 13 | var itemClickCallback: ItemClickCallback? = null 14 | 15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TagViewHolder { 16 | val itemView = LayoutInflater.from(context).inflate(R.layout.tag_view, parent, false) 17 | return TagViewHolder(itemView) 18 | } 19 | 20 | override fun getItemCount(): Int { 21 | return dataList.size 22 | } 23 | 24 | override fun onBindViewHolder(holder: TagViewHolder, position: Int) { 25 | val tagBean = dataList[position] 26 | holder.bindData(tagBean, position) 27 | holder.bindListener(tagBean, position) 28 | } 29 | 30 | inner class TagViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 31 | private lateinit var tagView: TextView 32 | 33 | init { 34 | bindViews() 35 | } 36 | 37 | private fun bindViews() { 38 | tagView = itemView.findViewById(R.id.tag_text_view) 39 | } 40 | 41 | fun bindData(tagBean: TagBean, position: Int) { 42 | tagView.text = tagBean.value 43 | 44 | tagView.isSelected = tagBean.isSelected 45 | tagView.isEnabled = tagBean.isEnabled 46 | } 47 | 48 | fun bindListener(tagBean: TagBean, position: Int) { 49 | itemClickCallback?.let { itemClickCallback -> 50 | itemView.setOnClickListener { itemView -> 51 | itemClickCallback.onItemClicked(itemView, tagBean, position) 52 | } 53 | 54 | itemView.setOnLongClickListener { itemView -> 55 | itemClickCallback.onItemLongClicked(itemView, tagBean, position) 56 | } 57 | } 58 | } 59 | } 60 | 61 | fun getDataList(): List { 62 | return dataList 63 | } 64 | 65 | fun setDataList(list: MutableList) { 66 | dataList.clear() 67 | dataList.addAll(list) 68 | notifyDataSetChanged() 69 | } 70 | 71 | fun setAllSelected(selected: Boolean) { 72 | for (tagBean in dataList) { 73 | tagBean.isSelected = selected 74 | } 75 | notifyDataSetChanged() 76 | } 77 | 78 | fun setItemSelected(selected: Boolean, position: Int) { 79 | dataList[position].isSelected = selected 80 | notifyDataSetChanged() 81 | } 82 | 83 | fun setAllEnabled(enabled: Boolean) { 84 | for (tagBean in dataList) { 85 | tagBean.isEnabled = enabled 86 | } 87 | notifyDataSetChanged() 88 | } 89 | 90 | 91 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/app/widget/tag/TagBean.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.app.widget.tag; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class TagBean implements Parcelable { 7 | 8 | private String key; 9 | private String value; 10 | private boolean selected = false; 11 | private boolean enabled = true; 12 | 13 | public TagBean() { 14 | this(null, null); 15 | } 16 | 17 | public TagBean(String key, String value) { 18 | this.key = key; 19 | this.value = value; 20 | } 21 | 22 | public String getKey() { 23 | return key; 24 | } 25 | 26 | public void setKey(String key) { 27 | this.key = key; 28 | } 29 | 30 | public String getValue() { 31 | return value; 32 | } 33 | 34 | public void setValue(String value) { 35 | this.value = value; 36 | } 37 | 38 | public boolean isSelected() { 39 | return selected; 40 | } 41 | 42 | public void setSelected(boolean selected) { 43 | this.selected = selected; 44 | } 45 | 46 | public boolean isEnabled() { 47 | return enabled; 48 | } 49 | 50 | public void setEnabled(boolean enabled) { 51 | this.enabled = enabled; 52 | } 53 | 54 | @Override 55 | public int describeContents() { 56 | return 0; 57 | } 58 | 59 | @Override 60 | public void writeToParcel(Parcel dest, int flags) { 61 | dest.writeString(key); 62 | dest.writeString(value); 63 | dest.writeByte((byte) (selected ? 1 : 0)); 64 | dest.writeByte((byte) (enabled ? 1 : 0)); 65 | } 66 | 67 | protected TagBean(Parcel in) { 68 | key = in.readString(); 69 | value = in.readString(); 70 | selected = in.readByte() != 0; 71 | enabled = in.readByte() != 0; 72 | } 73 | 74 | public static final Creator CREATOR = new Creator() { 75 | @Override 76 | public TagBean createFromParcel(Parcel in) { 77 | return new TagBean(in); 78 | } 79 | 80 | @Override 81 | public TagBean[] newArray(int size) { 82 | return new TagBean[size]; 83 | } 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/cons/AppConst.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.cons 2 | 3 | import com.tianma.tweaks.miui.BuildConfig 4 | 5 | object AppConst { 6 | // MiTweaks begin 7 | const val MAIN_ACTIVITY_ALIAS = BuildConfig.APPLICATION_ID + ".app.MainActivityAlias" 8 | const val PROJECT_SOURCE_CODE_URL = "https://github.com/tianma8023/XMiTools" 9 | const val XMI_TOOLS_PREFS_NAME = BuildConfig.APPLICATION_ID + "_preferences" 10 | // MiTweaks end 11 | 12 | // Alipay begin 13 | const val ALIPAY_PACKAGE_NAME = "com.eg.android.AlipayGphone" 14 | const val ALIPAY_QRCODE_URI_PREFIX = "alipayqr://platformapi/startapp?saId=10000007&qrcode=" 15 | const val ALIPAY_QRCODE_URL = "HTTPS://QR.ALIPAY.COM/FKX074142EKXD0OIMV8B60" 16 | // Alipay end 17 | 18 | // QQ begin 19 | const val QQ_GROUP_KEY = "bJYxW0EzBLB-3NeX1DBuOBxq9sSXnxN4" 20 | // QQ end 21 | 22 | // Taichi begin 23 | const val TAICHI_PACKAGE_NAME = "me.weishu.exp" 24 | const val TAICHI_MAIN_PAGE = "me.weishu.exp.ui.MainActivity" 25 | // Taichi end 26 | 27 | // Xposed Installer begin 28 | const val XPOSED_PACKAGE = "de.robv.android.xposed.installer" 29 | 30 | // Old Xposed installer 31 | const val XPOSED_OPEN_SECTION_ACTION = "$XPOSED_PACKAGE.OPEN_SECTION" 32 | const val XPOSED_EXTRA_SECTION = "section" 33 | 34 | // New Xposed installer 35 | const val XPOSED_ACTIVITY = "$XPOSED_PACKAGE.WelcomeActivity" 36 | const val XPOSED_EXTRA_FRAGMENT = "fragment" 37 | // Xposed Installer end 38 | 39 | // MIUI Weather begin 40 | const val MIUI_WEATHER_PACKAGE = "com.miui.weather2" 41 | // MIUI Weather end 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/cons/PrefConst.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.cons 2 | 3 | import com.tianma.tweaks.miui.BuildConfig 4 | 5 | object PrefConst { 6 | const val SHARED_PREFS_NAME = BuildConfig.APPLICATION_ID + "_preferences" 7 | 8 | // General Settings 9 | const val MAIN_SWITCH = "main_switch" 10 | const val HIDE_LAUNCHER_ICON = "hide_launcher_icon" 11 | // General Settings End 12 | 13 | // StatusBar Settings 14 | const val SHOW_SEC_IN_STATUS_BAR = "show_sec_in_status_bar" 15 | const val STATUS_BAR_CLOCK_ALIGNMENT = "status_bar_clock_alignment" 16 | const val ALIGNMENT_LEFT = "left" 17 | const val ALIGNMENT_CENTER = "center" 18 | const val ALIGNMENT_RIGHT = "right" 19 | const val STATUS_BAR_CLOCK_COLOR_ENABLE = "status_bar_clock_color_enable" 20 | const val STATUS_BAR_CLOCK_COLOR = "status_bar_clock_color" 21 | const val STATUS_BAR_CLOCK_FORMAT_ENABLE = "status_bar_clock_format_enable" 22 | const val STATUS_BAR_CLOCK_FORMAT = "status_bar_clock_format" 23 | const val STATUS_BAR_CLOCK_FORMAT_DEFAULT = "HH:mm:ss" 24 | const val STATUS_BAR_SIGNAL_ALIGN_LEFT = "status_bar_signal_align_left" 25 | const val STATUS_BAR_DUAL_MOBILE_SIGNAL = "status_bar_dual_mobile_signal" 26 | const val STATUS_BAR_HIDE_VPN_ICON = "status_bar_hide_vpn_icon" 27 | const val STATUS_BAR_HIDE_HD_ICON = "status_bar_hide_hd_icon" 28 | const val STATUS_BAR_SHOW_SMALL_BATTERY_PERCENT_SIGN = "status_bar_show_small_battery_percent_sign" 29 | const val CUSTOM_MOBILE_NETWORK_TYPE_ENABLE = "custom_mobile_network_type_enable" 30 | const val CUSTOM_MOBILE_NETWORK_TYPE = "custom_mobile_network_type" 31 | const val CUSTOM_MOBILE_NETWORK_TYPE_DEFAULT = "5G" 32 | const val ALWAYS_SHOW_STATUS_BAR_CLOCK = "always_show_status_bar_clock" 33 | // StatusBar Settings End 34 | 35 | // Dropdown StatusBar Settings 36 | const val SHOW_SEC_IN_DROPDOWN_STATUS_BAR = "show_sec_in_dropdown_status_bar" 37 | const val DROPDOWN_STATUS_BAR_CLOCK_COLOR_ENABLE = "dropdown_status_bar_clock_color_enable" 38 | const val DROPDOWN_STATUS_BAR_CLOCK_COLOR = "dropdown_status_bar_clock_color" 39 | const val DROPDOWN_STATUS_BAR_DATE_COLOR = "dropdown_status_bar_date_color" 40 | const val DROPDOWN_STATUS_BAR_WEATHER_ENABLE = "dropdown_status_bar_weather_enable" 41 | const val DROPDOWN_STATUS_BAR_WEATHER_TEXT_COLOR = "dropdown_status_bar_weather_text_color" 42 | const val DROPDOWN_STATUS_BAR_WEATHER_TEXT_SIZE = "dropdown_status_bar_weather_text_size" 43 | const val DROPDOWN_STATUS_BAR_WEATHER_TEXT_SIZE_DEFAULT = "14" 44 | // Dropdown StatusBar Settings End 45 | 46 | // Keyguard Settings 47 | const val SHOW_SEC_IN_KEYGUARD_HORIZONTAL = "show_sec_in_keyguard_horizontal" 48 | const val SHOW_SEC_IN_KEYGUARD_VERTICAL = "show_sec_in_keyguard_vertical" 49 | const val KEYGUARD_CLOCK_COLOR = "keyguard_clock_color" 50 | // Keyguard Settings end 51 | 52 | // About Settings 53 | const val APP_VERSION = "app_version" 54 | const val SOURCE_CODE = "source_code" 55 | const val KEY_JOIN_QQ_GROUP = "join_qq_group" 56 | const val DONATE_BY_ALIPAY = "donate_by_alipay" 57 | // About Settings End 58 | 59 | // 一言 60 | const val ONE_SENTENCE_ENABLE = "one_sentence_enable" 61 | const val ONE_SENTENCE_SETTINGS = "one_sentence_settings" 62 | const val ONE_SENTENCE_API_SOURCES = "one_sentence_api_sources" 63 | const val API_SOURCE_HITOKOTO = "source_hitokoto" 64 | const val API_SOURCE_ONE_POEM = "source_one_poem" 65 | const val HITOKOTO_CATEGORIES = "hitokoto_categories" 66 | const val HITOKOTO_CATEGORY_ALL = "all" 67 | const val SHOW_HITOKOTO_SOURCE = "show_hitokoto_source" 68 | const val ONE_POEM_CATEGORIES = "one_poem_categories" 69 | const val ONE_POEM_CATEGORY_ALL = "all" 70 | const val SHOW_POEM_AUTHOR = "show_poem_author" 71 | const val ONE_SENTENCE_REFRESH_RATE = "one_sentence_refresh_rate" 72 | const val ONE_SENTENCE_REFRESH_RATE_DEFAULT = "30" 73 | const val ONE_SENTENCE_COLOR = "one_sentence_color" 74 | const val ONE_SENTENCE_TEXT_SIZE = "one_sentence_text_size" 75 | const val ONE_SENTENCE_TEXT_SIZE_DEFAULT = "14" 76 | // 一言 End 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/data/http/APIConst.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.data.http 2 | 3 | object APIConst { 4 | /** 5 | * 一言API: https://developer.hitokoto.cn/ 6 | */ 7 | const val HITOKOTO_BASE_URL = "https://v1.hitokoto.cn/" 8 | 9 | /** 10 | * 诗词API: https://www.jinrishici.com/doc/ 11 | */ 12 | const val POEM_BASE_URL = "https://v1.jinrishici.com/" 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/data/http/entity/Hitokoto.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.data.http.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * 一言 7 | */ 8 | // { 9 | // "id": 4401, 10 | // "hitokoto": "那么难受,那么痛苦,可是 世界这么美丽...让我如何能够忘记!", 11 | // "type": "a", 12 | // "from": "朝花夕誓", 13 | // "creator": "飞龙project", 14 | // "created_at": "1553579805" 15 | // } 16 | data class Hitokoto( 17 | @SerializedName("id") 18 | val id: Int, 19 | @SerializedName("hitokoto") 20 | val content: String?, 21 | @SerializedName("type") 22 | val type: String?, 23 | @SerializedName("from") 24 | val from: String?, 25 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/data/http/entity/Poem.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.data.http.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * 诗 7 | */ 8 | data class Poem( 9 | @SerializedName("content") 10 | val content: String?, 11 | @SerializedName("origin") 12 | val origin: String?, 13 | @SerializedName("author") 14 | val author: String?, 15 | @SerializedName("category") 16 | val category: String? 17 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/data/http/repository/DataRepository.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.data.http.repository 2 | 3 | import com.tianma.tweaks.miui.data.http.APIConst 4 | import com.tianma.tweaks.miui.data.http.entity.Hitokoto 5 | import com.tianma.tweaks.miui.data.http.entity.Poem 6 | import com.tianma.tweaks.miui.data.http.service.HitokotoService 7 | import com.tianma.tweaks.miui.data.http.service.PoemService 8 | import com.tianma.tweaks.miui.data.http.service.ServiceGenerator 9 | import io.reactivex.Observable 10 | 11 | object DataRepository { 12 | 13 | @JvmStatic 14 | fun getHitokoto(categories: List): Observable { 15 | val hitokotoService = ServiceGenerator.instance 16 | .createService(APIConst.HITOKOTO_BASE_URL, HitokotoService::class.java) 17 | return hitokotoService.getHitokoto(categories) 18 | } 19 | 20 | @JvmStatic 21 | fun getPoem(category: String): Observable { 22 | val poemService = ServiceGenerator.instance 23 | .createService(APIConst.POEM_BASE_URL, PoemService::class.java) 24 | return poemService.getPoem(category) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/data/http/service/HitokotoService.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.data.http.service 2 | 3 | import com.tianma.tweaks.miui.data.http.entity.Hitokoto 4 | import io.reactivex.Observable 5 | import retrofit2.http.GET 6 | import retrofit2.http.Query 7 | 8 | /** 9 | * 一言API 10 | */ 11 | interface HitokotoService { 12 | @GET("/") 13 | fun getHitokoto(@Query("c") categories: List): Observable 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/data/http/service/PoemService.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.data.http.service 2 | 3 | import com.tianma.tweaks.miui.data.http.entity.Poem 4 | import io.reactivex.Observable 5 | import retrofit2.http.GET 6 | import retrofit2.http.Path 7 | 8 | /** 9 | * 今日诗词 API 10 | */ 11 | interface PoemService { 12 | @GET("/{category}") 13 | fun getPoem(@Path("category") category: String): Observable 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/data/http/service/ServiceGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.data.http.service 2 | 3 | import android.util.ArrayMap 4 | import okhttp3.OkHttpClient 5 | import retrofit2.Retrofit 6 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 7 | import retrofit2.converter.gson.GsonConverterFactory 8 | import retrofit2.converter.scalars.ScalarsConverterFactory 9 | 10 | /** 11 | * Retrofit Service Generator 12 | */ 13 | class ServiceGenerator private constructor() { 14 | private val mRetrofitMap: ArrayMap = ArrayMap() 15 | private val mOkHttpClient: OkHttpClient = OkHttpClient() 16 | 17 | private object InstanceHolder { 18 | val INSTANCE = ServiceGenerator() 19 | } 20 | 21 | fun createService(baseUrl: String, serviceClass: Class): T { 22 | var retrofit = mRetrofitMap[baseUrl] 23 | if (retrofit == null) { 24 | retrofit = Retrofit.Builder() 25 | .baseUrl(baseUrl) 26 | .client(mOkHttpClient) 27 | .addConverterFactory(ScalarsConverterFactory.create()) 28 | .addConverterFactory(GsonConverterFactory.create()) 29 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 30 | .build() 31 | mRetrofitMap[baseUrl] = retrofit 32 | } 33 | return retrofit!!.create(serviceClass) 34 | } 35 | 36 | companion object { 37 | val instance: ServiceGenerator 38 | get() = InstanceHolder.INSTANCE 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/data/sp/PreferenceContainer.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.data.sp 2 | 3 | import com.tianma.tweaks.miui.cons.AppConst 4 | import com.tianma.tweaks.miui.cons.PrefConst 5 | import com.tianma.tweaks.miui.utils.prefs.PreferenceDelegate 6 | 7 | /** 8 | * desc: App Preference Container 9 | * date: 2021/10/11 10 | */ 11 | object PreferenceContainer { 12 | 13 | /** 14 | * 一言 API 源 15 | */ 16 | var oneSentenceApiSources by pref(PrefConst.ONE_SENTENCE_API_SOURCES, setOf()) 17 | 18 | /** 19 | * 一言 Hitokoto 类别 20 | */ 21 | var hitokotoCategories by pref(PrefConst.HITOKOTO_CATEGORIES, setOf()) 22 | 23 | /** 24 | * 今日诗词类别 25 | */ 26 | var onePoemCategories by pref(PrefConst.ONE_POEM_CATEGORIES, setOf()) 27 | 28 | /** 29 | * 是否显示一言 Hitokoto 来源 30 | */ 31 | var showHitokotoSource by pref(PrefConst.SHOW_HITOKOTO_SOURCE, false) 32 | 33 | /** 34 | * 是否显示今日诗词作者 35 | */ 36 | var showOnePoemAuthor by pref(PrefConst.SHOW_POEM_AUTHOR, false) 37 | 38 | private fun pref(key: String, defaultValue: T): PreferenceDelegate { 39 | return PreferenceDelegate( 40 | key, 41 | defaultValue, 42 | AppConst.XMI_TOOLS_PREFS_NAME 43 | ) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/ContextUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | 7 | object ContextUtils { 8 | 9 | @RequiresApi(api = Build.VERSION_CODES.N) 10 | private fun getProtectedContext(context: Context): Context { 11 | return if (context.isDeviceProtectedStorage) { 12 | context 13 | } else { 14 | context.createDeviceProtectedStorageContext() 15 | } 16 | } 17 | 18 | fun getProtectedContextIfNecessary(context: Context): Context { 19 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 20 | // API >= 24 (Android 7.0+) 21 | // dataDir: /data/user_de/0// 22 | // spDir: /data/user_de/0//shared_prefs/ 23 | // spFile: /data/user_de/0//shared_prefs/.xml 24 | getProtectedContext(context) 25 | } else { 26 | // API < 24, there is no data encrypt. 27 | // dataDir: /data/data// 28 | context 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/DebugHelper.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import android.database.Cursor 4 | import android.net.Uri 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | 9 | object DebugHelper { 10 | fun printViewTree(rootView: View) { 11 | traverseViewTree(rootView, 0) 12 | } 13 | 14 | private fun traverseViewTree(rootView: View, depth: Int) { 15 | var tmpDepth = depth 16 | print(tmpDepth, rootView) 17 | if (rootView is ViewGroup) { 18 | tmpDepth++ 19 | for (i in 0 until rootView.childCount) { 20 | val view = rootView.getChildAt(i) 21 | traverseViewTree(view, tmpDepth) 22 | } 23 | } 24 | } 25 | 26 | private fun print(depth: Int, view: View) { 27 | val sb = StringBuilder() 28 | for (i in 0 until depth) { 29 | sb.append("\t") 30 | } 31 | sb.append("|---") 32 | sb.append(view.javaClass.name) 33 | if (view is TextView) { 34 | sb.append(" (") 35 | .append(view.text) 36 | .append(")") 37 | } 38 | logD("%s", sb.toString()) 39 | } 40 | 41 | fun printCursor(uri: Uri, c: Cursor?) { 42 | logD("%s", uri.toString()) 43 | if (c == null) { 44 | return 45 | } 46 | val hasNext = c.moveToNext() 47 | if (!hasNext) { 48 | return 49 | } 50 | val columnCount = c.columnCount 51 | val columnNames = c.columnNames 52 | val columnTypes = IntArray(columnCount) 53 | for (i in 0 until columnCount) { 54 | columnTypes[i] = c.getType(i) 55 | } 56 | c.moveToPrevious() 57 | while (c.moveToNext()) { 58 | val sb = StringBuilder() 59 | for (i in 0 until columnCount) { 60 | var value: Any? = null 61 | val columnType = columnTypes[i] 62 | if (columnType == Cursor.FIELD_TYPE_INTEGER) { 63 | c.getInt(i) 64 | } 65 | when (columnType) { 66 | Cursor.FIELD_TYPE_INTEGER -> value = c.getInt(i) 67 | Cursor.FIELD_TYPE_BLOB -> value = c.getBlob(i) 68 | Cursor.FIELD_TYPE_FLOAT -> value = c.getFloat(i) 69 | Cursor.FIELD_TYPE_STRING -> value = c.getString(i) 70 | Cursor.FIELD_TYPE_NULL -> { 71 | } 72 | else -> { 73 | } 74 | } 75 | sb.append(columnNames[i]).append(" = ").append(value).append(", ") 76 | } 77 | sb.append("\n") 78 | logD("%s", sb.toString()) 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/ModuleUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | /** 4 | * 当前Xposed模块相关工具类 5 | */ 6 | object ModuleUtils { 7 | /** 8 | * 当前模块是否在Xposed Installer中被启用 9 | */ 10 | @JvmStatic 11 | fun isModuleActive(): Boolean = getModuleVersion() > -1 12 | 13 | /** 14 | * 返回模块版本

15 | * 注意:该方法被本模块Hook住,返回的值是 BuildConfig.MODULE_VERSION,如果没被Hook则返回-1 16 | */ 17 | @JvmStatic 18 | fun getModuleVersion(): Int { 19 | logD("getModuleVersion()") 20 | return -1 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/PackageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.net.Uri 7 | import android.widget.Toast 8 | import androidx.annotation.IntDef 9 | import com.tianma.tweaks.miui.BuildConfig 10 | import com.tianma.tweaks.miui.R 11 | import com.tianma.tweaks.miui.cons.AppConst 12 | import com.tianma.tweaks.miui.xp.hook.systemui.SystemUIHook 13 | 14 | /** 15 | * 包相关工具类 16 | */ 17 | object PackageUtils { 18 | /** 19 | * not installed 20 | */ 21 | const val PACKAGE_NOT_INSTALLED = 0 22 | 23 | /** 24 | * installed & disabled 25 | */ 26 | const val PACKAGE_DISABLED = 1 27 | 28 | /** 29 | * installed & enabled 30 | */ 31 | const val PACKAGE_ENABLED = 2 32 | 33 | @IntDef(PACKAGE_NOT_INSTALLED, PACKAGE_DISABLED, PACKAGE_ENABLED) 34 | annotation class PackageState 35 | 36 | @JvmStatic 37 | @PackageState 38 | fun checkPackageState(context: Context, packageName: String?): Int { 39 | return if (isPackageEnabled(context, packageName)) { 40 | // installed & enabled 41 | PACKAGE_ENABLED 42 | } else { 43 | if (isPackageInstalled(context, packageName)) { 44 | // installed & disabled 45 | PACKAGE_DISABLED 46 | } else { 47 | // not installed 48 | PACKAGE_NOT_INSTALLED 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * 指定的包名对应的App是否已安装 55 | */ 56 | fun isPackageInstalled(context: Context, packageName: String?): Boolean { 57 | val pm = context.packageManager 58 | try { 59 | val packageInfo = pm.getPackageInfo(packageName, 0) 60 | return packageInfo != null 61 | } catch (e: PackageManager.NameNotFoundException) { 62 | // ignore 63 | } 64 | return false 65 | } 66 | 67 | /** 68 | * 对应包名的应用是否已启用 69 | */ 70 | fun isPackageEnabled(context: Context, packageName: String?): Boolean { 71 | val pm = context.packageManager 72 | try { 73 | val appInfo = pm.getApplicationInfo(packageName, 0) 74 | return appInfo != null && appInfo.enabled 75 | } catch (e: PackageManager.NameNotFoundException) { 76 | // ignore 77 | } 78 | return false 79 | } 80 | 81 | private fun checkAlipayExists(context: Context): Boolean { 82 | when (checkPackageState(context, AppConst.ALIPAY_PACKAGE_NAME)) { 83 | PACKAGE_ENABLED -> { 84 | return true 85 | } 86 | PACKAGE_DISABLED -> { 87 | Toast.makeText(context, R.string.alipay_enable_prompt, Toast.LENGTH_SHORT).show() 88 | } 89 | PACKAGE_NOT_INSTALLED -> { 90 | Toast.makeText(context, R.string.alipay_install_prompt, Toast.LENGTH_SHORT).show() 91 | } 92 | } 93 | return false 94 | } 95 | 96 | /** 97 | * 打开支付宝捐赠页 98 | */ 99 | fun startAlipayDonatePage(context: Context?) { 100 | context ?: return 101 | 102 | if (checkAlipayExists(context)) { 103 | val intent = Intent(Intent.ACTION_VIEW) 104 | intent.data = 105 | Uri.parse(AppConst.ALIPAY_QRCODE_URI_PREFIX + AppConst.ALIPAY_QRCODE_URL) 106 | context.startActivity(intent) 107 | } 108 | } 109 | 110 | private fun checkTaiChiExists(context: Context): Boolean { 111 | when (checkPackageState(context, AppConst.TAICHI_PACKAGE_NAME)) { 112 | PACKAGE_ENABLED -> { 113 | // installed & enabled 114 | return true 115 | } 116 | PACKAGE_NOT_INSTALLED -> { 117 | Toast.makeText(context, R.string.taichi_install_prompt, Toast.LENGTH_SHORT).show() 118 | } 119 | PACKAGE_DISABLED -> { 120 | Toast.makeText(context, R.string.taichi_enable_prompt, Toast.LENGTH_SHORT).show() 121 | } 122 | } 123 | return false 124 | } 125 | 126 | /** 127 | * 请求太极勾选本模块 128 | */ 129 | fun startCheckModuleInTaiChi(context: Context) { 130 | if (checkTaiChiExists(context)) { 131 | val intent = Intent("me.weishu.exp.ACTION_MODULE_MANAGE") 132 | intent.data = Uri.parse("package:" + BuildConfig.APPLICATION_ID) 133 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 134 | context.startActivity(intent) 135 | } 136 | } 137 | 138 | /** 139 | * 在太极中勾选本模块相关的应用 140 | */ 141 | fun startAddAppsInTaiChi(context: Context) { 142 | if (checkTaiChiExists(context)) { 143 | val intent = Intent("me.weishu.exp.ACTION_ADD_APP") 144 | val uriStr = "package:" + SystemUIHook.PACKAGE_NAME 145 | // "|" + MiuiLauncherHook.PACKAGE_NAME; 146 | intent.data = Uri.parse(uriStr) 147 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 148 | context.startActivity(intent) 149 | } 150 | } 151 | 152 | /** 153 | * Join QQ group 154 | */ 155 | fun joinQQGroup(context: Context?) { 156 | context ?: return 157 | 158 | val key = AppConst.QQ_GROUP_KEY 159 | val intent = Intent() 160 | intent.data = 161 | Uri.parse("mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26k%3D$key") 162 | // 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面 163 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 164 | try { 165 | context.startActivity(intent) 166 | } catch (e: Exception) { 167 | // 未安装手Q或安装的版本不支持 168 | Toast.makeText(context, R.string.prompt_join_qq_group_failed, Toast.LENGTH_SHORT).show() 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/PreferencesUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.tianma.tweaks.miui.cons.PrefConst 6 | 7 | /** 8 | * Common Shared preferences utils. 9 | */ 10 | object PreferencesUtils { 11 | 12 | private fun getPreferences(context: Context): SharedPreferences { 13 | return context.getSharedPreferences( 14 | PrefConst.SHARED_PREFS_NAME, 15 | Context.MODE_PRIVATE 16 | ) 17 | } 18 | 19 | @JvmStatic 20 | fun contains(context: Context, key: String?): Boolean { 21 | return getPreferences(context).contains(key) 22 | } 23 | 24 | @JvmStatic 25 | fun getString(context: Context, key: String?, defValue: String?): String? { 26 | return getPreferences(context).getString(key, defValue) 27 | } 28 | 29 | @JvmStatic 30 | fun putString(context: Context, key: String?, value: String?) { 31 | getPreferences(context).edit().putString(key, value).apply() 32 | } 33 | 34 | @JvmStatic 35 | fun getBoolean(context: Context, key: String?, defValue: Boolean): Boolean { 36 | return getPreferences(context).getBoolean(key, defValue) 37 | } 38 | 39 | @JvmStatic 40 | fun putBoolean(context: Context, key: String?, value: Boolean) { 41 | getPreferences(context).edit().putBoolean(key, value).apply() 42 | } 43 | 44 | @JvmStatic 45 | fun getInt(context: Context, key: String?, defValue: Int): Int { 46 | return getPreferences(context).getInt(key, defValue) 47 | } 48 | 49 | @JvmStatic 50 | fun putInt(context: Context, key: String?, value: Int) { 51 | getPreferences(context).edit().putInt(key, value).apply() 52 | } 53 | 54 | @JvmStatic 55 | fun getFloat(context: Context, key: String?, defValue: Float): Float { 56 | return getPreferences(context).getFloat(key, defValue) 57 | } 58 | 59 | @JvmStatic 60 | fun putFloat(context: Context, key: String?, value: Float) { 61 | getPreferences(context).edit().putFloat(key, value).apply() 62 | } 63 | 64 | @JvmStatic 65 | fun getLong(context: Context, key: String?, defValue: Long): Long { 66 | return getPreferences(context).getLong(key, defValue) 67 | } 68 | 69 | @JvmStatic 70 | fun putLong(context: Context, key: String?, value: Long) { 71 | getPreferences(context).edit().putLong(key, value).apply() 72 | } 73 | 74 | @JvmStatic 75 | fun putStringSet(context: Context, key: String?, values: Set?) { 76 | getPreferences(context).edit().putStringSet(key, values).apply() 77 | } 78 | 79 | @JvmStatic 80 | fun getStringSet(context: Context, key: String?, defValues: Set): Set? { 81 | return getPreferences(context).getStringSet(key, defValues) 82 | } 83 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/ReflectionExt.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import java.lang.reflect.Field 4 | import java.lang.reflect.Method 5 | 6 | /** 7 | * desc: Extension functions about Xposed. 8 | * date: 2021/8/5 9 | */ 10 | 11 | // 获取指定类的 Field (包括父类的 & 非 public 的) 12 | fun Class<*>.getExactFiled(fieldName: String): Field? = try { 13 | getDeclaredField(fieldName) 14 | } catch (e: NoSuchFieldException) { 15 | superclass.getExactFiled(fieldName) 16 | } 17 | 18 | // 获取对象中指定 Field (包括父类的 & 非 public 的) 的值 19 | operator fun Any?.get(propertyName: String): Any? = when { 20 | this == null -> null 21 | this is Class<*> -> { 22 | try { 23 | this.getExactFiled(propertyName)?.apply { 24 | isAccessible = true 25 | }?.get(null) 26 | } catch (e: NoSuchFieldException) { 27 | null 28 | } 29 | } 30 | else -> { 31 | try { 32 | this::class.java.getExactFiled(propertyName)?.apply { 33 | isAccessible = true 34 | }?.get(this) 35 | } catch (e: NoSuchFieldException) { 36 | null 37 | } 38 | } 39 | } 40 | 41 | // 获取指定类中的指定方法 (包含父类的 & 非 public 的) 42 | fun Class<*>.getExactMethod( 43 | methodName: String, 44 | parameterTypes: Array> = arrayOf(), 45 | ): Method? { 46 | return try { 47 | ReflectionUtils.getDeclaredMethod(this, methodName, *parameterTypes) 48 | } catch (e: NoSuchMethodException) { 49 | superclass.getExactMethod(methodName, parameterTypes) 50 | } 51 | } 52 | 53 | // 调用指定类or对象的指定方法 (包含父类方法 & 非 public 方法) 54 | fun Any?.callMethod( 55 | methodName: String, 56 | parameterTypes: Array> = arrayOf(), 57 | args: Array = arrayOf() 58 | ): Any? { 59 | return when { 60 | this == null -> null 61 | this is Class<*> -> { 62 | try { 63 | val method = this.getExactMethod(methodName, parameterTypes) 64 | ReflectionUtils.invoke(method, null, args) 65 | } catch (e: NoSuchMethodException) { 66 | logE("", e) 67 | null 68 | } 69 | } 70 | else -> { 71 | try { 72 | val method = this::class.java.getExactMethod(methodName, parameterTypes) 73 | ReflectionUtils.invoke(method, this, *args) 74 | } catch (e: NoSuchMethodException) { 75 | logE("", e) 76 | null 77 | } 78 | } 79 | } 80 | } 81 | 82 | 83 | fun Any?.safeAs(): T? { 84 | return this as? T 85 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | 7 | /** 8 | * Utils for reflection 9 | */ 10 | public final class ReflectionUtils { 11 | private ReflectionUtils() { 12 | } 13 | 14 | public static Class getClass(ClassLoader classLoader, String name) { 15 | try { 16 | return Class.forName(name, true, classLoader); 17 | } catch (ClassNotFoundException e) { 18 | throw new RuntimeException(e); 19 | } 20 | } 21 | 22 | public static Field getDeclaredField(Class cls, String fieldName) { 23 | Field field; 24 | try { 25 | field = cls.getDeclaredField(fieldName); 26 | } catch (NoSuchFieldException e) { 27 | throw new RuntimeException(e); 28 | } 29 | field.setAccessible(true); 30 | return field; 31 | } 32 | 33 | public static Field getField(Class cls, String fieldName) { 34 | Field field; 35 | try { 36 | field = cls.getField(fieldName); 37 | } catch (NoSuchFieldException e) { 38 | throw new RuntimeException(e); 39 | } 40 | field.setAccessible(true); 41 | return field; 42 | } 43 | 44 | public static Object getFieldValue(Field field, Object object) { 45 | try { 46 | return field.get(object); 47 | } catch (IllegalAccessException e) { 48 | throw new RuntimeException(e); 49 | } 50 | } 51 | 52 | public static void setFieldValue(Field field, Object object, Object value) { 53 | try { 54 | field.set(object, value); 55 | } catch (IllegalAccessException e) { 56 | throw new RuntimeException(e); 57 | } 58 | } 59 | 60 | public static Method getDeclaredMethod(Class cls, String methodName, Class... paramTypes) throws NoSuchMethodException { 61 | Method method = cls.getDeclaredMethod(methodName, paramTypes); 62 | method.setAccessible(true); 63 | return method; 64 | } 65 | 66 | public static Method getMethod(Class cls, String methodName, Class... paramTypes) throws NoSuchMethodException { 67 | Method method = cls.getMethod(methodName, paramTypes); 68 | method.setAccessible(true); 69 | return method; 70 | } 71 | 72 | public static Object invoke(Method method, Object thisObject, Object... params) { 73 | try { 74 | return method.invoke(thisObject, params); 75 | } catch (InvocationTargetException e) { 76 | Throwable cause = e.getCause(); 77 | if (cause instanceof RuntimeException) { 78 | throw (RuntimeException) cause; 79 | } else { 80 | throw new RuntimeException(e); 81 | } 82 | } catch (IllegalAccessException e) { 83 | throw new RuntimeException(e); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/ResolutionUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import android.content.Context 4 | import android.util.DisplayMetrics 5 | import com.tianma.tweaks.miui.utils.ResolutionUtils 6 | import android.view.WindowManager 7 | 8 | object ResolutionUtils { 9 | private fun getDisplayMetrics(context: Context): DisplayMetrics { 10 | return context.resources.displayMetrics 11 | } 12 | 13 | @JvmStatic 14 | fun dp2px(context: Context, dp: Float): Float { 15 | return dp * getDisplayMetrics(context).density 16 | } 17 | 18 | fun px2dp(context: Context, px: Float): Float { 19 | return px / getDisplayMetrics(context).density 20 | } 21 | 22 | fun getScreenWidth(context: Context): Int { 23 | val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager? 24 | val outMetrics = DisplayMetrics() 25 | return if (wm != null) { 26 | wm.defaultDisplay.getMetrics(outMetrics) 27 | outMetrics.widthPixels 28 | } else { 29 | 0 30 | } 31 | } 32 | 33 | fun getScreenHeight(context: Context): Int { 34 | val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager? 35 | val outMetrics = DisplayMetrics() 36 | return if (wm != null) { 37 | wm.defaultDisplay.getMetrics(outMetrics) 38 | outMetrics.heightPixels 39 | } else { 40 | 0 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/RootUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import com.jaredrummler.android.shell.Shell 4 | import com.tianma.tweaks.miui.utils.RootUtils 5 | import com.tianma.tweaks.miui.xp.hook.systemui.SystemUIHook 6 | import com.tianma.tweaks.miui.xp.hook.launcher.MiuiLauncherHook 7 | 8 | /** 9 | * Utils for root action 10 | */ 11 | object RootUtils { 12 | /** 13 | * Restart SystemUI 14 | */ 15 | fun restartSystemUI() { 16 | killAll(SystemUIHook.PACKAGE_NAME) 17 | } 18 | 19 | /** 20 | * Kill Miui Launcher 21 | */ 22 | fun killAllMiuiLauncher() { 23 | killAll(MiuiLauncherHook.PACKAGE_NAME) 24 | } 25 | 26 | /** 27 | * killall 28 | * 29 | * @param processName process name 30 | */ 31 | private fun killAll(processName: String) { 32 | val cmd = String.format("killall %s", processName) 33 | Shell.SU.run(cmd) 34 | } 35 | 36 | /** 37 | * Reboot 38 | */ 39 | fun reboot() { 40 | Shell.SU.run("reboot") 41 | } 42 | 43 | /** 44 | * Soft Reboot 45 | */ 46 | fun softReboot() { 47 | Shell.SU.run("setprop ctl.restart surfaceflinger; setprop ctl.restart zygote") 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/SPUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import android.content.Context 4 | import com.tianma.tweaks.miui.utils.PreferencesUtils.getLong 5 | import com.tianma.tweaks.miui.utils.PreferencesUtils.putLong 6 | 7 | object SPUtils { 8 | private const val ONE_SENTENCE_LAST_REFRESH_TIME = "one_sentence_last_refresh_time" 9 | 10 | // 存储上次刷新时间 11 | @JvmStatic 12 | fun setOneSentenceLastRefreshTime(context: Context, timestamp: Long) { 13 | putLong(context, ONE_SENTENCE_LAST_REFRESH_TIME, timestamp) 14 | } 15 | 16 | // 获取上次刷新时间 17 | @JvmStatic 18 | fun getOneSentenceLastRefreshTime(context: Context): Long { 19 | return getLong(context, ONE_SENTENCE_LAST_REFRESH_TIME, 0L) 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/StorageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.os.Environment 6 | import androidx.core.content.ContextCompat 7 | import com.tianma.tweaks.miui.BuildConfig 8 | import java.io.File 9 | 10 | /** 11 | * Utils for storage. 12 | */ 13 | object StorageUtils { 14 | 15 | /** 16 | * 是否有SD卡 17 | */ 18 | @JvmStatic 19 | fun isSDCardMounted(): Boolean { 20 | val state = Environment.getExternalStorageState() 21 | return Environment.MEDIA_MOUNTED == state 22 | } 23 | 24 | /** 25 | * Get sdcard directory 26 | * 27 | * @return SD card directory 28 | */ 29 | @JvmStatic 30 | fun getSDCardDir(): File = Environment.getExternalStorageDirectory() 31 | 32 | /** 33 | * get sdcard public documents directory 34 | * 35 | * @return SD card public documents directory 36 | */ 37 | @JvmStatic 38 | fun getPublicDocumentsDir(): File = 39 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) 40 | 41 | 42 | @JvmStatic 43 | fun getSharedPreferencesFile(context: Context?, preferencesName: String): File { 44 | val dataDir = ContextCompat.getDataDir(context!!) 45 | val prefsDir = File(dataDir, "shared_prefs") 46 | return File(prefsDir, "$preferencesName.xml") 47 | } 48 | 49 | /** 50 | * Get internal data dir. /data/data// 51 | * */ 52 | @JvmStatic 53 | fun getInternalDataDir(): File { 54 | return File(Environment.getDataDirectory(), "data/" + BuildConfig.APPLICATION_ID) 55 | } 56 | 57 | /** 58 | * Get internal files dir. /data/data//files/ 59 | * 60 | * @return 61 | * */ 62 | @JvmStatic 63 | fun getInternalFilesDir(): File { 64 | return File(getInternalDataDir(), "files") 65 | } 66 | 67 | /** 68 | * Get external files dir. /sdcard/Android/data//files/ 69 | * */ 70 | @JvmStatic 71 | fun getExternalFilesDir(): File { 72 | return File( 73 | Environment.getExternalStorageDirectory(), 74 | "Android/data/" + BuildConfig.APPLICATION_ID + "/files/" 75 | ) 76 | } 77 | 78 | /** 79 | * Get files dir 80 | * 81 | * @see StorageUtils.getExternalFilesDir 82 | * @see StorageUtils.getInternalFilesDir 83 | */ 84 | @JvmStatic 85 | fun getFilesDir(): File { 86 | return if (isSDCardMounted()) { 87 | val externalFilesDir = getExternalFilesDir() 88 | if (!externalFilesDir.exists()) { 89 | externalFilesDir.mkdirs() 90 | } 91 | externalFilesDir 92 | } else { 93 | getInternalFilesDir() 94 | } 95 | } 96 | 97 | /** 98 | * Set file world writable 99 | */ 100 | @JvmStatic 101 | @SuppressLint("SetWorldWritable", "SetWorldReadable") 102 | fun setFileWorldWritable(file: File?, parentDepth: Int) { 103 | file ?: return 104 | if (!file.exists()) { 105 | return 106 | } 107 | var tempFile: File? = file 108 | val tempDepth = parentDepth + 1 109 | for (i in 0 until tempDepth) { 110 | tempFile?.setExecutable(true, false) 111 | tempFile?.setWritable(true, false) 112 | tempFile?.setReadable(true, false) 113 | tempFile = tempFile?.parentFile 114 | if (tempFile == null) { 115 | break 116 | } 117 | } 118 | } 119 | 120 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import androidx.browser.customtabs.CustomTabsIntent 6 | import android.widget.Toast 7 | import com.tianma.tweaks.miui.R 8 | import java.lang.Exception 9 | 10 | /** 11 | * Other Utils 12 | */ 13 | object Utils { 14 | fun showWebPage(context: Context?, url: String?) { 15 | try { 16 | val cti = CustomTabsIntent.Builder().build() 17 | cti.launchUrl(context!!, Uri.parse(url)) 18 | } catch (e: Exception) { 19 | Toast.makeText(context, R.string.browser_install_or_enable_prompt, Toast.LENGTH_SHORT) 20 | .show() 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/XLog.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils 2 | 3 | import android.util.Log 4 | import com.tianma.tweaks.miui.BuildConfig 5 | 6 | /** 7 | * desc: Log util 8 | * date: 2021/8/6 9 | */ 10 | 11 | private const val LOG_TAG: String = BuildConfig.LOG_TAG 12 | private const val LOG_LEVEL = BuildConfig.LOG_LEVEL 13 | private const val LOG_TO_XPOSED = BuildConfig.LOG_TO_XPOSED 14 | private const val LOG_TO_EDXPOSED = BuildConfig.LOG_TO_EDXPOSED 15 | 16 | private fun log(priority: Int, message: String, vararg args: Any) { 17 | 18 | var targetPriority = priority 19 | var msg = message 20 | if (targetPriority < LOG_LEVEL) return 21 | 22 | if (args.isNotEmpty()) { 23 | msg = String.format(msg, *args) 24 | } 25 | 26 | if (args.isNotEmpty() && args[args.size - 1] is Throwable) { 27 | val throwable = args[args.size - 1] as Throwable 28 | val stacktraceStr = Log.getStackTraceString(throwable) 29 | msg += "\n${stacktraceStr}" 30 | } 31 | 32 | // Write to the default log tag 33 | Log.println(targetPriority, LOG_TAG, msg) 34 | 35 | // Duplicate to the Xposed log if enabled 36 | if (LOG_TO_XPOSED) { 37 | if (targetPriority <= Log.DEBUG) { // DEBUG level 不会在 Xposed 日志中生成,所以调整等级 38 | targetPriority = Log.INFO 39 | } 40 | Log.println(targetPriority, "Xposed", "$LOG_TAG: $msg") 41 | } 42 | if (LOG_TO_EDXPOSED) { 43 | if (targetPriority <= Log.DEBUG) { // DEBUG level 不会在 EdXposed 日志中生成,所以调整等级 44 | targetPriority = Log.INFO 45 | } 46 | // EdXposed 47 | Log.println(targetPriority, "EdXposed-Bridge", "$LOG_TAG: $msg") 48 | // LSPosed 49 | Log.println(targetPriority, "LSPosed-Bridge", "$LOG_TAG: $msg") 50 | } 51 | } 52 | 53 | fun logV(message: String, vararg args: Any) { 54 | log(Log.VERBOSE, message, *args) 55 | } 56 | 57 | fun logD(message: String, vararg args: Any) { 58 | log(Log.DEBUG, message, *args) 59 | } 60 | 61 | fun logI(message: String, vararg args: Any) { 62 | log(Log.INFO, message, *args) 63 | } 64 | 65 | fun logW(message: String, vararg args: Any) { 66 | log(Log.WARN, message, *args) 67 | } 68 | 69 | fun logE(message: String, vararg args: Any) { 70 | log(Log.ERROR, message, *args) 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/prefs/PreferenceDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils.prefs 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import androidx.core.content.edit 6 | import com.tianma.tweaks.miui.app.App 7 | import com.tianma.tweaks.miui.utils.ContextUtils 8 | import com.tianma.tweaks.miui.utils.safeAs 9 | import kotlin.properties.ReadWriteProperty 10 | import kotlin.reflect.KProperty 11 | 12 | /** 13 | * desc: Preference 的属性代理 14 | * date: 6/7/21 15 | */ 16 | class PreferenceDelegate( 17 | private val key: String, 18 | private val defaultValue: T, 19 | private val prefName: String = "default" 20 | ) : ReadWriteProperty { 21 | 22 | private val sharedPrefs: SharedPreferences by lazy { 23 | val context = ContextUtils.getProtectedContextIfNecessary(App.appContext) 24 | context.getSharedPreferences(prefName, Context.MODE_PRIVATE) 25 | } 26 | 27 | override fun getValue(thisRef: Any?, property: KProperty<*>): T { 28 | return findPreference(findPreferenceKey(property)) 29 | } 30 | 31 | private fun findPreferenceKey(property: KProperty<*>): String { 32 | return if (key.isEmpty()) { 33 | property.name 34 | } else { 35 | key 36 | } 37 | } 38 | 39 | @Suppress("UNCHECKED_CAST") 40 | private fun findPreference(key: String): T { 41 | return when (defaultValue) { 42 | is Int -> sharedPrefs.getInt(key, defaultValue) 43 | is String -> sharedPrefs.getString(key, defaultValue) 44 | is Boolean -> sharedPrefs.getBoolean(key, defaultValue) 45 | is Long -> sharedPrefs.getLong(key, defaultValue) 46 | is Float -> sharedPrefs.getFloat(key, defaultValue) 47 | is Set<*> -> sharedPrefs.getStringSet(key, defaultValue.safeAs()).safeAs() 48 | else -> throw IllegalArgumentException("Unsupported type. $key $defaultValue") 49 | } as T 50 | } 51 | 52 | override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 53 | putPreference(findPreferenceKey(property), value) 54 | } 55 | 56 | private fun putPreference(key: String, value: T) { 57 | sharedPrefs.edit { 58 | when (value) { 59 | is Long -> putLong(key, value) 60 | is Int -> putInt(key, value) 61 | is Boolean -> putBoolean(key, value) 62 | is String -> putString(key, value) 63 | is Float -> putFloat(key, value) 64 | is Set<*> -> putStringSet(key, value.safeAs()) 65 | else -> throw IllegalArgumentException("Unsupported type. $key $defaultValue") 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/prefs/XPreferenceDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils.prefs 2 | 3 | import android.os.Build 4 | import androidx.core.content.edit 5 | import com.tianma.tweaks.miui.utils.logD 6 | import com.tianma.tweaks.miui.utils.logE 7 | import com.tianma.tweaks.miui.utils.safeAs 8 | import de.robv.android.xposed.XSharedPreferences 9 | import java.io.File 10 | import kotlin.properties.ReadWriteProperty 11 | import kotlin.reflect.KProperty 12 | 13 | /** 14 | * desc: XSharedPreference 的属性代理 15 | * date: 6/7/21 16 | */ 17 | class XPreferenceDelegate( 18 | private val key: String, 19 | private val defaultValue: T, 20 | private val packageName: String, 21 | private val prefFileName: String 22 | ) : ReadWriteProperty { 23 | 24 | private val sharedPrefs: XSharedPreferences by lazy { 25 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // Android 7.0+ 26 | val prefFile = File("/data/user_de/0/${packageName}/shared_prefs/${prefFileName}.xml") 27 | XSharedPreferences(prefFile) 28 | } else { 29 | XSharedPreferences(packageName, prefFileName) 30 | }.also { xsp -> 31 | try { 32 | xsp.makeWorldReadable() 33 | } catch (t: Throwable) { 34 | logE("", t) 35 | } 36 | } 37 | } 38 | 39 | override fun getValue(thisRef: Any?, property: KProperty<*>): T { 40 | return findPreference(findPreferenceKey(property)) 41 | } 42 | 43 | private fun findPreferenceKey(property: KProperty<*>): String { 44 | return if (key.isEmpty()) { 45 | property.name 46 | } else { 47 | key 48 | } 49 | } 50 | 51 | @Suppress("UNCHECKED_CAST") 52 | private fun findPreference(key: String): T { 53 | return (when (defaultValue) { 54 | is Int -> sharedPrefs.getInt(key, defaultValue) 55 | is String -> sharedPrefs.getString(key, defaultValue) 56 | is Boolean -> sharedPrefs.getBoolean(key, defaultValue) 57 | is Long -> sharedPrefs.getLong(key, defaultValue) 58 | is Float -> sharedPrefs.getFloat(key, defaultValue) 59 | is Set<*> -> sharedPrefs.getStringSet(key, defaultValue.safeAs()).safeAs() 60 | else -> throw IllegalArgumentException("Unsupported type. $key $defaultValue") 61 | } as T).also { 62 | } 63 | } 64 | 65 | override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 66 | putPreference(findPreferenceKey(property), value) 67 | } 68 | 69 | private fun putPreference(key: String, value: T) { 70 | sharedPrefs.edit { 71 | when (value) { 72 | is Long -> putLong(key, value) 73 | is Int -> putInt(key, value) 74 | is Boolean -> putBoolean(key, value) 75 | is String -> putString(key, value) 76 | is Float -> putFloat(key, value) 77 | is Set<*> -> putStringSet(key, value.safeAs()) 78 | else -> throw IllegalArgumentException("Unsupported type. $key $defaultValue") 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/rom/MiuiUtils.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils.rom; 2 | 3 | import android.os.Build; 4 | import android.text.TextUtils; 5 | 6 | import com.tianma.tweaks.miui.utils.XLogKt; 7 | 8 | /** 9 | * MIUI Rom 相关工具类 10 | */ 11 | public class MiuiUtils { 12 | 13 | private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code"; 14 | private static final String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name"; 15 | private static final String KEY_VERSION_CODE_TIME = "ro.miui.version.code_time"; 16 | 17 | private MiuiUtils() { 18 | } 19 | 20 | /** 21 | * 判断当前Rom是否是MIUI 22 | */ 23 | public static boolean isMiui() { 24 | return !TextUtils.isEmpty(getMiuiVersionName()) || getMiuiVersionCode() != -1; 25 | } 26 | 27 | /** 28 | * 获取MIUI版本名(v10, v9 之类) 29 | */ 30 | public static String getMiuiVersionName() { 31 | return RomUtils.getSystemProperty(KEY_MIUI_VERSION_NAME); 32 | } 33 | 34 | /** 35 | * 获取 MIUI 大版本,MIUI v10 返回 10, MIUI v9 返回 9, ro.miui.ui.version.code 键不存在,否则返回 -1 36 | */ 37 | public static int getMiuiVersionCode() { 38 | String versionCode = RomUtils.getSystemProperty(KEY_MIUI_VERSION_CODE); 39 | if (!TextUtils.isEmpty(versionCode)) { 40 | try { 41 | return Integer.parseInt(versionCode) + 2; 42 | } catch (Exception e) { 43 | XLogKt.logE("get MIUI version code failed: %s", versionCode, e); 44 | } 45 | } 46 | return -1; 47 | } 48 | 49 | /** 50 | * 获取 MIUI 小版本,比如 9.5.9, 8.12.27 之类的,代表构建日期 51 | * @return 52 | */ 53 | public static String getMiuiVersionIncremental() { 54 | return Build.VERSION.INCREMENTAL; 55 | } 56 | 57 | /** 58 | * 获取当前MIUI版本构建时间(单位ms) 59 | * @return 60 | */ 61 | private static long getMiuiVersionCodeTime() { 62 | String versionCodeTime = RomUtils.getSystemProperty(KEY_VERSION_CODE_TIME); 63 | if (!TextUtils.isEmpty(versionCodeTime)) { 64 | try { 65 | return Long.parseLong(versionCodeTime) * 1000; 66 | } catch (Exception e) { 67 | XLogKt.logE("get MIUI version code time failed: %s", versionCodeTime, e); 68 | } 69 | } 70 | return 0L; 71 | } 72 | 73 | public static MiuiVersion getMiuiVersion() { 74 | return new MiuiVersion(getMiuiVersionCodeTime()); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/rom/MiuiVersion.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils.rom; 2 | 3 | import com.tianma.tweaks.miui.utils.XLogKt; 4 | 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | import java.util.Locale; 8 | 9 | public class MiuiVersion { 10 | 11 | public final static MiuiVersion V_19_5_7 = new MiuiVersion("19.5.7"); 12 | 13 | private static final String DATE_FORMAT = "yy.M.d"; 14 | 15 | private long time; 16 | 17 | public MiuiVersion(String timeStr) { 18 | SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT, Locale.getDefault()); 19 | try { 20 | time = sdf.parse(timeStr).getTime(); 21 | } catch (Exception e) { 22 | XLogKt.logE("time format error %s", timeStr, e); 23 | } 24 | } 25 | 26 | public MiuiVersion(long time) { 27 | this.time = time; 28 | } 29 | 30 | public long getTime() { 31 | return time; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "MiuiVersion{" + 37 | "time=" + new SimpleDateFormat(DATE_FORMAT, Locale.getDefault()).format(new Date(time)) + 38 | '}'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/utils/rom/RomUtils.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.utils.rom; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | 7 | /** 8 | * Rom 相关工具类 9 | */ 10 | public class RomUtils { 11 | 12 | private RomUtils() { 13 | } 14 | 15 | public static String getSystemProperty(String propertyName) { 16 | String result = null; 17 | BufferedReader br = null; 18 | try { 19 | Process process = Runtime.getRuntime().exec("getprop " + propertyName); 20 | br = new BufferedReader(new InputStreamReader(process.getInputStream()), 1024); 21 | result = br.readLine(); 22 | } catch (Exception e) { 23 | e.printStackTrace(); 24 | } finally { 25 | if (br != null) { 26 | try { 27 | br.close(); 28 | } catch (IOException e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | } 33 | return result; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/HookEntry.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp 2 | 3 | import com.tianma.tweaks.miui.xp.hook.BaseHook 4 | import com.tianma.tweaks.miui.xp.hook.self.ModuleUtilsHook 5 | import com.tianma.tweaks.miui.xp.hook.systemui.SystemUIHook 6 | import de.robv.android.xposed.IXposedHookLoadPackage 7 | import de.robv.android.xposed.IXposedHookZygoteInit 8 | import de.robv.android.xposed.IXposedHookZygoteInit.StartupParam 9 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam 10 | 11 | class HookEntry : IXposedHookLoadPackage, IXposedHookZygoteInit { 12 | 13 | private val hookList: List by lazy { 14 | mutableListOf().apply { 15 | add(ModuleUtilsHook()) // Self Hook 16 | add(SystemUIHook()) // SystemUI Hook 17 | // add(new MiuiLauncherHook()); // Miui Launcher Hook 18 | } 19 | } 20 | 21 | @Throws(Throwable::class) 22 | override fun initZygote(startupParam: StartupParam?) { 23 | startupParam ?: return 24 | for (hook in hookList) { 25 | if (hook.shouldHookInitZygote()) { 26 | hook.initZygote(startupParam) 27 | } 28 | } 29 | } 30 | 31 | @Throws(Throwable::class) 32 | override fun handleLoadPackage(lpparam: LoadPackageParam?) { 33 | lpparam ?: return 34 | for (hook in hookList) { 35 | if (hook.shouldHookOnLoadPackage()) { 36 | hook.onLoadPackage(lpparam) 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/BaseHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook 2 | 3 | import de.robv.android.xposed.IXposedHookZygoteInit.StartupParam 4 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam 5 | 6 | open class BaseHook : IHook { 7 | @Throws(Throwable::class) 8 | override fun initZygote(startupParam: StartupParam) { 9 | } 10 | 11 | open fun shouldHookInitZygote(): Boolean { 12 | return false 13 | } 14 | 15 | @Throws(Throwable::class) 16 | override fun onLoadPackage(lpparam: LoadPackageParam) { 17 | } 18 | 19 | open fun shouldHookOnLoadPackage(): Boolean { 20 | return true 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/BaseSubHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook 2 | 3 | import kotlin.jvm.JvmOverloads 4 | import com.tianma.tweaks.miui.utils.rom.MiuiVersion 5 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo 6 | 7 | abstract class BaseSubHook @JvmOverloads constructor( 8 | val mClassLoader: ClassLoader?, 9 | val mAppInfo: AppInfo? = null, 10 | val mMiuiVersion: MiuiVersion? = null 11 | ) { 12 | 13 | abstract fun startHook() 14 | 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/IHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook 2 | 3 | import kotlin.Throws 4 | import de.robv.android.xposed.IXposedHookZygoteInit.StartupParam 5 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam 6 | 7 | interface IHook { 8 | @Throws(Throwable::class) 9 | fun initZygote(startupParam: StartupParam) 10 | 11 | @Throws(Throwable::class) 12 | fun onLoadPackage(lpparam: LoadPackageParam) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/launcher/MiuiLauncherHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.launcher 2 | 3 | import com.tianma.tweaks.miui.data.sp.XPrefContainer.mainSwitchEnable 4 | import com.tianma.tweaks.miui.utils.logI 5 | import com.tianma.tweaks.miui.utils.rom.MiuiUtils 6 | import com.tianma.tweaks.miui.xp.hook.BaseHook 7 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam 8 | 9 | class MiuiLauncherHook : BaseHook() { 10 | 11 | companion object { 12 | const val PACKAGE_NAME = "com.miui.home" 13 | } 14 | 15 | @Throws(Throwable::class) 16 | override fun onLoadPackage(lpparam: LoadPackageParam) { 17 | if (PACKAGE_NAME == lpparam.packageName) { 18 | logI("Hooking MIUI Launcher...") 19 | val classLoader = lpparam.classLoader 20 | if (mainSwitchEnable) { 21 | if (!MiuiUtils.isMiui()) { 22 | return 23 | } 24 | WorkSpaceHook(classLoader).startHook() 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/launcher/WorkSpaceHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.launcher 2 | 3 | import com.tianma.tweaks.miui.data.sp.XPrefContainer 4 | import com.tianma.tweaks.miui.utils.logD 5 | import com.tianma.tweaks.miui.utils.logE 6 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook 7 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper 8 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 9 | 10 | class WorkSpaceHook(classLoader: ClassLoader?) : BaseSubHook(classLoader) { 11 | 12 | companion object { 13 | private const val CLASS_WORK_SPACE = "com.miui.home.launcher.Workspace" 14 | } 15 | 16 | private val mAlwaysShowStatusBarClock: Boolean = XPrefContainer.alwaysShowStatusBarClock 17 | 18 | override fun startHook() { 19 | try { 20 | logD("Hooking WorkSpace...") 21 | if (mAlwaysShowStatusBarClock) { 22 | hookIsScreenHasClockGadgets() 23 | } 24 | } catch (t: Throwable) { 25 | logE("Error occurs when hook WorkSpace", t) 26 | } 27 | } 28 | 29 | // #isScreenHasClockGadget() 30 | private fun hookIsScreenHasClockGadgets() { 31 | XposedWrapper.findAndHookMethod( 32 | CLASS_WORK_SPACE, 33 | mClassLoader, 34 | "isScreenHasClockGadget", 35 | Long::class.javaPrimitiveType, 36 | object : MethodHookWrapper() { 37 | override fun before(param: MethodHookParam) { 38 | param.result = false 39 | } 40 | }) 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/self/ModuleUtilsHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.self 2 | 3 | import com.tianma.tweaks.miui.BuildConfig 4 | import com.tianma.tweaks.miui.utils.ModuleUtils 5 | import com.tianma.tweaks.miui.utils.logE 6 | import com.tianma.tweaks.miui.utils.logI 7 | import com.tianma.tweaks.miui.xp.hook.BaseHook 8 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 9 | import de.robv.android.xposed.XC_MethodReplacement 10 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam 11 | 12 | /** 13 | * Hook class ModuleUtils 14 | */ 15 | class ModuleUtilsHook : BaseHook() { 16 | 17 | companion object { 18 | private const val MI_TWEAKS_PACKAGE = BuildConfig.APPLICATION_ID 19 | private const val MODULE_VERSION = BuildConfig.MODULE_VERSION 20 | } 21 | 22 | @Throws(Throwable::class) 23 | override fun onLoadPackage(lpparam: LoadPackageParam) { 24 | if (MI_TWEAKS_PACKAGE == lpparam.packageName) { 25 | try { 26 | logI("Hooking current Xposed module status...") 27 | hookModuleUtils(lpparam) 28 | } catch (e: Throwable) { 29 | logE("Failed to hook current Xposed module status.") 30 | } 31 | } 32 | } 33 | 34 | @Throws(Throwable::class) 35 | private fun hookModuleUtils(lpparam: LoadPackageParam) { 36 | val className = ModuleUtils::class.java.name 37 | XposedWrapper.findAndHookMethod( 38 | className, lpparam.classLoader, 39 | "getModuleVersion", 40 | XC_MethodReplacement.returnConstant(MODULE_VERSION) 41 | ) 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/helper/ResHelpers.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.helper; 2 | 3 | import android.content.res.Resources; 4 | import android.util.ArrayMap; 5 | 6 | import com.tianma.tweaks.miui.xp.hook.systemui.SystemUIHook; 7 | 8 | public class ResHelpers { 9 | 10 | private static ArrayMap sNameIdMap; 11 | 12 | static { 13 | sNameIdMap = new ArrayMap<>(); 14 | } 15 | 16 | public static Integer getId(Resources res, String name) { 17 | if (!sNameIdMap.containsKey(name)) { 18 | int id = res.getIdentifier(name, "id", SystemUIHook.PACKAGE_NAME); 19 | sNameIdMap.put(name, id); 20 | } 21 | return sNameIdMap.get(name); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/keyguard/def/Ease.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.keyguard.def; 2 | 3 | import android.animation.TimeInterpolator; 4 | 5 | public class Ease { 6 | 7 | public static class Cubic { 8 | public static final TimeInterpolator easeIn = new TimeInterpolator() { 9 | public float getInterpolation(float f) { 10 | float f2 = f / 1.0f; 11 | f = f2; 12 | return (((1.0f * f2) * f) * f) + 0.0f; 13 | } 14 | }; 15 | public static final TimeInterpolator easeInOut = new TimeInterpolator() { 16 | public float getInterpolation(float f) { 17 | float f2 = f / 0.5f; 18 | f = f2; 19 | if (f2 < 1.0f) { 20 | return (((0.5f * f) * f) * f) + 0.0f; 21 | } 22 | float f3 = f - 2.0f; 23 | f = f3; 24 | return (0.5f * (((f3 * f) * f) + 2.0f)) + 0.0f; 25 | } 26 | }; 27 | public static final TimeInterpolator easeOut = new TimeInterpolator() { 28 | public float getInterpolation(float f) { 29 | float f2 = (f / 1.0f) - 1.0f; 30 | f = f2; 31 | return (1.0f * (((f2 * f) * f) + 1.0f)) + 0.0f; 32 | } 33 | }; 34 | } 35 | 36 | public static class Quint { 37 | public static final TimeInterpolator easeIn = new TimeInterpolator() { 38 | public float getInterpolation(float f) { 39 | float f2 = f / 1.0f; 40 | f = f2; 41 | return (((((1.0f * f2) * f) * f) * f) * f) + 0.0f; 42 | } 43 | }; 44 | public static final TimeInterpolator easeInOut = new TimeInterpolator() { 45 | public float getInterpolation(float f) { 46 | float f2 = f / 0.5f; 47 | f = f2; 48 | if (f2 < 1.0f) { 49 | return (((((0.5f * f) * f) * f) * f) * f) + 0.0f; 50 | } 51 | float f3 = f - 2.0f; 52 | f = f3; 53 | return (0.5f * (((((f3 * f) * f) * f) * f) + 2.0f)) + 0.0f; 54 | } 55 | }; 56 | public static final TimeInterpolator easeOut = new TimeInterpolator() { 57 | public float getInterpolation(float f) { 58 | float f2 = (f / 1.0f) - 1.0f; 59 | f = f2; 60 | return (1.0f * (((((f2 * f) * f) * f) * f) + 1.0f)) + 0.0f; 61 | } 62 | }; 63 | } 64 | 65 | public static class Sine { 66 | public static final TimeInterpolator easeIn = new TimeInterpolator() { 67 | public float getInterpolation(float f) { 68 | return ((-1.0f * ((float) Math.cos(((double) (f / 1.0f)) * 1.5707963267948966d))) + 1.0f) + 0.0f; 69 | } 70 | }; 71 | public static final TimeInterpolator easeInOut = new TimeInterpolator() { 72 | public float getInterpolation(float f) { 73 | return (-0.5f * (((float) Math.cos((3.141592653589793d * ((double) f)) / 1.0d)) - 1.0f)) + 0.0f; 74 | } 75 | }; 76 | public static final TimeInterpolator easeOut = new TimeInterpolator() { 77 | public float getInterpolation(float f) { 78 | return (1.0f * ((float) Math.sin(((double) (f / 1.0f)) * 1.5707963267948966d))) + 0.0f; 79 | } 80 | }; 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/keyguard/def/KeyguardClockContainerHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.keyguard.def 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.IntentFilter 5 | import android.os.UserHandle 6 | import android.widget.FrameLayout 7 | import com.tianma.tweaks.miui.data.sp.XPrefContainer 8 | import com.tianma.tweaks.miui.utils.logD 9 | import com.tianma.tweaks.miui.utils.logE 10 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook 11 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo 12 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppVersionConst 13 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper 14 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 15 | import de.robv.android.xposed.XposedHelpers 16 | 17 | /** 18 | * 锁屏时钟容器 Hook 19 | * 适用版本 9.4.x 20 | */ 21 | class KeyguardClockContainerHook(classLoader: ClassLoader?, appInfo: AppInfo?) : 22 | BaseSubHook(classLoader, appInfo) { 23 | 24 | companion object { 25 | private const val CLASS_KEYGUARD_CLOCK_CONTAINER_OLD = 26 | "com.android.keyguard.KeyguardClockContainer" 27 | 28 | private const val CLASS_KEYGUARD_CLOCK_CONTAINER_NEW = 29 | "com.android.keyguard.clock.KeyguardClockContainer" 30 | 31 | private const val CLASS_DEPENDENCY = "com.android.systemui.Dependency" 32 | } 33 | 34 | private val mShowHorizontalSec: Boolean = XPrefContainer.showSecInKeyguardHorizontal 35 | private val mShowVerticalSec: Boolean = XPrefContainer.showSecInKeyguardVertical 36 | 37 | private var mKeyguardClockContainerClass: Class<*>? = null 38 | 39 | override fun startHook() { 40 | if (!mShowHorizontalSec && !mShowVerticalSec) { 41 | return 42 | } 43 | try { 44 | logD("Hooking KeyguardClockContainerHook...") 45 | mKeyguardClockContainerClass = 46 | if (mAppInfo!!.versionCode >= AppVersionConst.SYSTEM_UI_V201912130) { 47 | XposedHelpers.findClass(CLASS_KEYGUARD_CLOCK_CONTAINER_NEW, mClassLoader) 48 | } else { 49 | XposedHelpers.findClass(CLASS_KEYGUARD_CLOCK_CONTAINER_OLD, mClassLoader) 50 | } 51 | hookOnAttachedToWindow() 52 | } catch (t: Throwable) { 53 | logE("Error occurs when hook KeyguardClockContainerHook", t) 54 | } 55 | } 56 | 57 | // com.android.keyguard.KeyguardClockContainer#onAttachedToWindow() 58 | private fun hookOnAttachedToWindow() { 59 | XposedWrapper.findAndHookMethod(mKeyguardClockContainerClass, 60 | "onAttachedToWindow", 61 | object : MethodHookWrapper() { 62 | override fun before(param: MethodHookParam) { 63 | val clockContainer = param.thisObject as FrameLayout 64 | val parent = clockContainer.parent 65 | XposedHelpers.callMethod(parent, "onAttachedToWindow") 66 | 67 | val filter = IntentFilter() 68 | // 目的: 取消注册 TIME_TICK 事件 69 | // filter.addAction("android.intent.action.TIME_TICK"); 70 | filter.addAction("android.intent.action.TIME_SET") 71 | filter.addAction("android.intent.action.TIMEZONE_CHANGED") 72 | 73 | val mIntentReceiver = XposedHelpers.getObjectField( 74 | clockContainer, 75 | "mIntentReceiver" 76 | ) as BroadcastReceiver 77 | 78 | val userHandleAll = XposedHelpers.getStaticObjectField(UserHandle::class.java, "ALL") 79 | val dependencyClass = XposedHelpers.findClass(CLASS_DEPENDENCY, mClassLoader) 80 | val timeTickHandler = XposedHelpers.getStaticObjectField(dependencyClass, "TIME_TICK_HANDLER") 81 | val handler = 82 | XposedHelpers.callStaticMethod(dependencyClass, "get", timeTickHandler) 83 | 84 | XposedHelpers.callMethod( 85 | clockContainer.context, 86 | "registerReceiverAsUser", 87 | mIntentReceiver, 88 | userHandleAll, 89 | filter, 90 | null, 91 | handler 92 | ) 93 | 94 | XposedHelpers.callMethod(clockContainer, "registerDualClockObserver") 95 | XposedHelpers.callMethod(clockContainer, "registerClockPositionObserver") 96 | 97 | param.result = null 98 | } 99 | }) 100 | } 101 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/keyguard/v20190507/ChooseKeyguardClockActivityHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.keyguard.v20190507 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import com.tianma.tweaks.miui.data.sp.XPrefContainer 6 | import com.tianma.tweaks.miui.utils.logD 7 | import com.tianma.tweaks.miui.utils.logE 8 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook 9 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.IntentAction 10 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo 11 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper 12 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 13 | 14 | /** 15 | * MIUI设置页面 - 选择锁屏时钟界面 Hook 16 | * 适用版本 9.5.7+ 17 | */ 18 | class ChooseKeyguardClockActivityHook(classLoader: ClassLoader?, appInfo: AppInfo?) : 19 | BaseSubHook(classLoader, appInfo) { 20 | 21 | companion object { 22 | private const val CLASS_CHOOSE_KEYGUARD_CLOCK_ACTIVITY = 23 | "com.android.keyguard.settings.ChooseKeyguardClockActivity" 24 | } 25 | 26 | private val mShowVerticalSec: Boolean = XPrefContainer.showSecInKeyguardVertical 27 | private val mShowHorizontalSec: Boolean = XPrefContainer.showSecInKeyguardHorizontal 28 | 29 | override fun startHook() { 30 | if (!mShowHorizontalSec && !mShowVerticalSec) { 31 | return 32 | } 33 | try { 34 | logD("Hooking ChooseKeyguardClockActivity...") 35 | hookOnStop() 36 | } catch (t: Throwable) { 37 | logE("Error occurs when hook ChooseKeyguardClockActivity", t) 38 | } 39 | } 40 | 41 | // com.android.keyguard.setting.ChooseKeyguardClockActivity#onStop() 42 | private fun hookOnStop() { 43 | XposedWrapper.findAndHookMethod( 44 | CLASS_CHOOSE_KEYGUARD_CLOCK_ACTIVITY, 45 | mClassLoader, 46 | "onStop", 47 | object : MethodHookWrapper() { 48 | override fun before(param: MethodHookParam) { 49 | val context = param.thisObject as Context 50 | val intent = Intent(IntentAction.KEYGUARD_STOP_TIME_TICK) 51 | context.sendBroadcast(intent) 52 | } 53 | }) 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/keyguard/v20191213/MiuiCenterHorizontalClockHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.keyguard.v20191213 2 | 3 | import android.view.View 4 | import android.view.ViewTreeObserver.OnWindowAttachListener 5 | import android.widget.TextView 6 | import com.tianma.tweaks.miui.data.sp.XPrefContainer 7 | import com.tianma.tweaks.miui.utils.logD 8 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook 9 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.ScreenBroadcastManager 10 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.SimpleScreenListener 11 | import com.tianma.tweaks.miui.xp.hook.systemui.tick.TickObserver 12 | import com.tianma.tweaks.miui.xp.hook.systemui.tick.TimeTicker 13 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo 14 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper 15 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 16 | import de.robv.android.xposed.XposedHelpers 17 | import java.util.* 18 | 19 | /** 20 | * 锁屏居中水平时钟 21 | * 适用版本 20.4.27+ 22 | */ 23 | class MiuiCenterHorizontalClockHook(classLoader: ClassLoader?, appInfo: AppInfo?) : BaseSubHook(classLoader, appInfo), TickObserver { 24 | 25 | companion object { 26 | const val CLASS_MIUI_CENTER_HORIZONTAL_CLOCK = "miui.keyguard.clock.MiuiCenterHorizontalClock" 27 | } 28 | 29 | private var centerHorizontalClockClass: Class<*>? = null 30 | 31 | private val clockList = mutableListOf() 32 | 33 | private val showHorizontalSec = XPrefContainer.showSecInKeyguardHorizontal 34 | 35 | override fun startHook() { 36 | if (showHorizontalSec) { 37 | logD("Hooking MiuiCenterHorizontalClock...") 38 | centerHorizontalClockClass = XposedWrapper.findClass(CLASS_MIUI_CENTER_HORIZONTAL_CLOCK, mClassLoader) 39 | 40 | centerHorizontalClockClass?.let { 41 | hookConstructor() 42 | hookUpdateTime() 43 | } 44 | } 45 | } 46 | 47 | private fun hookConstructor() { 48 | XposedWrapper.hookAllConstructors(centerHorizontalClockClass, 49 | object : MethodHookWrapper() { 50 | override fun after(param: MethodHookParam?) { 51 | param?.let { 52 | val miuiBaseClock = it.thisObject as View 53 | miuiBaseClock.viewTreeObserver.addOnWindowAttachListener(object : OnWindowAttachListener { 54 | override fun onWindowAttached() { 55 | addClock(miuiBaseClock) 56 | } 57 | 58 | override fun onWindowDetached() { 59 | removeClock(miuiBaseClock) 60 | } 61 | }) 62 | 63 | addClock(miuiBaseClock) 64 | 65 | ScreenBroadcastManager.getInstance(miuiBaseClock.context).registerListener(screenListener) 66 | } 67 | } 68 | }) 69 | } 70 | 71 | @Synchronized 72 | private fun addClock(clock: View) { 73 | if (!clockList.contains(clock)) { 74 | clockList.add(clock) 75 | val size: Int = clockList.size 76 | val limitedSize = 2 77 | if (size > limitedSize) { 78 | for (i in 0 until size - limitedSize) { 79 | val item: View? = clockList[i] 80 | clockList.remove(item) 81 | } 82 | } 83 | } 84 | if (clockList.isNotEmpty()) { 85 | TimeTicker.get().registerObserver(this) 86 | } 87 | } 88 | 89 | @Synchronized 90 | private fun removeClock(clock: View) { 91 | clockList.remove(clock) 92 | if (clockList.isEmpty()) { 93 | TimeTicker.get().unregisterObserver(this) 94 | } 95 | } 96 | 97 | private val screenListener: SimpleScreenListener = object : SimpleScreenListener() { 98 | override fun onScreenOn() { 99 | TimeTicker.get().registerObserver(this@MiuiCenterHorizontalClockHook) 100 | } 101 | 102 | override fun onScreenOff() { 103 | TimeTicker.get().unregisterObserver(this@MiuiCenterHorizontalClockHook) 104 | } 105 | 106 | override fun onUserPresent() { 107 | TimeTicker.get().unregisterObserver(this@MiuiCenterHorizontalClockHook) 108 | } 109 | 110 | override fun onStopTimeTick() { 111 | TimeTicker.get().unregisterObserver(this@MiuiCenterHorizontalClockHook) 112 | } 113 | } 114 | 115 | override fun onTimeTick() { 116 | for (keyguardClock in clockList) { 117 | if (keyguardClock != null) { 118 | XposedHelpers.callMethod(keyguardClock, "updateTime") 119 | } 120 | } 121 | } 122 | 123 | private fun hookUpdateTime() { 124 | XposedWrapper.findAndHookMethod(centerHorizontalClockClass, 125 | "updateTime", 126 | object : MethodHookWrapper() { 127 | override fun after(param: MethodHookParam?) { 128 | param?.let { 129 | val mTimeText = XposedHelpers.getObjectField(param.thisObject, "mTimeText") as TextView 130 | val originalTimeStr = mTimeText.text.toString() 131 | mTimeText.text = addInSecond(originalTimeStr) 132 | } 133 | } 134 | }) 135 | } 136 | 137 | private fun addInSecond(originalTimeStr: String): String? { 138 | val sec = Calendar.getInstance()[Calendar.SECOND] 139 | val secStr = String.format(Locale.getDefault(), "%02d", sec) 140 | return originalTimeStr.replace("(\\d+:\\d+)(:\\d+)?".toRegex(), "$1:$secStr") 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/keyguard/v20191213/MiuiLeftTopClockHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.keyguard.v20191213 2 | 3 | import android.view.View 4 | import android.view.ViewTreeObserver.OnWindowAttachListener 5 | import android.widget.TextView 6 | import com.tianma.tweaks.miui.data.sp.XPrefContainer 7 | import com.tianma.tweaks.miui.utils.logD 8 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook 9 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.ScreenBroadcastManager 10 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.SimpleScreenListener 11 | import com.tianma.tweaks.miui.xp.hook.systemui.tick.TickObserver 12 | import com.tianma.tweaks.miui.xp.hook.systemui.tick.TimeTicker 13 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo 14 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper 15 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 16 | import de.robv.android.xposed.XposedHelpers 17 | import java.util.* 18 | 19 | /** 20 | * 锁屏左上角小时钟 21 | * 适用版本 20.4.27+ 22 | */ 23 | class MiuiLeftTopClockHook(classLoader: ClassLoader?, appInfo: AppInfo?) : BaseSubHook(classLoader, appInfo), TickObserver { 24 | 25 | companion object { 26 | const val CLASS_MIUI_LEFT_TOP_CLOCK = "miui.keyguard.clock.MiuiLeftTopClock" 27 | } 28 | 29 | private var leftTopClockClass: Class<*>? = null 30 | 31 | private val clockList = mutableListOf() 32 | 33 | private val showHorizontalSec = XPrefContainer.showSecInKeyguardHorizontal 34 | 35 | override fun startHook() { 36 | if (showHorizontalSec) { 37 | logD("Hooking MiuiLeftTopClock...") 38 | leftTopClockClass = XposedWrapper.findClass(CLASS_MIUI_LEFT_TOP_CLOCK, mClassLoader) 39 | leftTopClockClass?.let { 40 | hookConstructor() 41 | hookUpdateTime() 42 | } 43 | } 44 | } 45 | 46 | private fun hookConstructor() { 47 | XposedWrapper.hookAllConstructors(leftTopClockClass, 48 | object : MethodHookWrapper() { 49 | override fun after(param: MethodHookParam?) { 50 | param?.let { 51 | val miuiBaseClock = it.thisObject as View 52 | miuiBaseClock.viewTreeObserver.addOnWindowAttachListener(object : OnWindowAttachListener { 53 | override fun onWindowAttached() { 54 | addClock(miuiBaseClock) 55 | } 56 | 57 | override fun onWindowDetached() { 58 | removeClock(miuiBaseClock) 59 | } 60 | }) 61 | 62 | addClock(miuiBaseClock) 63 | 64 | ScreenBroadcastManager.getInstance(miuiBaseClock.context).registerListener(screenListener) 65 | } 66 | } 67 | }) 68 | } 69 | 70 | @Synchronized 71 | private fun addClock(clock: View) { 72 | if (!clockList.contains(clock)) { 73 | clockList.add(clock) 74 | val size: Int = clockList.size 75 | val limitedSize = 2 76 | if (size > limitedSize) { 77 | for (i in 0 until size - limitedSize) { 78 | val item: View? = clockList[i] 79 | clockList.remove(item) 80 | } 81 | } 82 | } 83 | if (clockList.isNotEmpty()) { 84 | TimeTicker.get().registerObserver(this@MiuiLeftTopClockHook) 85 | } 86 | } 87 | 88 | @Synchronized 89 | private fun removeClock(clock: View) { 90 | clockList.remove(clock) 91 | if (clockList.isEmpty()) { 92 | TimeTicker.get().unregisterObserver(this@MiuiLeftTopClockHook) 93 | } 94 | } 95 | 96 | private val screenListener: SimpleScreenListener = object : SimpleScreenListener() { 97 | override fun onScreenOn() { 98 | TimeTicker.get().registerObserver(this@MiuiLeftTopClockHook) 99 | } 100 | 101 | override fun onScreenOff() { 102 | TimeTicker.get().unregisterObserver(this@MiuiLeftTopClockHook) 103 | } 104 | 105 | override fun onUserPresent() { 106 | TimeTicker.get().unregisterObserver(this@MiuiLeftTopClockHook) 107 | } 108 | 109 | override fun onStopTimeTick() { 110 | TimeTicker.get().unregisterObserver(this@MiuiLeftTopClockHook) 111 | } 112 | } 113 | 114 | override fun onTimeTick() { 115 | for (keyguardClock in clockList) { 116 | if (keyguardClock != null) { 117 | XposedHelpers.callMethod(keyguardClock, "updateTime") 118 | } 119 | } 120 | } 121 | 122 | private fun hookUpdateTime() { 123 | XposedWrapper.findAndHookMethod(leftTopClockClass, 124 | "updateTime", 125 | object : MethodHookWrapper() { 126 | override fun after(param: MethodHookParam?) { 127 | param?.let { 128 | val mTimeText = XposedHelpers.getObjectField(param.thisObject, "mTimeText") as TextView 129 | val originalTimeStr = mTimeText.text.toString() 130 | mTimeText.text = addInSecond(originalTimeStr) 131 | } 132 | } 133 | }) 134 | } 135 | 136 | private fun addInSecond(originalTimeStr: String): String? { 137 | val sec = Calendar.getInstance()[Calendar.SECOND] 138 | val secStr = String.format(Locale.getDefault(), "%02d", sec) 139 | return originalTimeStr.replace("(\\d+:\\d+)(:\\d+)?".toRegex(), "$1:$secStr") 140 | } 141 | 142 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/keyguard/v20191213/MiuiLeftTopLargeClockHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.keyguard.v20191213 2 | 3 | import android.view.View 4 | import android.view.ViewTreeObserver.OnWindowAttachListener 5 | import android.widget.TextView 6 | import com.tianma.tweaks.miui.data.sp.XPrefContainer 7 | import com.tianma.tweaks.miui.utils.logD 8 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook 9 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.ScreenBroadcastManager 10 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.SimpleScreenListener 11 | import com.tianma.tweaks.miui.xp.hook.systemui.tick.TickObserver 12 | import com.tianma.tweaks.miui.xp.hook.systemui.tick.TimeTicker 13 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo 14 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper 15 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 16 | import de.robv.android.xposed.XposedHelpers 17 | import java.util.* 18 | 19 | /** 20 | * 锁屏左上角大时钟 21 | * 适用版本 20.4.27+ 22 | */ 23 | class MiuiLeftTopLargeClockHook(classLoader: ClassLoader?, appInfo: AppInfo?) : BaseSubHook(classLoader, appInfo), TickObserver { 24 | 25 | companion object { 26 | const val CLASS_MIUI_LEFT_TOP_LARGE_CLOCK = "miui.keyguard.clock.MiuiLeftTopLargeClock" 27 | } 28 | 29 | private var leftTopLargeClockClass: Class<*>? = null 30 | 31 | private val clockList = mutableListOf() 32 | 33 | // private val showHorizontalSec = XSPUtils.showSecInKeyguardHorizontal(xsp) 34 | private val showHorizontalSec = XPrefContainer.showSecInKeyguardHorizontal 35 | 36 | override fun startHook() { 37 | if (showHorizontalSec) { 38 | logD("Hooking MiuiLeftTopLargeClock...") 39 | leftTopLargeClockClass = XposedWrapper.findClass(CLASS_MIUI_LEFT_TOP_LARGE_CLOCK, mClassLoader) 40 | 41 | leftTopLargeClockClass?.let { 42 | hookConstructor() 43 | hookUpdateTime() 44 | } 45 | } 46 | } 47 | 48 | private fun hookConstructor() { 49 | XposedWrapper.hookAllConstructors(leftTopLargeClockClass, 50 | object : MethodHookWrapper() { 51 | override fun after(param: MethodHookParam?) { 52 | param?.let { 53 | val miuiBaseClock = it.thisObject as View 54 | miuiBaseClock.viewTreeObserver.addOnWindowAttachListener(object : OnWindowAttachListener { 55 | override fun onWindowAttached() { 56 | addClock(miuiBaseClock) 57 | } 58 | 59 | override fun onWindowDetached() { 60 | removeClock(miuiBaseClock) 61 | } 62 | }) 63 | 64 | addClock(miuiBaseClock) 65 | 66 | ScreenBroadcastManager.getInstance(miuiBaseClock.context).registerListener(screenListener) 67 | } 68 | } 69 | }) 70 | } 71 | 72 | @Synchronized 73 | private fun addClock(clock: View) { 74 | if (!clockList.contains(clock)) { 75 | clockList.add(clock) 76 | val size: Int = clockList.size 77 | val limitedSize = 2 78 | if (size > limitedSize) { 79 | for (i in 0 until size - limitedSize) { 80 | val item: View? = clockList[i] 81 | clockList.remove(item) 82 | } 83 | } 84 | } 85 | if (clockList.isNotEmpty()) { 86 | TimeTicker.get().registerObserver(this) 87 | } 88 | } 89 | 90 | @Synchronized 91 | private fun removeClock(clock: View) { 92 | clockList.remove(clock) 93 | if (clockList.isEmpty()) { 94 | TimeTicker.get().unregisterObserver(this) 95 | } 96 | } 97 | 98 | private val screenListener: SimpleScreenListener = object : SimpleScreenListener() { 99 | override fun onScreenOn() { 100 | TimeTicker.get().registerObserver(this@MiuiLeftTopLargeClockHook) 101 | } 102 | 103 | override fun onScreenOff() { 104 | TimeTicker.get().unregisterObserver(this@MiuiLeftTopLargeClockHook) 105 | } 106 | 107 | override fun onUserPresent() { 108 | TimeTicker.get().unregisterObserver(this@MiuiLeftTopLargeClockHook) 109 | } 110 | 111 | override fun onStopTimeTick() { 112 | TimeTicker.get().unregisterObserver(this@MiuiLeftTopLargeClockHook) 113 | } 114 | } 115 | 116 | override fun onTimeTick() { 117 | for (keyguardClock in clockList) { 118 | if (keyguardClock != null) { 119 | XposedHelpers.callMethod(keyguardClock, "updateTime") 120 | } 121 | } 122 | } 123 | 124 | private fun hookUpdateTime() { 125 | XposedWrapper.findAndHookMethod(leftTopLargeClockClass, 126 | "updateTime", 127 | object : MethodHookWrapper() { 128 | override fun after(param: MethodHookParam?) { 129 | param?.let { 130 | val mTimeText = XposedHelpers.getObjectField(param.thisObject, "mTimeText") as TextView 131 | val originalTimeStr = mTimeText.text.toString() 132 | mTimeText.text = addInSecond(originalTimeStr) 133 | } 134 | } 135 | }) 136 | } 137 | 138 | private fun addInSecond(originalTimeStr: String): String? { 139 | val sec = Calendar.getInstance()[Calendar.SECOND] 140 | val secStr = String.format(Locale.getDefault(), "%02d", sec) 141 | return originalTimeStr.replace("(\\d+:\\d+)(:\\d+)?".toRegex(), "$1:$secStr") 142 | } 143 | 144 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/keyguard/v20191213/MiuiVerticalClockHook.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.keyguard.v20191213 2 | 3 | import android.view.View 4 | import android.view.ViewTreeObserver.OnWindowAttachListener 5 | import android.widget.TextView 6 | import com.tianma.tweaks.miui.data.sp.XPrefContainer 7 | import com.tianma.tweaks.miui.utils.logD 8 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook 9 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.ScreenBroadcastManager 10 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.SimpleScreenListener 11 | import com.tianma.tweaks.miui.xp.hook.systemui.tick.TickObserver 12 | import com.tianma.tweaks.miui.xp.hook.systemui.tick.TimeTicker 13 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo 14 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper 15 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 16 | import de.robv.android.xposed.XposedHelpers 17 | import java.util.* 18 | 19 | /** 20 | * 锁屏居中垂直时钟 21 | * 适用版本 20.4.27+ 22 | */ 23 | class MiuiVerticalClockHook(classLoader: ClassLoader?, appInfo: AppInfo?) : BaseSubHook(classLoader, appInfo), TickObserver { 24 | 25 | companion object { 26 | const val CLASS_MIUI_VERTICAL_CLOCK = "miui.keyguard.clock.MiuiVerticalClock" 27 | } 28 | 29 | private var verticalClockClass: Class<*>? = null 30 | 31 | private val clockList = mutableListOf() 32 | 33 | // private var showVerticalSec = XSPUtils.showSecInKeyguardVertical(xsp) 34 | private var showVerticalSec = XPrefContainer.showSecInKeyguardVertical 35 | 36 | override fun startHook() { 37 | if(showVerticalSec) { 38 | logD("Hooking MiuiVerticalClock...") 39 | verticalClockClass = XposedWrapper.findClass(CLASS_MIUI_VERTICAL_CLOCK, mClassLoader) 40 | verticalClockClass?.let { 41 | hookConstructor() 42 | hookUpdateTime() 43 | } 44 | } 45 | } 46 | 47 | private fun hookConstructor() { 48 | XposedWrapper.hookAllConstructors(verticalClockClass, 49 | object : MethodHookWrapper() { 50 | override fun after(param: MethodHookParam?) { 51 | param?.let { 52 | val miuiBaseClock = it.thisObject as View 53 | miuiBaseClock.viewTreeObserver.addOnWindowAttachListener(object : OnWindowAttachListener { 54 | override fun onWindowAttached() { 55 | addClock(miuiBaseClock) 56 | } 57 | 58 | override fun onWindowDetached() { 59 | removeClock(miuiBaseClock) 60 | } 61 | }) 62 | 63 | addClock(miuiBaseClock) 64 | 65 | ScreenBroadcastManager.getInstance(miuiBaseClock.context).registerListener(screenListener) 66 | } 67 | } 68 | }) 69 | } 70 | 71 | @Synchronized 72 | private fun addClock(clock: View) { 73 | if (!clockList.contains(clock)) { 74 | clockList.add(clock) 75 | val size: Int = clockList.size 76 | val limitedSize = 2 77 | if (size > limitedSize) { 78 | for (i in 0 until size - limitedSize) { 79 | val item: View? = clockList[i] 80 | clockList.remove(item) 81 | } 82 | } 83 | } 84 | if (clockList.isNotEmpty()) { 85 | TimeTicker.get().registerObserver(this@MiuiVerticalClockHook) 86 | } 87 | } 88 | 89 | @Synchronized 90 | private fun removeClock(clock: View) { 91 | clockList.remove(clock) 92 | if (clockList.isEmpty()) { 93 | TimeTicker.get().unregisterObserver(this@MiuiVerticalClockHook) 94 | } 95 | } 96 | 97 | private val screenListener: SimpleScreenListener = object : SimpleScreenListener() { 98 | override fun onScreenOn() { 99 | TimeTicker.get().registerObserver(this@MiuiVerticalClockHook) 100 | } 101 | 102 | override fun onScreenOff() { 103 | TimeTicker.get().unregisterObserver(this@MiuiVerticalClockHook) 104 | } 105 | 106 | override fun onUserPresent() { 107 | TimeTicker.get().unregisterObserver(this@MiuiVerticalClockHook) 108 | } 109 | 110 | override fun onStopTimeTick() { 111 | TimeTicker.get().unregisterObserver(this@MiuiVerticalClockHook) 112 | } 113 | } 114 | 115 | override fun onTimeTick() { 116 | for (keyguardClock in clockList) { 117 | if (keyguardClock != null) { 118 | XposedHelpers.callMethod(keyguardClock, "updateTime") 119 | } 120 | } 121 | } 122 | 123 | private fun hookUpdateTime() { 124 | XposedWrapper.findAndHookMethod(verticalClockClass, 125 | "updateTime", 126 | object : MethodHookWrapper() { 127 | override fun after(param: MethodHookParam?) { 128 | param?.let { 129 | val mTimeText = XposedHelpers.getObjectField(param.thisObject, "mTimeText") as TextView 130 | val originalTimeStr = mTimeText.text.toString() 131 | mTimeText.text = addInSecond(originalTimeStr) 132 | } 133 | } 134 | }) 135 | } 136 | 137 | private fun addInSecond(originalTimeStr: String): String? { 138 | val sec = Calendar.getInstance()[Calendar.SECOND] 139 | val secStr = String.format(Locale.getDefault(), "%02d", sec) 140 | return originalTimeStr.replace("(\\d+\n\\d+)(\n\\d+)?".toRegex(), "$1\n$secStr") 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/screen/IntentAction.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.screen 2 | 3 | object IntentAction { 4 | const val KEYGUARD_STOP_TIME_TICK = "com.tianma.tweaks.miui.KEYGUARD_STOP_TIME_TICK" 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/screen/ScreenBroadcastManager.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.screen 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import com.tianma.tweaks.miui.xp.hook.systemui.tick.TimeTicker 8 | import java.lang.ref.WeakReference 9 | import java.util.* 10 | 11 | /** 12 | * Screen Broadcast Manager 13 | */ 14 | class ScreenBroadcastManager private constructor(context: Context) { 15 | 16 | private val listeners: MutableList = ArrayList() 17 | private val contextWeakReference: WeakReference = 18 | WeakReference(context.applicationContext) 19 | 20 | companion object { 21 | @Volatile 22 | private var sInstance: ScreenBroadcastManager? = null 23 | 24 | @JvmStatic 25 | fun getInstance(modContext: Context): ScreenBroadcastManager { 26 | return sInstance ?: synchronized(TimeTicker::class.java) { 27 | sInstance ?: ScreenBroadcastManager(modContext).also { 28 | sInstance = it 29 | } 30 | } 31 | } 32 | } 33 | 34 | @Synchronized 35 | fun registerListener(screenListener: ScreenListener) { 36 | if (listeners.isEmpty()) { 37 | registerBroadcastReceiver() 38 | } 39 | if (!listeners.contains(screenListener)) { 40 | listeners.add(screenListener) 41 | } 42 | } 43 | 44 | @Synchronized 45 | fun unregisterListener(screenListener: ScreenListener) { 46 | listeners.remove(screenListener) 47 | if (listeners.isEmpty()) { 48 | unregisterBroadcastReceiver() 49 | } 50 | } 51 | 52 | // register broadcast receiver for screen on, off, user-present, stop-time-tick 53 | private fun registerBroadcastReceiver() { 54 | val context = contextWeakReference.get() 55 | if (context != null) { 56 | // register receiver 57 | val filter = IntentFilter() 58 | filter.addAction(Intent.ACTION_SCREEN_ON) 59 | filter.addAction(Intent.ACTION_USER_PRESENT) 60 | filter.addAction(Intent.ACTION_SCREEN_OFF) 61 | filter.addAction(IntentAction.KEYGUARD_STOP_TIME_TICK) 62 | context.registerReceiver(screenReceiver, filter) 63 | } 64 | } 65 | 66 | // unregister broadcast receiver 67 | private fun unregisterBroadcastReceiver() { 68 | val context = contextWeakReference.get() 69 | context?.unregisterReceiver(screenReceiver) 70 | } 71 | 72 | // screen receiver 73 | private val screenReceiver: BroadcastReceiver by lazy { 74 | object : BroadcastReceiver() { 75 | override fun onReceive(context: Context, intent: Intent) { 76 | val action = intent.action 77 | notifyListeners(action) 78 | } 79 | } 80 | } 81 | 82 | // notify screen event to all listeners (ScreenListener) 83 | private fun notifyListeners(action: String?) { 84 | val screenOn = Intent.ACTION_SCREEN_ON == action 85 | val userPresent = Intent.ACTION_USER_PRESENT == action 86 | val screenOff = Intent.ACTION_SCREEN_OFF == action 87 | val stopTimeTick = IntentAction.KEYGUARD_STOP_TIME_TICK == action 88 | 89 | for (listener in listeners) { 90 | when { 91 | screenOn -> { 92 | listener.onScreenOn() 93 | } 94 | userPresent -> { 95 | listener.onUserPresent() 96 | } 97 | screenOff -> { 98 | listener.onScreenOff() 99 | } 100 | stopTimeTick -> { 101 | listener.onStopTimeTick() 102 | } 103 | } 104 | } 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/screen/ScreenListener.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.screen 2 | 3 | /** 4 | * Screen Listener 5 | */ 6 | interface ScreenListener { 7 | /** 8 | * called when screen on 9 | */ 10 | fun onScreenOn() 11 | 12 | /** 13 | * called when screen off 14 | */ 15 | fun onScreenOff() 16 | 17 | /** 18 | * called when user present 19 | */ 20 | fun onUserPresent() 21 | 22 | /** 23 | * called when stop time tick 24 | */ 25 | fun onStopTimeTick() 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/screen/SimpleScreenListener.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.screen 2 | 3 | open class SimpleScreenListener : ScreenListener { 4 | override fun onScreenOn() {} 5 | override fun onScreenOff() {} 6 | override fun onUserPresent() {} 7 | override fun onStopTimeTick() {} 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/statusbar/def/BatteryMeterViewHook.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.statusbar.def; 2 | 3 | import android.text.SpannableString; 4 | import android.text.Spanned; 5 | import android.text.style.AbsoluteSizeSpan; 6 | import android.widget.TextView; 7 | 8 | import com.tianma.tweaks.miui.data.sp.XPrefContainer; 9 | import com.tianma.tweaks.miui.utils.XLogKt; 10 | import com.tianma.tweaks.miui.utils.rom.MiuiVersion; 11 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook; 12 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper; 13 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper; 14 | 15 | import de.robv.android.xposed.XposedHelpers; 16 | 17 | public class BatteryMeterViewHook extends BaseSubHook { 18 | 19 | private static final String CLASS_BATTERY_VIEW = "com.android.systemui.BatteryMeterView"; 20 | 21 | private boolean mShowSmallPercentSign; 22 | 23 | public BatteryMeterViewHook(ClassLoader classLoader, MiuiVersion miuiVersion) { 24 | super(classLoader, null, miuiVersion); 25 | 26 | // mShowSmallPercentSign = XSPUtils.showSmallBatteryPercentSign(xsp); 27 | mShowSmallPercentSign = XPrefContainer.getShowSmallBatteryPercentSign(); 28 | } 29 | 30 | @Override 31 | public void startHook() { 32 | XLogKt.logD("Hooking BatteryMeterView..."); 33 | if (mShowSmallPercentSign) { 34 | hookUpdateShowPercent(); 35 | } 36 | } 37 | 38 | private void hookUpdateShowPercent() { 39 | XposedWrapper.findAndHookMethod(CLASS_BATTERY_VIEW, 40 | getMClassLoader(), 41 | "updateShowPercent", 42 | new MethodHookWrapper() { 43 | @Override 44 | protected void after(MethodHookParam param) { 45 | 46 | TextView mBatteryPercentView = (TextView) XposedHelpers 47 | .getObjectField(param.thisObject, "mBatteryPercentView"); 48 | if (mBatteryPercentView != null) { 49 | CharSequence cs = mBatteryPercentView.getText(); 50 | if (cs == null) { 51 | return; 52 | } 53 | String text = cs.toString(); 54 | int percentSignIdx = text.indexOf('%'); 55 | if (percentSignIdx != -1) { 56 | SpannableString ss = new SpannableString(text); 57 | float originSize = mBatteryPercentView.getTextSize(); 58 | ss.setSpan(new AbsoluteSizeSpan((int) (originSize * 3 / 4)), percentSignIdx, percentSignIdx + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 59 | mBatteryPercentView.setText(ss); 60 | } 61 | } 62 | } 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/statusbar/def/CollapsedStatusBarFragmentHook.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.statusbar.def; 2 | 3 | import static com.tianma.tweaks.miui.xp.wrapper.XposedWrapper.findAndHookMethod; 4 | 5 | import android.content.res.Resources; 6 | import android.os.Bundle; 7 | import android.view.Gravity; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.LinearLayout; 11 | 12 | import com.tianma.tweaks.miui.data.sp.XPrefContainer; 13 | import com.tianma.tweaks.miui.utils.XLogKt; 14 | import com.tianma.tweaks.miui.utils.rom.MiuiVersion; 15 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook; 16 | import com.tianma.tweaks.miui.xp.hook.systemui.helper.ResHelpers; 17 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper; 18 | 19 | import de.robv.android.xposed.XposedHelpers; 20 | 21 | public class CollapsedStatusBarFragmentHook extends BaseSubHook { 22 | 23 | private static final String CLASS_STATUS_BAR_FRAGMENT = "com.android.systemui.statusbar.phone.CollapsedStatusBarFragment"; 24 | 25 | private boolean mSignalAlignLeft; 26 | private boolean mAlwaysShowStatusBarClock; 27 | 28 | public CollapsedStatusBarFragmentHook(ClassLoader classLoader, MiuiVersion miuiVersion) { 29 | super(classLoader, null, miuiVersion); 30 | 31 | // mSignalAlignLeft = XSPUtils.isSignalAlignLeft(xsp); 32 | mSignalAlignLeft = XPrefContainer.isSignalAlignLeft(); 33 | // mAlwaysShowStatusBarClock = XSPUtils.alwaysShowStatusBarClock(xsp); 34 | mAlwaysShowStatusBarClock = XPrefContainer.getAlwaysShowStatusBarClock(); 35 | } 36 | 37 | @Override 38 | public void startHook() { 39 | try { 40 | XLogKt.logD("Hooking CollapsedStatusBarFragment... "); 41 | if (mSignalAlignLeft) { 42 | hookOnViewCreated(); 43 | } 44 | 45 | if (mAlwaysShowStatusBarClock) { 46 | hookClockVisibleAnimate(); 47 | } 48 | } catch (Throwable t) { 49 | XLogKt.logE("Error occurs when hook CollapsedStatusBarFragment", t); 50 | } 51 | } 52 | 53 | // CollapsedStatusBarFragment#onViewCreated() 54 | private void hookOnViewCreated() { 55 | findAndHookMethod(CLASS_STATUS_BAR_FRAGMENT, 56 | getMClassLoader(), 57 | "onViewCreated", 58 | View.class, 59 | Bundle.class, 60 | new MethodHookWrapper() { 61 | @Override 62 | protected void after(MethodHookParam param) { 63 | ViewGroup phoneStatusBarView = (ViewGroup) XposedHelpers.getObjectField(param.thisObject, "mStatusBar"); 64 | Resources res = phoneStatusBarView.getResources(); 65 | 66 | View signalClusterViewContainer = phoneStatusBarView 67 | .findViewById(ResHelpers.getId(res, "signal_cluster_view")); 68 | ((ViewGroup) signalClusterViewContainer.getParent()).removeView(signalClusterViewContainer); 69 | 70 | if (getMMiuiVersion().getTime() >= MiuiVersion.V_19_5_7.getTime()) { 71 | try { 72 | LinearLayout contentsContainer = phoneStatusBarView 73 | .findViewById(ResHelpers.getId(res, "phone_status_bar_contents_container")); 74 | contentsContainer.setGravity(Gravity.CENTER_VERTICAL); 75 | contentsContainer.addView(signalClusterViewContainer, 0); 76 | } catch (Throwable t) { 77 | LinearLayout statusBarContents = phoneStatusBarView 78 | .findViewById(ResHelpers.getId(res, "status_bar_contents")); 79 | statusBarContents.setGravity(Gravity.CENTER_VERTICAL); 80 | statusBarContents.addView(signalClusterViewContainer, 0); 81 | } 82 | } else { 83 | LinearLayout statusBarContents = phoneStatusBarView 84 | .findViewById(ResHelpers.getId(res, "status_bar_contents")); 85 | statusBarContents.setGravity(Gravity.CENTER_VERTICAL); 86 | statusBarContents.addView(signalClusterViewContainer, 0); 87 | } 88 | } 89 | }); 90 | } 91 | 92 | private void hookClockVisibleAnimate() { 93 | findAndHookMethod(CLASS_STATUS_BAR_FRAGMENT, 94 | getMClassLoader(), 95 | "clockVisibleAnimate", 96 | boolean.class, 97 | boolean.class, 98 | new MethodHookWrapper() { 99 | @Override 100 | protected void before(MethodHookParam param) { 101 | View mStatusClock = (View) XposedHelpers.getObjectField(param.thisObject, "mStatusClock"); 102 | mStatusClock.setVisibility(View.VISIBLE); 103 | param.setResult(null); 104 | } 105 | }); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/statusbar/def/PhoneStatusBarViewHook.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.statusbar.def; 2 | 3 | import static com.tianma.tweaks.miui.xp.wrapper.XposedWrapper.findAndHookMethod; 4 | 5 | import android.content.Context; 6 | import android.content.res.Resources; 7 | import android.view.Gravity; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | 13 | import com.tianma.tweaks.miui.cons.PrefConst; 14 | import com.tianma.tweaks.miui.data.sp.XPrefContainer; 15 | import com.tianma.tweaks.miui.utils.XLogKt; 16 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook; 17 | import com.tianma.tweaks.miui.xp.hook.systemui.SystemUIHook; 18 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo; 19 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper; 20 | 21 | import de.robv.android.xposed.XC_MethodHook; 22 | 23 | /** 24 | * 状态栏时钟居中显示 25 | * 适用版本 9.4.x+ 26 | */ 27 | public class PhoneStatusBarViewHook extends BaseSubHook { 28 | 29 | private static final String PACKAGE_NAME = SystemUIHook.PACKAGE_NAME; 30 | 31 | private static final String CLASS_PHONE_STATUS_BAR_VIEW = "com.android.systemui.statusbar.phone.PhoneStatusBarView"; 32 | private static final String CLASS_STATUS_BAR = "com.android.systemui.statusbar.phone.StatusBar"; 33 | 34 | private static final String CLASS_NOTIFICATION_ICON_CONTAINER = "com.android.systemui.statusbar.phone.NotificationIconContainer"; 35 | 36 | private LinearLayout mCenterLayout; 37 | 38 | private boolean mAlignmentCenter = false; 39 | private boolean mAlignmentRight = false; 40 | 41 | public PhoneStatusBarViewHook(ClassLoader classLoader, AppInfo appInfo) { 42 | super(classLoader, appInfo); 43 | // String alignment = XSPUtils.getStatusBarClockAlignment(xsp); 44 | String alignment = XPrefContainer.getStatusBarClockAlignment(); 45 | if (PrefConst.ALIGNMENT_CENTER.equals(alignment)) { 46 | mAlignmentCenter = true; 47 | mAlignmentRight = false; 48 | } else if (PrefConst.ALIGNMENT_RIGHT.equals(alignment)) { 49 | mAlignmentCenter = false; 50 | mAlignmentRight = true; 51 | } 52 | } 53 | 54 | public void startHook() { 55 | if (mAlignmentCenter || mAlignmentRight) { 56 | try { 57 | XLogKt.logD("Hooking PhoneStatusBarView..."); 58 | hookSetBar(); 59 | if (mAlignmentCenter) { 60 | hookGetActualWidth(); 61 | } 62 | } catch (Throwable t) { 63 | XLogKt.logE("Error occurs when hook PhoneStatusBarView", t); 64 | } 65 | } 66 | } 67 | 68 | private void hookSetBar() { 69 | findAndHookMethod(CLASS_PHONE_STATUS_BAR_VIEW, 70 | getMClassLoader(), 71 | "setBar", 72 | CLASS_STATUS_BAR, 73 | new MethodHookWrapper() { 74 | @Override 75 | protected void after(MethodHookParam param) { 76 | prepareLayoutStatusBar(param); 77 | } 78 | }); 79 | } 80 | 81 | private void prepareLayoutStatusBar(XC_MethodHook.MethodHookParam param) { 82 | // FrameLayout 83 | ViewGroup phoneStatusBarView = (ViewGroup) param.thisObject; 84 | 85 | Context context = phoneStatusBarView.getContext(); 86 | Resources res = context.getResources(); 87 | 88 | // LinearLayout statusBarContents = phoneStatusBarView.findViewById( 89 | // res.getIdentifier("status_bar_contents", "id", PACKAGE_NAME)); 90 | 91 | TextView clock = phoneStatusBarView.findViewById( 92 | res.getIdentifier("clock", "id", PACKAGE_NAME)); 93 | ((ViewGroup) clock.getParent()).removeView(clock); 94 | 95 | if (mAlignmentCenter) { 96 | // 注入新的居中的layout 到 phoneStatusBarView 中去 97 | mCenterLayout = new LinearLayout(context); 98 | LinearLayout.LayoutParams lp = 99 | new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); 100 | mCenterLayout.setLayoutParams(lp); 101 | mCenterLayout.setGravity(Gravity.CENTER); 102 | phoneStatusBarView.addView(mCenterLayout); 103 | 104 | clock.setPaddingRelative(2, 0, 2, 0); 105 | clock.setGravity(Gravity.CENTER); 106 | mCenterLayout.addView(clock); 107 | } else if (mAlignmentRight) { // 居右对齐 108 | LinearLayout rightAreaLayout = phoneStatusBarView.findViewById( 109 | res.getIdentifier("system_icons", "id", PACKAGE_NAME)); 110 | clock.setGravity(Gravity.END | Gravity.CENTER_VERTICAL); 111 | rightAreaLayout.addView(clock); 112 | } 113 | } 114 | 115 | private void hookGetActualWidth() { 116 | findAndHookMethod(CLASS_NOTIFICATION_ICON_CONTAINER, 117 | getMClassLoader(), 118 | "getActualWidth", 119 | new MethodHookWrapper() { 120 | @Override 121 | protected void after(MethodHookParam param) { 122 | if (mCenterLayout == null) 123 | return; 124 | if (mCenterLayout.getChildCount() == 0) 125 | return; 126 | View clock = mCenterLayout.getChildAt(0); 127 | int width = Math.round(mCenterLayout.getWidth() / 2f - clock.getWidth() / 2f) - 8; 128 | param.setResult(width); 129 | } 130 | }); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/statusbar/v20201109/CollapsedStatusBarFragmentHook20201109.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.statusbar.v20201109 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import com.tianma.tweaks.miui.data.sp.XPrefContainer 7 | import com.tianma.tweaks.miui.utils.callMethod 8 | import com.tianma.tweaks.miui.utils.logD 9 | import com.tianma.tweaks.miui.utils.logE 10 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook 11 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.ScreenBroadcastManager 12 | import com.tianma.tweaks.miui.xp.hook.systemui.screen.SimpleScreenListener 13 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo 14 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper 15 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 16 | import de.robv.android.xposed.XposedHelpers 17 | 18 | class CollapsedStatusBarFragmentHook20201109(classLoader: ClassLoader?, appInfo: AppInfo?) : 19 | BaseSubHook(classLoader, appInfo) { 20 | 21 | companion object { 22 | private const val CLASS_STATUS_BAR_FRAGMENT = 23 | "com.android.systemui.statusbar.phone.CollapsedStatusBarFragment" 24 | } 25 | 26 | private val mSignalAlignLeft: Boolean = XPrefContainer.isSignalAlignLeft 27 | private val mAlwaysShowStatusBarClock: Boolean = XPrefContainer.alwaysShowStatusBarClock 28 | 29 | // 是否可以展示状态栏时钟 (锁屏状态下不展示,梁平状态下有可能展示) 30 | private var canShowStatusBarClock = false 31 | 32 | override fun startHook() { 33 | try { 34 | logD("Hooking CollapsedStatusBarFragment... ") 35 | if (mSignalAlignLeft) { 36 | // nothing 37 | } 38 | if (mAlwaysShowStatusBarClock) { 39 | hookOnViewCreated() 40 | hookHideClock() 41 | } 42 | } catch (t: Throwable) { 43 | logE("Error occurs when hook CollapsedStatusBarFragment", t) 44 | } 45 | } 46 | 47 | // CollapsedStatusBarFragment#onViewCreated() 48 | private fun hookOnViewCreated() { 49 | XposedWrapper.findAndHookMethod( 50 | CLASS_STATUS_BAR_FRAGMENT, 51 | mClassLoader, 52 | "onViewCreated", 53 | View::class.java, 54 | Bundle::class.java, 55 | object : MethodHookWrapper() { 56 | override fun after(param: MethodHookParam) { 57 | val phoneStatusBarView = 58 | XposedHelpers.getObjectField(param.thisObject, "mStatusBar") as ViewGroup 59 | 60 | val context = phoneStatusBarView.context 61 | val statusBarFragment = param.thisObject 62 | 63 | val screenListener = object : SimpleScreenListener() { 64 | override fun onUserPresent() { 65 | // 屏幕解锁时,可以展示状态栏时钟 66 | canShowStatusBarClock = true 67 | 68 | // 展示时钟 69 | statusBarFragment.callMethod( 70 | "showClock", 71 | arrayOf(Boolean::class.java), 72 | arrayOf(true) 73 | ) 74 | } 75 | 76 | override fun onScreenOff() { 77 | // 屏幕锁定时,不可以展示状态栏时钟 78 | canShowStatusBarClock = false 79 | 80 | statusBarFragment.callMethod( 81 | "hideClock", 82 | arrayOf(Boolean::class.java), 83 | arrayOf(true) 84 | ) 85 | } 86 | } 87 | ScreenBroadcastManager.getInstance(context).registerListener(screenListener) 88 | } 89 | }) 90 | } 91 | 92 | private fun hookHideClock() { 93 | XposedWrapper.findAndHookMethod( 94 | CLASS_STATUS_BAR_FRAGMENT, 95 | mClassLoader, 96 | "hideClock", 97 | Boolean::class.java, 98 | object : MethodHookWrapper() { 99 | override fun before(param: MethodHookParam?) { 100 | param ?: return 101 | // 拦截 hideClock() 102 | if (canShowStatusBarClock) { 103 | param.result = null 104 | } 105 | } 106 | } 107 | ) 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/statusbar/v20201109/StatusBarMobileViewHook20201109.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.statusbar.v20201109 2 | 3 | import android.widget.TextView 4 | import com.tianma.tweaks.miui.data.sp.XPrefContainer 5 | import com.tianma.tweaks.miui.utils.logE 6 | import com.tianma.tweaks.miui.utils.logI 7 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook 8 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo 9 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper 10 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 11 | import de.robv.android.xposed.XposedBridge 12 | import de.robv.android.xposed.XposedHelpers 13 | 14 | /** 15 | * StatusBarMobileView Hook, 用于 hook 自定义网络类型 16 | * 适用版本 MIUISystemUI(versionCode >= 202011090) 17 | */ 18 | class StatusBarMobileViewHook20201109( 19 | classLoader: ClassLoader, 20 | appInfo: AppInfo 21 | ) : BaseSubHook(classLoader, appInfo) { 22 | 23 | companion object { 24 | private const val CLASS_STATUS_BAR_MOBILE_VIEW = "com.android.systemui.statusbar.StatusBarMobileView" 25 | } 26 | 27 | // private val isCustomNetworkTypeEnabled = XSPUtils.isCustomMobileNetworkEnabled(xsp) 28 | private val isCustomNetworkTypeEnabled = XPrefContainer.isCustomMobileNetworkEnabled 29 | private var customNetworkType: String = "" 30 | 31 | init { 32 | if (isCustomNetworkTypeEnabled) { 33 | // customNetworkType = XSPUtils.customMobileNetwork(xsp) 34 | customNetworkType = XPrefContainer.customMobileNetwork 35 | } 36 | } 37 | 38 | override fun startHook() { 39 | try { 40 | logI("Hooking StatusBarMobileView...") 41 | 42 | if (isCustomNetworkTypeEnabled) { 43 | hookUpdateState() 44 | } 45 | 46 | }catch (t: Throwable) { 47 | logE("Error occurs when hook StatusBarMobileView", t) 48 | } 49 | } 50 | 51 | private fun hookUpdateState() { 52 | val targetMethod = XposedWrapper.findMethodByNameIfExists( 53 | CLASS_STATUS_BAR_MOBILE_VIEW, 54 | mClassLoader, 55 | "updateState" 56 | ) 57 | 58 | targetMethod?.also { 59 | XposedBridge.hookMethod( 60 | targetMethod, 61 | object: MethodHookWrapper() { 62 | override fun after(param: MethodHookParam?) { 63 | param?.also { 64 | val thisObj = param.thisObject 65 | val mMobileType = XposedHelpers.getObjectField(thisObj, "mMobileType") as TextView 66 | mMobileType.text = customNetworkType 67 | } 68 | } 69 | } 70 | ) 71 | } 72 | } 73 | 74 | 75 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/statusbar/v20201109/StatusBarSignalPolicyHook20201109.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.statusbar.v20201109 2 | 3 | import com.tianma.tweaks.miui.data.sp.XPrefContainer 4 | import com.tianma.tweaks.miui.utils.logE 5 | import com.tianma.tweaks.miui.utils.logI 6 | import com.tianma.tweaks.miui.xp.hook.BaseSubHook 7 | import com.tianma.tweaks.miui.xp.utils.appinfo.AppInfo 8 | import com.tianma.tweaks.miui.xp.wrapper.MethodHookWrapper 9 | import com.tianma.tweaks.miui.xp.wrapper.XposedWrapper 10 | import de.robv.android.xposed.XposedHelpers 11 | 12 | /** 13 | * StatusBarSignalPolicy Hook, 用于处理 隐藏 VPN 图标 14 | * 适用版本 MIUISystemUI(versionCode >= 202011090) 15 | */ 16 | class StatusBarSignalPolicyHook20201109( 17 | classLoader: ClassLoader, 18 | appInfo: AppInfo 19 | ) : BaseSubHook(classLoader, appInfo) { 20 | 21 | companion object { 22 | private const val CLASS_STATUS_BAR_POLICY = "com.android.systemui.statusbar.phone.StatusBarSignalPolicy" 23 | } 24 | 25 | // private val mHideVpnIcon: Boolean = XSPUtils.isHideVpnIcon(xsp) 26 | private val mHideVpnIcon: Boolean = XPrefContainer.isHideVpnIcon 27 | 28 | override fun startHook() { 29 | 30 | try { 31 | logI("Hooking StatusBarSignalPolicy...") 32 | 33 | if (mHideVpnIcon) { 34 | hookUpdateVpn() 35 | } 36 | 37 | } catch (t: Throwable) { 38 | logE("Error occurs when hook StatusBarSignalPolicy", t) 39 | } 40 | 41 | } 42 | 43 | private fun hookUpdateVpn() { 44 | XposedWrapper.findAndHookMethod( 45 | CLASS_STATUS_BAR_POLICY, 46 | mClassLoader, 47 | "updateVpn", 48 | object : MethodHookWrapper() { 49 | override fun after(param: MethodHookParam?) { 50 | param?.also { 51 | val thisObj = param.thisObject 52 | val mSlotVpn = XposedHelpers.getObjectField(thisObj, "mSlotVpn") 53 | val mIconController = XposedHelpers.getObjectField(thisObj, "mIconController") 54 | XposedHelpers.callMethod(mIconController, "setIconVisibility", mSlotVpn, false) 55 | } 56 | } 57 | } 58 | ) 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/tick/TickObserver.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.tick; 2 | 3 | public interface TickObserver { 4 | 5 | void onTimeTick(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/tick/TimeTicker.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.tick; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.os.SystemClock; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class TimeTicker { 11 | 12 | private List mObserverList; 13 | 14 | private volatile static TimeTicker sTimeTicker; 15 | 16 | private Handler mSecondsHandler = new Handler(Looper.getMainLooper()); 17 | 18 | private TimeTicker() { 19 | mObserverList = new ArrayList<>(); 20 | } 21 | 22 | private Runnable mSecondsTicker = new Runnable() { 23 | 24 | @Override 25 | public void run() { 26 | long now = SystemClock.uptimeMillis(); 27 | long next = now + (1000 - now % 1000); 28 | mSecondsHandler.postAtTime(this, next); 29 | for (TickObserver observer : mObserverList) { 30 | if (observer != null) { 31 | observer.onTimeTick(); 32 | } 33 | } 34 | } 35 | }; 36 | 37 | public static TimeTicker get() { 38 | if (sTimeTicker == null) { 39 | synchronized (TimeTicker.class) { 40 | if (sTimeTicker == null) { 41 | sTimeTicker = new TimeTicker(); 42 | } 43 | } 44 | } 45 | return sTimeTicker; 46 | } 47 | 48 | public synchronized void registerObserver(TickObserver observer) { 49 | if (mObserverList.isEmpty()) { 50 | startTick(); 51 | } 52 | 53 | if (!mObserverList.contains(observer)) { 54 | mObserverList.add(observer); 55 | } 56 | } 57 | 58 | public synchronized void unregisterObserver(TickObserver observer) { 59 | mObserverList.remove(observer); 60 | 61 | if (mObserverList.isEmpty()) { 62 | stopTick(); 63 | } 64 | } 65 | 66 | private void startTick() { 67 | mSecondsHandler.post(mSecondsTicker); 68 | } 69 | 70 | private void stopTick() { 71 | mSecondsHandler.removeCallbacks(mSecondsTicker); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/hook/systemui/weather/WeatherObserver.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.hook.systemui.weather; 2 | 3 | public interface WeatherObserver { 4 | 5 | void onWeatherChanged(String newWeatherInfo); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/utils/appinfo/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.utils.appinfo 2 | 3 | data class AppInfo(val packageName: String, val versionCode: Int, val versionName: String) -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/utils/appinfo/AppInfoHelper.kt: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.utils.appinfo 2 | 3 | import com.tianma.tweaks.miui.utils.logE 4 | import de.robv.android.xposed.XposedHelpers 5 | import de.robv.android.xposed.callbacks.XC_LoadPackage 6 | import java.io.File 7 | 8 | object AppInfoHelper { 9 | 10 | @JvmStatic 11 | fun getAppInfo(lpparam: XC_LoadPackage.LoadPackageParam?): AppInfo { 12 | var versionCode = 0 13 | var versionName = "0" 14 | var packageName = "" 15 | 16 | if (lpparam != null) { 17 | try { 18 | val packageParserCls = XposedHelpers.findClass("android.content.pm.PackageParser", lpparam.classLoader) 19 | val packageParser = packageParserCls.newInstance() 20 | val apkPath = File(lpparam.appInfo.sourceDir) 21 | val pkg = XposedHelpers.callMethod(packageParser, "parsePackage", apkPath, 0) 22 | versionCode = XposedHelpers.getIntField(pkg, "mVersionCode") 23 | versionName = XposedHelpers.getObjectField(pkg, "mVersionName") as String 24 | packageName = lpparam.packageName 25 | } catch (throwable: Throwable) { 26 | logE("Parse package info failed", throwable) 27 | } 28 | } 29 | 30 | return AppInfo(packageName, versionCode, versionName) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/utils/appinfo/AppVersionConst.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.utils.appinfo; 2 | 3 | public interface AppVersionConst { 4 | 5 | int SYSTEM_UI_V201912130 = 201912130; 6 | 7 | int SYSTEM_UI_V202011090 = 202011090; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/wrapper/MethodHookWrapper.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.wrapper; 2 | 3 | import com.tianma.tweaks.miui.utils.XLogKt; 4 | import com.tianma.tweaks.miui.utils.XLogKt; 5 | 6 | import de.robv.android.xposed.XC_MethodHook; 7 | 8 | public abstract class MethodHookWrapper extends XC_MethodHook { 9 | 10 | @Override 11 | final protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 12 | try { 13 | before(param); 14 | } catch (Throwable t) { 15 | XLogKt.logE("Error in hook %s", param.method.getName(), t); 16 | } 17 | } 18 | 19 | protected void before(MethodHookParam param) { 20 | } 21 | 22 | @Override 23 | final protected void afterHookedMethod(MethodHookParam param) throws Throwable { 24 | try { 25 | after(param); 26 | } catch (Throwable t) { 27 | XLogKt.logE("Error in hook %s", param.method.getName(), t); 28 | } 29 | } 30 | 31 | protected void after(MethodHookParam param) { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/tianma/tweaks/miui/xp/wrapper/XposedWrapper.java: -------------------------------------------------------------------------------- 1 | package com.tianma.tweaks.miui.xp.wrapper; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import com.tianma.tweaks.miui.utils.XLogKt; 6 | 7 | import java.lang.reflect.Method; 8 | import java.lang.reflect.Modifier; 9 | import java.util.HashMap; 10 | import java.util.Set; 11 | 12 | import de.robv.android.xposed.XC_MethodHook; 13 | import de.robv.android.xposed.XposedBridge; 14 | import de.robv.android.xposed.XposedHelpers; 15 | 16 | /** 17 | * Xposed Wrapper Utils 18 | */ 19 | public class XposedWrapper { 20 | 21 | private static final HashMap methodCache = new HashMap(); 22 | 23 | private XposedWrapper() { 24 | } 25 | 26 | public static Class findClass(String className, ClassLoader classLoader) { 27 | try { 28 | return XposedHelpers.findClass(className, classLoader); 29 | } catch (Throwable t) { 30 | XLogKt.logE("Class not found: %s", className); 31 | return null; 32 | } 33 | } 34 | 35 | public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) { 36 | try { 37 | return XposedHelpers.findAndHookMethod(className, classLoader, methodName, parameterTypesAndCallback); 38 | } catch (Throwable t) { 39 | XLogKt.logE("Error in hook %s#%s", className, methodName, t); 40 | return null; 41 | } 42 | } 43 | 44 | public static XC_MethodHook.Unhook findAndHookMethod(Class clazz, String methodName, Object... parameterTypesAndCallback) { 45 | try { 46 | return XposedHelpers.findAndHookMethod(clazz, methodName, parameterTypesAndCallback); 47 | } catch (Throwable t) { 48 | XLogKt.logE("Error in hook %s#%s", clazz.getName(), methodName, t); 49 | return null; 50 | } 51 | } 52 | 53 | public static Set hookAllConstructors(Class hookClass, XC_MethodHook callback) { 54 | try { 55 | return XposedBridge.hookAllConstructors(hookClass, callback); 56 | } catch (Throwable t) { 57 | XLogKt.logE("Error in hookAllConstructors: %s", hookClass.getName(), t); 58 | return null; 59 | } 60 | } 61 | 62 | public static XC_MethodHook.Unhook findAndHookConstructor(Class clazz, Object... parameterTypesAndCallback) { 63 | try { 64 | return XposedHelpers.findAndHookConstructor(clazz, parameterTypesAndCallback); 65 | } catch (Throwable t) { 66 | XLogKt.logE("Error in findAndHookConstructor: %s", clazz.getName(), t); 67 | return null; 68 | } 69 | } 70 | 71 | public static XC_MethodHook.Unhook findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback) { 72 | try { 73 | return XposedHelpers.findAndHookConstructor(className, classLoader, parameterTypesAndCallback); 74 | } catch (Throwable t) { 75 | XLogKt.logE("Error in findAndHookConstructor: %s", className, t); 76 | return null; 77 | } 78 | } 79 | 80 | // 通过方法名查找方法,模糊查询 81 | @Nullable 82 | public static Method findMethodByNameIfExists(String className, ClassLoader classLoader, String methodName) { 83 | return findMethodByNameIfExists(findClass(className, classLoader), methodName); 84 | } 85 | 86 | // 通过方法名查找方法,模糊查询 87 | @Nullable 88 | public static Method findMethodByNameIfExists(Class clazz, String methodName) { 89 | if (clazz == null) { 90 | return null; 91 | } 92 | String fullMethodName = clazz.getName() + '#' + methodName + "#fuzzyMatch"; 93 | 94 | if (methodCache.containsKey(fullMethodName)) { 95 | Method method = methodCache.get(fullMethodName); 96 | if (method == null) 97 | throw new NoSuchMethodError(fullMethodName); 98 | return method; 99 | } 100 | 101 | Method fuzzyMatch = null; 102 | Class clz = clazz; 103 | boolean considerPrivateMethods = true; 104 | do { 105 | for (Method method : clz.getDeclaredMethods()) { 106 | // don't consider private methods of superclasses 107 | if (!considerPrivateMethods && Modifier.isPrivate(method.getModifiers())) 108 | continue; 109 | 110 | // compare name and parameters 111 | if (method.getName().equals(methodName)) { 112 | // get accessible version of method 113 | fuzzyMatch = method; 114 | break; 115 | } 116 | } 117 | 118 | if (fuzzyMatch != null) { 119 | break; 120 | } 121 | 122 | considerPrivateMethods = false; 123 | } while ((clz = clz.getSuperclass()) != null); 124 | 125 | if (fuzzyMatch != null) { 126 | fuzzyMatch.setAccessible(true); 127 | } 128 | methodCache.put(fullMethodName, fuzzyMatch); 129 | return fuzzyMatch; 130 | } 131 | 132 | private static String getParametersString(Class... clazzes) { 133 | StringBuilder sb = new StringBuilder("("); 134 | boolean first = true; 135 | for (Class clazz : clazzes) { 136 | if (first) 137 | first = false; 138 | else 139 | sb.append(","); 140 | 141 | if (clazz != null) 142 | sb.append(clazz.getCanonicalName()); 143 | else 144 | sb.append("null"); 145 | } 146 | sb.append(")"); 147 | return sb.toString(); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/res/color/tag_view_text_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tag_view_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_hitokoto_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 29 | 30 | 42 | 43 | 52 | 53 | 64 | 65 | 77 | 78 | 87 | 88 | 99 | 100 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preference_category.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/layout/tag_view.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 20 | 21 | 25 | 26 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-sw360dp-v13/values-preference.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 0dp 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | #90008577 8 | 9 | #FFFFFF 10 | #CDCDCD 11 | #000000 12 | #BDBDBD 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #7C24AB 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/xml/dropdown_statusbar_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 15 | 16 | 21 | 22 | 27 | 28 | 32 | 33 | 38 | 39 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/xml/keyguard_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 16 | 17 | 18 | 19 | 24 | 25 | 29 | 30 | 38 | 39 | 44 | 45 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/xml/main_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 33 | 34 | 38 | 39 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/xml/statusbar_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 17 | 18 | 22 | 23 | 28 | 29 | 33 | 34 | 40 | 41 | 46 | 47 | 51 | 52 | 56 | 57 | 61 | 62 | 66 | 67 | 71 | 72 | 77 | 78 | 83 | 84 | -------------------------------------------------------------------------------- /art/cn/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/art/cn/01.png -------------------------------------------------------------------------------- /art/cn/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/art/cn/02.png -------------------------------------------------------------------------------- /art/en/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/art/en/01.png -------------------------------------------------------------------------------- /art/en/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/art/en/02.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.4.31' 5 | ext.and_res_guard_version = '1.2.20' 6 | 7 | repositories { 8 | google() 9 | jcenter() 10 | 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:4.1.0' 14 | 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | // classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.0' 18 | 19 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 20 | classpath "com.tencent.mm:AndResGuard-gradle-plugin:$and_res_guard_version" // AndResGuard 21 | } 22 | } 23 | 24 | allprojects { 25 | repositories { 26 | google() 27 | jcenter() 28 | 29 | } 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /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=-Xmx1536m 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 | # Custom Properties start 15 | tianma.keystore.path=/Users/tianma/Codes/Keystore/tianma.jks 16 | tianma.signature.path=/Users/tianma/Codes/Keystore/signature.properties 17 | # Custom Properties end 18 | 19 | # 表示使用 androidx 20 | android.useAndroidX=true 21 | # 表示将第三方库迁移到 androidx 22 | android.enableJetifier=true 23 | # 禁用R8 Android code shrinker 24 | # android.enableR8=true 25 | 26 | android.injected.testOnly=false 27 | 28 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tianma8023/XMiTools/16e2f92e1e2863de79b8dd5360a05595828dd1f6/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 04 21:42:26 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------