├── .github ├── ISSUE_TEMPLATE.md ├── dependabot.yml └── workflows │ └── Android.yml ├── .gitignore ├── .gitmodules ├── EULA.md ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── xposed_init │ ├── java │ ├── cn │ │ └── fuckhome │ │ │ └── xiaowine │ │ │ ├── activity │ │ │ └── SettingsActivity.kt │ │ │ ├── config │ │ │ └── Config.kt │ │ │ ├── hook │ │ │ ├── BaseHook.kt │ │ │ ├── MainHook.kt │ │ │ └── module │ │ │ │ ├── add │ │ │ │ └── Info.kt │ │ │ │ └── modify │ │ │ │ ├── HideAppIcon.kt │ │ │ │ ├── HideAppName.kt │ │ │ │ ├── HideSmallWindow.kt │ │ │ │ ├── HideStatusBarWhenEnterResents.kt │ │ │ │ ├── RemoveSmallWindowRestriction1.kt │ │ │ │ ├── RemoveSmallWindowRestriction2.kt │ │ │ │ ├── RemoveSmallWindowRestriction3.kt │ │ │ │ ├── ShortcutItemCount.kt │ │ │ │ ├── ShortcutSmallWindow.kt │ │ │ │ ├── UnlockGrids.kt │ │ │ │ ├── UnlockHotseatIcon.kt │ │ │ │ ├── UnlockNavType.kt │ │ │ │ └── UnlockPad.kt │ │ │ └── utils │ │ │ ├── ActivityOwnSP.kt │ │ │ ├── ActivityUtils.kt │ │ │ ├── BackupUtils.kt │ │ │ ├── ConfigUtils.kt │ │ │ ├── FileUtils.kt │ │ │ ├── KotlinXposedHelper.kt │ │ │ ├── LogUtils.kt │ │ │ ├── MemoryUtils.kt │ │ │ └── Utils.kt │ └── com │ │ └── jaredrummler │ │ └── ktsh │ │ └── Shell.kt │ └── res │ ├── drawable │ ├── header_xiaowine.webp │ ├── ic_launcher_fore.xml │ ├── ic_small_window_dark.xml │ └── ic_small_window_light.xml │ ├── mipmap-anydpi │ └── ic_launcher.xml │ ├── values-night │ ├── colors.xml │ └── styles.xml │ ├── values-ru-rRU │ └── strings.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values-zh-rHK │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ └── values │ ├── arrays.xml │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 17 | 18 | **环境信息** 19 | 20 | 模块版本: 21 | Android版本: 22 | 屏幕类型: 23 | 24 | **在最新的release版本中能否复现** 25 | 26 | **bug内容** 27 | 28 | 29 | **复现方法** 30 | 31 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/Android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | workflow_dispatch: 7 | inputs: 8 | run: 9 | description: "" 10 | required: true 11 | default: run 12 | type: choice 13 | options: 14 | - run 15 | 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: set up JDK 17 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: '17' 27 | distribution: 'adopt' 28 | cache: gradle 29 | 30 | - uses: actions/cache@v2 31 | with: 32 | path: | 33 | ~/.gradle/caches 34 | ~/.gradle/wrapper 35 | !~/.gradle/caches/build-cache-* 36 | key: gradle-deps-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 37 | restore-keys: gradle-deps 38 | 39 | - name: Grant execute permission for gradlew 40 | run: chmod +x gradlew 41 | 42 | - name: Clone UI 43 | run: | 44 | cd blockmiui 45 | git submodule init 46 | git submodule update 47 | - name: Clone TOAST 48 | run: | 49 | cd xtoast 50 | git submodule init 51 | git submodule update 52 | 53 | - name: Build with Gradle 54 | run: | 55 | bash ./gradlew assembleRelease 56 | bash ./gradlew assembleDebug 57 | 58 | - name: Sign Release APK 59 | if: success() 60 | id: sign_release 61 | uses: r0adkll/sign-android-release@v1.0.4 62 | with: 63 | releaseDirectory: ./app/build/outputs/apk/release 64 | signingKeyBase64: ${{ secrets.SIGNING_KEY }} 65 | alias: xiao_wine 66 | keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} 67 | keyPassword: ${{ secrets.KEY_STORE_PASSWORD }} 68 | 69 | - name: Sign Debug APK 70 | if: success() 71 | id: sign_debug 72 | uses: r0adkll/sign-android-release@v1.0.4 73 | with: 74 | releaseDirectory: ./app/build/outputs/apk/debug 75 | signingKeyBase64: ${{ secrets.SIGNING_KEY }} 76 | alias: xiao_wine 77 | keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} 78 | keyPassword: ${{ secrets.KEY_STORE_PASSWORD }} 79 | 80 | - name: Upload Release APK 81 | uses: actions/upload-artifact@v2 82 | with: 83 | name: InkHome_release 84 | path: ${{ steps.sign_release.outputs.signedReleaseFile }} 85 | 86 | - name: Upload Debug APK 87 | uses: actions/upload-artifact@v2 88 | with: 89 | name: InkHome_debug 90 | path: ${{ steps.sign_debug.outputs.signedReleaseFile }} 91 | 92 | - name: Upload Release Mapping 93 | uses: actions/upload-artifact@v2 94 | with: 95 | name: InkHone_release_mapping 96 | path: ./app/build/outputs/mapping/release/mapping.txt 97 | 98 | 99 | - name: Post to channel 100 | if: contains(github.event.head_commit.message, '[skip post]') == false 101 | env: 102 | CHANNEL_ID: ${{ secrets.CHANNEL_ID }} 103 | BOT_TOKEN: ${{ secrets.BOT_TOKEN }} 104 | RELEASE: ${{ steps.sign_release.outputs.signedReleaseFile }} 105 | DEBUG: ${{ steps.sign_debug.outputs.signedReleaseFile }} 106 | COMMIT_MESSAGE: |+ 107 | Github CI 108 | ``` 109 | ${{ github.event.head_commit.message }} 110 | ``` 111 | run: | 112 | ESCAPED=`python3 -c 'import json,os,urllib.parse; print(urllib.parse.quote(json.dumps(os.environ["COMMIT_MESSAGE"])))'` 113 | curl -v "https://api.telegram.org/bot${BOT_TOKEN}/sendMediaGroup?chat_id=${CHANNEL_ID}&media=%5B%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Frelease%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fdebug%22%2C%22parse_mode%22%3A%22MarkdownV2%22%2C%22caption%22%3A${ESCAPED}%7D%5D" -F release="@$RELEASE" -F debug="@$DEBUG" 114 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "blockmiui"] 2 | path = blockmiui 3 | url = https://github.com/Block-Network/blockmiui 4 | [submodule "xtoast"] 5 | path = xtoast 6 | url = https://github.com/Wine-Network/xToast 7 | -------------------------------------------------------------------------------- /EULA.md: -------------------------------------------------------------------------------- 1 | ## 简言之 2 | 3 | - 不得用于商业用途。 4 | - 不得以此模块牟利。 5 | - 二改后不得给他人使用。 6 | - 不得去除本模块的版权声明。 7 | - 自行承担一切使用此模块造成的意外和风险。 8 | - 为了安全考虑,本应用会上传使用者的部分相关信息。 9 | - 最终解释权归本模块作者所有。 10 | - 未尽之处,以下方「状态栏歌词 最终用户许可协议与隐私条款」为准。 11 | 如果您还有疑问,请阅读下方文本。 12 | 13 | --- 14 | 15 | # 最终用户许可协议与隐私条款 16 | 17 | *版本 1.1.1 (2022.1.12)* 18 | 19 | 请您务必仔细阅读和理解 最终用户许可协议(以下简称「本协议」或「此协议」)与隐私条款。在开始之前,您需要决定是否同意本协议与隐私条款。除非您理解并接受此协议与隐私条款,否则此模块不得在您的任何终端上安装或使用。 20 | 21 | 您一旦同意此协议与隐私条款,就表明您同意接受本协议与隐私条款的所有约束。如您不同意此协议与隐私条款,您应当立即停止使用并卸载此模块。 22 | 23 | ## 您可以 24 | 25 | - 在一台个人所有的终端上安装、使用、运行本模块的一份副本。本模块仅供个人所有终端使用,不得用于法人或其他组织(包括但不限于政府机关、公司、企事业单位和其他组织,无论该组织是否为经济性组织,无论该组织的使用是否构成商业目的)所有终端。如若个人所有终端长期固定为法人或其他组织服务,则将被视为「法人或其他组织所有终端」。任何超出上述授权范围的使用均被视为非法复制的盗版行为,本模块作者保留权利要求相关责任人承担相应的法律责任,包括但不限于民事责任、行政责任、刑事责任。 26 | - 为了防止副本损坏而制作备份复制品。这些备份复制品不得通过任何方式提供给他人使用,且在您丧失该合法副本的所有权时,有义务将备份复制品销毁。 27 | - 为了把本模块用于实际的终端环境或者改进其功能、性能而进行必要的修改。但是,除本协议另有约定外,未经本模块作者书面同意,不得向第三方以任何形式提供修改后的模块。 28 | - 对本模块进行以学习目的的反向工程、反向编译、反汇编或其他获得本模块源代码、运行逻辑的行为。(显然,在 Github 获取本模块的全部源代码是一种更为明智的行为) 29 | 30 | ## 您保证 31 | 32 | - 不得出售、贩发出租或以其他方式传播本模块以获利。 33 | - 不得以任何方式商用本模块,包括但不限于使用本模块发送广告或出售、二次贩卖、发行、出租本模块。 34 | - 在本模块的所有副本上包含所有的版权标识与许可证。 35 | 36 | ## 权利的保留 37 | 38 | - 未明示授予的一切权利均为本模块作者所有。 39 | - 最终解释权归本模块作者所有。 40 | 41 | ## 本模块的著作权 42 | 43 | - 您不得去掉本模块上的任何版权标识,并应在其所有复制品上依照现有的表述方式标注其版权归属本模块作者。 44 | - 本模块(包括但不限于本模块中所含的任何代码、图像、动画、视频、音乐、文字和附加程序)、随附的印刷材料、宣传材料及任何副本的著作权,均由本模块作者拥有。 45 | - 您不可以从本模块中去掉版权声明,并保证为本模块的复制品(全部或部分)复制版权声明。 46 | 47 | ## 免责声明 48 | 49 | - 用户在下载或使用本模块时均被视为已经仔细阅读本协议并完全同意。凡以任何方式激活本模块,或以任何方式(包括但不限于直接、间接)使用本模块,均被视为自愿接受相关声明和用户许可的约束。 50 | - 本模块仅供用户做测试或娱乐使用,不得用于非法用途;一切自行用于其它用途的用户,本模块概不承担任何责任。 51 | - 用户使用本模块的过程中,如果侵犯了第三方知识产权或其他权利,责任由使用人本人承担,本模块对此不承担任何责任。 52 | - 用户明确并同意其使用本模块所存在的风险将完全由其本人承担,因其实用模块而产生的一切后果也由其本人承担,本模块对此不承担任何责任。 53 | - 除本模块注明的条款外,其它因不当使用本模块而导致的任何意外、疏忽、合约损坏、诽谤、版权或其他知识产权侵犯及其所造成的任何损失,本模块概不负责,亦不承担任何责任。 54 | - 对于因不可抗力、黑客攻击、通讯线路中断等本模块不能控制的原因或其他缺陷,导致用户不能正常使用本模块的,本模块不承担任何责任。 55 | - 本模块未涉及的问题请参见国家有关法律法规,当本声明与国家有关法律法规冲突时,以国家法律为准。 56 | 57 | ## 隐私保护政策与条款 58 | 59 | 首先感谢您使用本应用!本应用非常重视用户的隐私,将保护用户隐私视为基本原则并将其贯彻到底。 60 | 61 | 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### MIUI桌面增强模块 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | import com.android.build.gradle.internal.api.BaseVariantOutputImpl 4 | 5 | plugins { 6 | id("com.android.application") 7 | id("kotlin-android") 8 | } 9 | 10 | android { 11 | compileSdk = 34 12 | namespace = "cn.fuckhome.xiaowine" 13 | val buildTime = System.currentTimeMillis() 14 | defaultConfig { 15 | applicationId = "cn.fuckhome.xiaowine" 16 | minSdk = 26 17 | targetSdk = 34 18 | versionCode = 13 19 | versionName = "2.3.4" 20 | aaptOptions.cruncherEnabled = false 21 | aaptOptions.useNewCruncher = false 22 | buildConfigField("String", "BUILD_TIME", "\"$buildTime\"") 23 | } 24 | 25 | buildTypes { 26 | release { 27 | isMinifyEnabled = true 28 | isShrinkResources = true 29 | setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro", "proguard-log.pro")) 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility = JavaVersion.VERSION_17 34 | targetCompatibility = JavaVersion.VERSION_17 35 | } 36 | kotlinOptions { 37 | jvmTarget = JavaVersion.VERSION_17.majorVersion 38 | } 39 | packagingOptions { 40 | resources { 41 | excludes += "/META-INF/**" 42 | excludes += "/kotlin/**" 43 | excludes += "/*.txt" 44 | excludes += "/*.bin" 45 | } 46 | dex { 47 | useLegacyPackaging = true 48 | } 49 | } 50 | buildFeatures { 51 | viewBinding = true 52 | buildConfig = true 53 | } 54 | applicationVariants.all { 55 | outputs.all { 56 | (this as BaseVariantOutputImpl).outputFileName = "Fuck_Home-$versionName($versionCode)-$name-$buildTime.apk" 57 | } 58 | } 59 | } 60 | 61 | 62 | dependencies { 63 | implementation("com.github.kyuubiran:EzXHelper:1.0.3") 64 | compileOnly("de.robv.android.xposed:api:82") 65 | implementation(project(":blockmiui")) 66 | implementation(project(":xtoast")) 67 | } 68 | -------------------------------------------------------------------------------- /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 | -optimizationpasses 5 24 | 25 | 26 | 27 | -keep public class cn.fuckhome.xiaowine.hook.MainHook 28 | 29 | -keep class cn.fuckhome.xiaowine.hook.MainHook { 30 | (); 31 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 41 | 44 | 47 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | cn.fuckhome.xiaowine.hook.MainHook -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/activity/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.activity 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.* 5 | import android.content.pm.PackageManager 6 | import android.graphics.Color 7 | import android.os.Build 8 | import android.os.Bundle 9 | import android.os.Handler 10 | import android.os.Looper 11 | import android.view.Gravity 12 | import cn.aodlyric.xiaowine.utils.ActivityUtils 13 | import cn.fkj233.ui.activity.MIUIActivity 14 | import cn.fkj233.ui.activity.view.SpinnerV 15 | import cn.fkj233.ui.activity.view.TextV 16 | import cn.fkj233.ui.dialog.MIUIDialog 17 | import cn.fuckhome.xiaowine.BuildConfig 18 | import cn.fuckhome.xiaowine.R 19 | import cn.fuckhome.xiaowine.utils.ActivityOwnSP.ownSPConfig as config 20 | import cn.fuckhome.xiaowine.utils.ActivityOwnSP 21 | import cn.fuckhome.xiaowine.utils.BackupUtils 22 | import cn.fuckhome.xiaowine.utils.FileUtils 23 | import cn.fuckhome.xiaowine.utils.Utils 24 | import cn.fuckhome.xiaowine.utils.Utils.isNotNull 25 | import com.jaredrummler.ktsh.Shell 26 | import java.text.SimpleDateFormat 27 | import java.util.* 28 | import kotlin.system.exitProcess 29 | 30 | class SettingsActivity : MIUIActivity() { 31 | private val activity = this 32 | private val openFontFile = 1511 33 | 34 | init { 35 | initView { 36 | registerMain(getString(R.string.AppName), false) { 37 | TextS(textId = R.string.MainSwitch, key = "MainSwitch") 38 | Line() 39 | TextA(textId = R.string.AddInformation, onClickListener = { showFragment("AddInformation") }) 40 | TextA(textId = R.string.AddInformationStyle, onClickListener = { showFragment("AddInformationStyle") }) 41 | Line() 42 | TextA(textId = R.string.FunModify, onClickListener = { showFragment("Unrestricted") }) 43 | Line() 44 | TextA(textId = R.string.About, onClickListener = { showFragment("About") }) 45 | Text() 46 | } 47 | registerMenu(getString(R.string.Menu)) { 48 | TextS(textId = R.string.HideDeskIcon, key = "hLauncherIcon", onClickListener = { 49 | packageManager.setComponentEnabledSetting(ComponentName(activity, "${BuildConfig.APPLICATION_ID}.launcher"), if (it) { 50 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED 51 | } else { 52 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED 53 | }, PackageManager.DONT_KILL_APP) 54 | }) 55 | TextS(textId = R.string.DebugMode, key = "Debug") 56 | TextA(textId = R.string.ResetModule, onClickListener = { 57 | MIUIDialog(activity) { 58 | setTitle(R.string.ResetModuleDialog) 59 | setMessage(R.string.ResetModuleDialogTips) 60 | setLButton(R.string.Ok) { 61 | config.clear() 62 | ActivityUtils.showToastOnLooper(activity, activity.getString(R.string.ResetSuccess)) 63 | activity.finishActivity(0) 64 | dismiss() 65 | } 66 | setRButton(R.string.Cancel) { dismiss() } 67 | }.show() 68 | }) 69 | TextA(textId = R.string.ReStartHome, onClickListener = { 70 | Thread { Shell("su").run("am force-stop com.miui.home") }.start() 71 | }) 72 | TextA(textId = R.string.Backup, onClickListener = { BackupUtils.backup(activity, ActivityOwnSP.ownSP) }) 73 | TextA(textId = R.string.Recovery, onClickListener = { BackupUtils.recovery(activity, ActivityOwnSP.ownSP) }) 74 | Line() 75 | TextSummary(textId = R.string.ModulePackName, tips = BuildConfig.APPLICATION_ID) 76 | TextSummary(textId = R.string.ModuleVersion, tips = "${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE})-${BuildConfig.BUILD_TYPE}") 77 | val buildTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(BuildConfig.BUILD_TIME.toLong()) 78 | TextSummary(textId = R.string.BuildTime, tips = buildTime) 79 | Text() 80 | } 81 | register("About", getString(R.string.About), true) { 82 | TitleText(textId = R.string.Author) 83 | Author(getDrawable(R.drawable.header_xiaowine)!!, "xiaowine", getString(R.string.AboutTips), onClickListener = { ActivityUtils.openUrl(activity, "https://github.com/xiaowine") }) 84 | TextA("Coolapk", onClickListener = { 85 | ActivityUtils.openUrl(activity, "https://www.coolapk.com/apk/cn.fuckhome.xiaowine") 86 | }) 87 | Line() 88 | TextWithSpinner(TextV(textId = R.string.ThkListTips), SpinnerV("") { 89 | add("Xposed") { ActivityUtils.openUrl(activity, "https://github.com/rovo89/Xposed") } 90 | add("LSPosed") { ActivityUtils.openUrl(activity, "https://github.com/LSPosed/LSPosed") } 91 | add("blockmiui") { ActivityUtils.openUrl(activity, "https://github.com/Block-Network/blockmiui") } 92 | add("EzXHelper") { ActivityUtils.openUrl(activity, "https://github.com/KyuubiRan/EzXHelper") } 93 | }) 94 | Text() 95 | } 96 | register("AddInformation", getString(R.string.AddInformation), false) { 97 | TextS(textId = R.string.Memory, key = "MemoryView") 98 | TextS(textId = R.string.Zarm, key = "ZarmView") 99 | TextS(textId = R.string.Storage, key = "StorageView") 100 | TextS(textId = R.string.Uptime, key = "Uptime") 101 | TextS(textId = R.string.RunningAppTotal, key = "RunningAppTotal") 102 | TextS(textId = R.string.RunningServiceTotal, key = "RunningServiceTotal") 103 | TextS(textId = R.string.warning, key = "Warning") 104 | TextS(textId = R.string.CleanMode, key = "CleanMode") 105 | } 106 | register("AddInformationStyle", getString(R.string.AddInformationStyle), false) { 107 | TextA(textId = R.string.Color, onClickListener = { 108 | MIUIDialog(activity) { 109 | setTitle(R.string.Color) 110 | setMessage(R.string.LyricColorTips) 111 | setEditText(config.getColor(), "") 112 | setRButton(R.string.Ok) { 113 | if (getEditText().isNotEmpty()) { 114 | try { 115 | Color.parseColor(getEditText()) 116 | config.setColor(getEditText()) 117 | dismiss() 118 | return@setRButton 119 | } catch (_: Throwable) { 120 | } 121 | } 122 | ActivityUtils.showToastOnLooper(activity, getString(R.string.LyricColorError)) 123 | config.setColor("") 124 | dismiss() 125 | } 126 | setLButton(R.string.Cancel) { dismiss() } 127 | }.show() 128 | }) 129 | TextA(textId = R.string.BackgroundColor, onClickListener = { 130 | MIUIDialog(activity) { 131 | setTitle(R.string.BackgroundColor) 132 | setMessage(R.string.LyricColorTips) 133 | setEditText(config.getBgColor(), "#00000000") 134 | setRButton(R.string.Ok) { 135 | if (getEditText().isNotEmpty()) { 136 | try { 137 | Color.parseColor(getEditText()) 138 | config.setBgColor(getEditText()) 139 | dismiss() 140 | return@setRButton 141 | } catch (_: Throwable) { 142 | } 143 | } 144 | ActivityUtils.showToastOnLooper(activity, getString(R.string.LyricColorError)) 145 | config.setBgColor("#00000000") 146 | dismiss() 147 | } 148 | setLButton(R.string.Cancel) { dismiss() } 149 | }.show() 150 | }) 151 | TextA(textId = R.string.BgCorners, onClickListener = { 152 | MIUIDialog(activity) { 153 | setTitle(R.string.BgCorners) 154 | setMessage(R.string.BgCornersTips) 155 | setEditText(config.getBgCorners().toString(), "0") 156 | setRButton(R.string.Ok) { 157 | if (getEditText().isNotEmpty()) { 158 | try { 159 | val value = getEditText().toInt() 160 | if (value in (0..100)) { 161 | config.setBgCorners(value) 162 | dismiss() 163 | return@setRButton 164 | } 165 | } catch (_: Throwable) { 166 | } 167 | } 168 | ActivityUtils.showToastOnLooper(activity, getString(R.string.InputError)) 169 | config.setBgCorners(0) 170 | dismiss() 171 | } 172 | setLButton(R.string.Cancel) { dismiss() } 173 | }.show() 174 | }) 175 | TextA(textId = R.string.CustomFont, onClickListener = { 176 | MIUIDialog(activity) { 177 | setTitle(R.string.CustomFont) 178 | setRButton(R.string.ChooseFont) { 179 | val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) 180 | intent.addCategory(Intent.CATEGORY_OPENABLE) 181 | intent.type = "*/*" 182 | intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) 183 | startActivityForResult(intent, openFontFile) 184 | dismiss() 185 | } 186 | setLButton(R.string.Reset) { 187 | application.sendBroadcast(Intent().apply { 188 | action = "MIUIHOME_Server" 189 | putExtra("Type", "delete_font") 190 | }) 191 | dismiss() 192 | } 193 | }.show() 194 | }) 195 | val dict: HashMap = hashMapOf() 196 | dict[Gravity.CENTER] = getString(R.string.CENTER) 197 | dict[Gravity.START] = getString(R.string.START) 198 | dict[Gravity.END] = getString(R.string.END) 199 | TextWithSpinner(TextV(textId = R.string.Gravity), SpinnerV(dict[Gravity.START]!!) { 200 | dict.forEach { (key, value) -> add(value) { config.setGravity(key) } } 201 | }) 202 | var marginTips = if (config.getUnit()) R.string.MarginTips1 else R.string.MarginTips2 203 | var marginRange = if (config.getUnit()) (-100..100) else (-2000..2000) 204 | val unit: HashMap = hashMapOf() 205 | unit[true] = getString(R.string.Scale) 206 | unit[false] = getString(R.string.Pixel) 207 | TextWithSpinner(TextV(textId = R.string.UnitMargin), SpinnerV(unit[config.getUnit()]!!) { 208 | unit.forEach { (key, value) -> 209 | add(value) { 210 | config.setUnit(key) 211 | marginTips = if (config.getUnit()) R.string.MarginTips1 else R.string.MarginTips2 212 | marginRange = if (config.getUnit()) (-100..100) else (-2000..2000) 213 | } 214 | } 215 | }) 216 | 217 | TextA(textId = R.string.LeftMargin0, onClickListener = { 218 | MIUIDialog(activity) { 219 | setTitle(R.string.LeftMargin0) 220 | setMessage(marginTips) 221 | setEditText(config.getInt("LeftMargin0").toString(), "0") 222 | setRButton(R.string.Ok) { 223 | if (getEditText().isNotEmpty()) { 224 | try { 225 | val value = getEditText().toInt() 226 | if (value in marginRange) { 227 | config.setValue("LeftMargin0", value) 228 | dismiss() 229 | return@setRButton 230 | } 231 | } catch (_: Throwable) { 232 | } 233 | } 234 | ActivityUtils.showToastOnLooper(activity, getString(R.string.InputError)) 235 | config.setValue("LeftMargin0", 0) 236 | dismiss() 237 | } 238 | setLButton(R.string.Cancel) { dismiss() } 239 | }.show() 240 | }) 241 | TextA(textId = R.string.TopMargin0, onClickListener = { 242 | MIUIDialog(activity) { 243 | setTitle(R.string.TopMargin0) 244 | setMessage(marginTips) 245 | setEditText(config.getInt("TopMargin0", 4).toString(), "0") 246 | setRButton(R.string.Ok) { 247 | if (getEditText().isNotEmpty()) { 248 | try { 249 | val value = getEditText().toInt() 250 | if (value in marginRange) { 251 | config.setValue("TopMargin0", value) 252 | dismiss() 253 | return@setRButton 254 | } 255 | } catch (_: Throwable) { 256 | } 257 | } 258 | ActivityUtils.showToastOnLooper(activity, getString(R.string.InputError)) 259 | config.setValue("TopMargin0", 0) 260 | dismiss() 261 | } 262 | setLButton(R.string.Cancel) { dismiss() } 263 | }.show() 264 | }) 265 | TextA(textId = R.string.LeftMargin1, onClickListener = { 266 | MIUIDialog(activity) { 267 | setTitle(R.string.LeftMargin1) 268 | setMessage(marginTips) 269 | setEditText(config.getInt("LeftMargin1").toString(), "0") 270 | setRButton(R.string.Ok) { 271 | if (getEditText().isNotEmpty()) { 272 | try { 273 | val value = getEditText().toInt() 274 | if (value in marginRange) { 275 | config.setValue("LeftMargin1", value) 276 | dismiss() 277 | return@setRButton 278 | } 279 | } catch (_: Throwable) { 280 | } 281 | } 282 | ActivityUtils.showToastOnLooper(activity, getString(R.string.InputError)) 283 | config.setValue("LeftMargin1", 0) 284 | dismiss() 285 | } 286 | setLButton(R.string.Cancel) { dismiss() } 287 | }.show() 288 | }) 289 | TextA(textId = R.string.TopMargin1, onClickListener = { 290 | MIUIDialog(activity) { 291 | setTitle(R.string.TopMargin1) 292 | setMessage(marginTips) 293 | setEditText(config.getInt("TopMargin1", 4).toString(), "0") 294 | setRButton(R.string.Ok) { 295 | if (getEditText().isNotEmpty()) { 296 | try { 297 | val value = getEditText().toInt() 298 | if (value in marginRange) { 299 | config.setValue("TopMargin1", value) 300 | dismiss() 301 | return@setRButton 302 | } 303 | } catch (_: Throwable) { 304 | } 305 | } 306 | ActivityUtils.showToastOnLooper(activity, getString(R.string.InputError)) 307 | config.setValue("TopMargin1", 0) 308 | dismiss() 309 | } 310 | setLButton(R.string.Cancel) { dismiss() } 311 | }.show() 312 | }) 313 | } 314 | register("Unrestricted", getString(R.string.FunModify), false) { 315 | TextS(textId = R.string.Pad, key = "Pad") 316 | TextS(textId = R.string.Shortcuts, key = "Shortcuts") 317 | TextS(textId = R.string.UnlockGrids, key = "UnlockGrids") 318 | TextS(textId = R.string.UnlockHotseat, key = "UnlockHotseat") 319 | TextS(textId = R.string.ShortcutSmallWindow, key = "ShortcutSmallWindow") 320 | TextS(textId = R.string.HideStatusBar, key = "HideStatusBar") 321 | TextS(textId = R.string.HideAppName, key = "HideAppName") 322 | TextS(textId = R.string.HideAppIcon, key = "HideAppIcon") 323 | TextS(textId = R.string.HideSmallWindow, key = "HideSmallWindow") 324 | TextS(textId = R.string.RemoveSmallWindowRestriction, key = "RemoveSmallWindowRestriction") 325 | TextS(textId = R.string.UnlockNavType, key = "UnlockNavType") 326 | } 327 | } 328 | } 329 | 330 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 331 | if (data.isNotNull() && resultCode == RESULT_OK) { 332 | when (requestCode) { 333 | BackupUtils.CREATE_DOCUMENT_CODE -> { 334 | BackupUtils.handleCreateDocument(activity, data!!.data) 335 | } 336 | 337 | BackupUtils.OPEN_DOCUMENT_CODE -> { 338 | BackupUtils.handleReadDocument(activity, data!!.data) 339 | } 340 | 341 | openFontFile -> { 342 | data!!.data?.let { 343 | activity.sendBroadcast(Intent().apply { 344 | action = "MIUIHOME_Server" 345 | putExtra("Type", "copy_font") 346 | putExtra("Font_Path", FileUtils(activity).getFilePathByUri(it)) 347 | }) 348 | } 349 | } 350 | } 351 | } 352 | } 353 | 354 | 355 | @SuppressLint("UnspecifiedRegisterReceiverFlag") 356 | override fun onCreate(savedInstanceState: Bundle?) { 357 | ActivityOwnSP.activity = this 358 | if (!checkLSPosed()) isLoad = false 359 | super.onCreate(savedInstanceState) 360 | if (isLoad) { 361 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 362 | registerReceiver(AppReceiver(), IntentFilter().apply { addAction("MIUIHOME_App_Server") }, RECEIVER_NOT_EXPORTED) 363 | }else{ 364 | registerReceiver(AppReceiver(), IntentFilter().apply { addAction("MIUIHOME_App_Server") }) 365 | } 366 | if (BuildConfig.DEBUG) { 367 | config.setValue("MemoryView", true) 368 | config.setValue("ZarmView", true) 369 | config.setValue("MainSwitch", true) 370 | config.setValue("Debug", true) 371 | } 372 | } 373 | } 374 | 375 | private fun checkLSPosed(): Boolean { 376 | return try { 377 | Utils.getSP(this, "Fuck_Home_Config")?.let { setSP(it) } 378 | true 379 | } catch (e: Exception) { 380 | e.printStackTrace() 381 | MIUIDialog(activity) { 382 | setTitle(R.string.Tips) 383 | setMessage(R.string.NotSupport) 384 | setRButton(R.string.Restart) { 385 | val intent = packageManager.getLaunchIntentForPackage(packageName) 386 | intent!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 387 | startActivity(intent) 388 | exitProcess(0) 389 | } 390 | setCancelable(false) 391 | }.show() 392 | false 393 | } 394 | } 395 | 396 | inner class AppReceiver : BroadcastReceiver() { 397 | override fun onReceive(context: Context, intent: Intent) { 398 | try { 399 | Handler(Looper.getMainLooper()).post { 400 | when (intent.getStringExtra("app_Type")) { 401 | "CopyFont" -> { 402 | val message: String = if (intent.getBooleanExtra("CopyFont", false)) { 403 | getString(R.string.CustomFontSuccess) 404 | } else { 405 | getString(R.string.CustomFontFail) + "\n" + intent.getStringExtra("font_error") 406 | } 407 | MIUIDialog(activity) { 408 | setTitle(getString(R.string.CustomFont)) 409 | setMessage(message) 410 | setRButton(getString(R.string.Ok)) { dismiss() } 411 | }.show() 412 | } 413 | 414 | "DeleteFont" -> { 415 | val message: String = if (intent.getBooleanExtra("DeleteFont", false)) { 416 | getString(R.string.DeleteFontSuccess) 417 | } else { 418 | getString(R.string.DeleteFontFail) 419 | } 420 | MIUIDialog(activity) { 421 | setTitle(getString(R.string.DeleteFont)) 422 | setMessage(message) 423 | setRButton(getString(R.string.Ok)) { dismiss() } 424 | }.show() 425 | } 426 | } 427 | } 428 | } catch (_: Throwable) { 429 | } 430 | } 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/config/Config.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.config 2 | 3 | 4 | import android.content.SharedPreferences 5 | import android.view.Gravity 6 | import cn.fuckhome.xiaowine.utils.ConfigUtils 7 | import de.robv.android.xposed.XSharedPreferences 8 | 9 | class Config { 10 | private var config: ConfigUtils 11 | 12 | constructor(xSharedPreferences: XSharedPreferences?) { 13 | config = ConfigUtils(xSharedPreferences) 14 | } 15 | 16 | constructor(sharedPreferences: SharedPreferences) { 17 | config = ConfigUtils(sharedPreferences) 18 | } 19 | 20 | fun update() { 21 | config.update() 22 | } 23 | 24 | 25 | 26 | fun setColor(str: String) { 27 | config.put("Color", str) 28 | } 29 | 30 | fun getColor(): String { 31 | return config.optString("Color", "") 32 | } 33 | fun setBgColor(str: String) { 34 | config.put("BgColor", str) 35 | } 36 | 37 | fun getBgColor(): String { 38 | return config.optString("BgColor", "#00000000") 39 | } 40 | fun setBgCorners(i: Int) { 41 | config.put("BgCorners", i) 42 | } 43 | 44 | fun getBgCorners(): Int { 45 | return config.optInt("BgCorners", 30) 46 | } 47 | fun setGravity(i: Int) { 48 | config.put("Gravity", i) 49 | } 50 | fun getGravity(): Int { 51 | return config.optInt("Gravity", Gravity.START) 52 | } 53 | fun setUnit(b: Boolean) { 54 | config.put("Unit", b) 55 | } 56 | fun getUnit(): Boolean { 57 | return config.optBoolean("Unit", true) 58 | } 59 | 60 | fun getString(key: String, def: String = ""): String { 61 | return config.optString(key, def) 62 | } 63 | 64 | fun getBoolean(key: String, def: Boolean = false): Boolean { 65 | return config.optBoolean(key, def) 66 | } 67 | 68 | fun getInt(key: String, def: Int = 0): Int { 69 | return config.optInt(key, def) 70 | } 71 | 72 | 73 | fun setValue(key: String, value: Any) { 74 | config.put(key, value) 75 | } 76 | 77 | 78 | fun clear() { 79 | config.clearConfig() 80 | } 81 | 82 | 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/BaseHook.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook 2 | 3 | abstract class BaseHook { 4 | var isInit: Boolean = false 5 | abstract fun init() 6 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/MainHook.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import cn.fuckhome.xiaowine.R 6 | import cn.fuckhome.xiaowine.hook.module.add.Info 7 | import cn.fuckhome.xiaowine.hook.module.modify.* 8 | import cn.fuckhome.xiaowine.utils.LogUtils 9 | import cn.fuckhome.xiaowine.utils.Utils.XConfig 10 | import cn.fuckhome.xiaowine.utils.hookBeforeMethod 11 | import com.github.kyuubiran.ezxhelper.init.EzXHelperInit 12 | import com.github.kyuubiran.ezxhelper.init.EzXHelperInit.initHandleLoadPackage 13 | import com.github.kyuubiran.ezxhelper.init.EzXHelperInit.setEzClassLoader 14 | import com.github.kyuubiran.ezxhelper.init.InitFields 15 | import com.github.kyuubiran.ezxhelper.utils.Log.logexIfThrow 16 | import de.robv.android.xposed.IXposedHookLoadPackage 17 | import de.robv.android.xposed.IXposedHookZygoteInit 18 | import de.robv.android.xposed.callbacks.XC_LoadPackage 19 | 20 | private const val PACKAGE_MIUI_HOME = "com.miui.home" 21 | private const val PACKAGE_POCO_HOME = "com.mi.android.globallauncher" 22 | val homeList = arrayOf(PACKAGE_POCO_HOME, PACKAGE_MIUI_HOME) 23 | 24 | private const val PACKAGE_SystemUi = "com.android.systemui" 25 | private const val PACKAGE_Android = "android" 26 | 27 | class MainHook : IXposedHookLoadPackage, IXposedHookZygoteInit { 28 | private var isInit: Boolean = true 29 | var context: Context? = null 30 | 31 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { 32 | if (!XConfig.getBoolean("MainSwitch")) { 33 | LogUtils.i("总开关未打开") 34 | return 35 | } 36 | initHandleLoadPackage(lpparam) 37 | setEzClassLoader(lpparam.classLoader) 38 | when (lpparam.packageName) { 39 | in homeList -> Application::class.java.hookBeforeMethod("attach", Context::class.java) { 40 | EzXHelperInit.apply { 41 | initAppContext(it.args[0] as Context) 42 | runCatching { 43 | if (isInit) { 44 | if (XConfig.getBoolean("Pad")) { 45 | UnlockPad.init() 46 | } 47 | if (XConfig.getBoolean("Shortcuts")) { 48 | ShortcutItemCount.init() 49 | } 50 | if (XConfig.getBoolean("UnlockGrids")) { 51 | UnlockGrids.init() 52 | } 53 | if (XConfig.getBoolean("HideAppName")) { 54 | HideAppName.init() 55 | } 56 | if (XConfig.getBoolean("HideAppIcon")) { 57 | HideAppIcon.init() 58 | } 59 | if (XConfig.getBoolean("UnlockHotseat")) { 60 | UnlockHotseatIcon.init() 61 | } 62 | if (XConfig.getBoolean("ShortcutSmallWindow")) { 63 | ShortcutSmallWindow.init() 64 | } 65 | if (XConfig.getBoolean("HideSmallWindow")) { 66 | HideSmallWindow.init() 67 | } 68 | if (XConfig.getBoolean("RemoveSmallWindowRestriction")) { 69 | RemoveSmallWindowRestriction2.init() 70 | } 71 | if (XConfig.getBoolean("UnlockNavType")) { 72 | UnlockNavType.init() 73 | } 74 | Info.init() 75 | HideStatusBarWhenEnterResents.init() 76 | isInit = false 77 | LogUtils.i(InitFields.moduleRes.getString(R.string.HookSuccess)) 78 | } 79 | }.logexIfThrow(InitFields.moduleRes.getString(R.string.HookFailed)) 80 | } 81 | } 82 | 83 | PACKAGE_SystemUi -> { 84 | if (XConfig.getBoolean("RemoveSmallWindowRestriction")) { 85 | RemoveSmallWindowRestriction3.init() 86 | } 87 | } 88 | 89 | PACKAGE_Android -> { 90 | if (XConfig.getBoolean("RemoveSmallWindowRestriction")) { 91 | RemoveSmallWindowRestriction1.init() 92 | } 93 | } 94 | 95 | else -> return 96 | } 97 | 98 | } 99 | 100 | 101 | override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) { 102 | EzXHelperInit.initZygote(startupParam) 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/add/Info.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package cn.fuckhome.xiaowine.hook.module.add 4 | 5 | import android.annotation.SuppressLint 6 | import android.app.ActivityManager 7 | import android.content.BroadcastReceiver 8 | import android.content.Context 9 | import android.content.Intent 10 | import android.content.IntentFilter 11 | import android.content.res.ColorStateList 12 | import android.graphics.Color 13 | import android.graphics.Typeface 14 | import android.graphics.drawable.GradientDrawable 15 | import android.os.Environment 16 | import android.os.Handler 17 | import android.os.Looper 18 | import android.view.View 19 | import android.view.ViewGroup 20 | import android.view.animation.AlphaAnimation 21 | import android.view.animation.Animation 22 | import android.widget.FrameLayout 23 | import android.widget.LinearLayout 24 | import android.widget.TextView 25 | import cn.fuckhome.xiaowine.R 26 | import cn.fuckhome.xiaowine.hook.BaseHook 27 | import cn.fuckhome.xiaowine.utils.FileUtils 28 | import cn.fuckhome.xiaowine.utils.LogUtils 29 | import cn.fuckhome.xiaowine.utils.MemoryUtils 30 | import cn.fuckhome.xiaowine.utils.Utils 31 | import cn.fuckhome.xiaowine.utils.Utils.XConfig 32 | import cn.fuckhome.xiaowine.utils.Utils.formatSize 33 | import cn.fuckhome.xiaowine.utils.Utils.isNull 34 | import com.github.kyuubiran.ezxhelper.init.InitFields.appContext 35 | import com.github.kyuubiran.ezxhelper.init.InitFields.moduleRes 36 | import com.github.kyuubiran.ezxhelper.utils.* 37 | import java.io.File 38 | import java.util.* 39 | import kotlin.math.roundToInt 40 | import kotlin.system.exitProcess 41 | 42 | 43 | @SuppressLint("StaticFieldLeak") 44 | object Info : BaseHook() { 45 | lateinit var textColors: ColorStateList 46 | private var TextViewMaps = LinkedHashMap() 47 | private var TextViewList = arrayListOf() 48 | private lateinit var mLinearLayout: LinearLayout 49 | 50 | private val metrics = appContext.resources.displayMetrics 51 | private val widthPixels = metrics.widthPixels 52 | private val heightPixels = metrics.heightPixels 53 | private var topMargin = 0 54 | private var leftMargin = 0 55 | 56 | const val threshold = 20 57 | 58 | private val moduleReceiver by lazy { ModuleReceiver() } 59 | 60 | private var timer: Timer? = null 61 | private var timerQueue: ArrayList = arrayListOf() 62 | private var infoTimer: TimerTask? = null 63 | 64 | 65 | private val handler by lazy { Handler(Looper.getMainLooper()) } 66 | override fun init() { 67 | // 初始化根控件 68 | Utils.catchNoClass { 69 | findConstructor("com.miui.home.recents.views.RecentsContainer") { parameterCount == 2 }.hookAfter { 70 | LogUtils.i(moduleRes.getString(R.string.InitRootView)) 71 | val mView = it.thisObject as FrameLayout 72 | mLinearLayout = LinearLayout(appContext).apply { 73 | orientation = LinearLayout.VERTICAL 74 | if (XConfig.getBoolean("optimizeAnimation")) { 75 | visibility = View.GONE 76 | } 77 | val gd = GradientDrawable() 78 | gd.setColor(Color.parseColor(XConfig.getBgColor())) 79 | // gd.setColor(Color.BLUE) 80 | gd.cornerRadius = XConfig.getBgCorners().toFloat() 81 | gd.setStroke(width, Color.BLACK) 82 | background = gd 83 | } 84 | 85 | mView.addView(mLinearLayout) 86 | listOf("MemoryView", "ZarmView", "StorageView", "Uptime", "RunningAppTotal", "RunningServiceTotal").forEach { s -> 87 | if (XConfig.getBoolean(s)) { 88 | TextViewList.add(s) 89 | } 90 | } 91 | } 92 | } 93 | 94 | // 初始化添加控件 95 | Utils.catchNoClass { 96 | findMethod("com.miui.home.recents.views.RecentsContainer") { name == "onFinishInflate" }.hookAfter { 97 | LogUtils.i(moduleRes.getString(R.string.InitAddView)) 98 | val mTxtMemoryViewGroup = it.thisObject.getObjectAs("mTxtMemoryContainer") 99 | it.thisObject.putObject("mSeparatorForMemoryInfo", View(appContext)) 100 | for (i in 0 until mTxtMemoryViewGroup.childCount) { 101 | mTxtMemoryViewGroup.getChildAt(i).visibility = View.GONE 102 | } 103 | val mTxtMemoryInfo1 = it.thisObject.getObjectAs("mTxtMemoryInfo1") 104 | textColors = mTxtMemoryInfo1.textColors 105 | 106 | if (TextViewMaps.size != 0) { 107 | try { 108 | TextViewMaps.forEach { its -> 109 | TextViewMaps.remove(its.key) 110 | } 111 | } catch (_: ConcurrentModificationException) { 112 | exitProcess(0) 113 | } 114 | } 115 | TextViewMaps.apply { 116 | TextViewList.forEach { name -> 117 | val view = TextView(appContext).apply { 118 | // setBackgroundColor(Color.parseColor(XConfig.getBgColor())) 119 | Utils.viewColor(this, null) 120 | gravity = XConfig.getGravity() 121 | textSize = 12f 122 | marqueeRepeatLimit = -1 123 | isSingleLine = true 124 | maxLines = 1 125 | } 126 | mLinearLayout.addView(view) 127 | this[name] = view 128 | } 129 | } 130 | 131 | } 132 | } 133 | 134 | // 调节边距 135 | Utils.catchNoClass { 136 | findMethod("com.miui.home.recents.views.RecentsContainer") { name == "updateRotation" }.hookAfter { 137 | val mResentsContainerRotation = it.args[0] as Int 138 | if (mResentsContainerRotation == 0) { 139 | if (XConfig.getUnit()) { 140 | topMargin = (10 + XConfig.getInt("TopMargin0", 4) / 100.0 * heightPixels).toInt() 141 | leftMargin = (10 + XConfig.getInt("LeftMargin0") / 100.0 * widthPixels).roundToInt() 142 | } else { 143 | LogUtils.i(XConfig.getInt("TopMargin0", 4)) 144 | topMargin = 10 + XConfig.getInt("TopMargin0", 4) 145 | leftMargin = 10 + XConfig.getInt("LeftMargin0") 146 | } 147 | } else { 148 | if (XConfig.getUnit()) { 149 | topMargin = (10 + XConfig.getInt("TopMargin1", 5) / 100.0 * widthPixels).roundToInt() 150 | leftMargin = (10 + XConfig.getInt("LeftMargin1") / 100.0 * heightPixels).roundToInt() 151 | } else { 152 | topMargin = 10 + XConfig.getInt("TopMargin1", 5) 153 | leftMargin = 10 + XConfig.getInt("LeftMargin1") 154 | } 155 | 156 | } 157 | } 158 | } 159 | 160 | 161 | // 动态隐藏以优化动画 刷新数据 162 | Utils.catchNoClass { 163 | findMethod("com.miui.home.recents.views.RecentsContainer") { name == "startRecentsContainerFadeInAnim" }.hookAfter { 164 | LogUtils.i(moduleRes.getString(R.string.VisibleView)) 165 | val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT) 166 | params.topMargin = topMargin 167 | params.leftMargin = leftMargin 168 | mLinearLayout.layoutParams = params 169 | LogUtils.i(moduleRes.getString(R.string.UpdateView)) 170 | 171 | val animation = AlphaAnimation(0f, 1f) 172 | animation.duration = 300 173 | animation.setAnimationListener(object : Animation.AnimationListener { 174 | override fun onAnimationStart(animation: Animation) {} 175 | override fun onAnimationRepeat(animation: Animation) {} 176 | override fun onAnimationEnd(animation: Animation) { 177 | mLinearLayout.clearAnimation() 178 | } 179 | }) 180 | mLinearLayout.startAnimation(animation) 181 | mLinearLayout.visibility = View.VISIBLE 182 | 183 | startTimer() 184 | } 185 | } 186 | 187 | Utils.catchNoClass { 188 | findMethod("com.miui.home.recents.views.RecentsContainer") { name == "startRecentsContainerFadeOutAnim" }.hookAfter { 189 | LogUtils.i(moduleRes.getString(R.string.GoneView)) 190 | stopTimer() 191 | if (mLinearLayout.visibility != View.GONE) { 192 | val animation = AlphaAnimation(1f, 0f) 193 | animation.duration = 300 194 | animation.setAnimationListener(object : Animation.AnimationListener { 195 | override fun onAnimationStart(animation: Animation) {} 196 | override fun onAnimationRepeat(animation: Animation) {} 197 | override fun onAnimationEnd(animation: Animation) { 198 | mLinearLayout.clearAnimation() 199 | } 200 | }) 201 | mLinearLayout.startAnimation(animation) 202 | mLinearLayout.visibility = View.GONE 203 | } 204 | } 205 | } 206 | 207 | 208 | // 广播 209 | Utils.catchNoClass { appContext.unregisterReceiver(moduleReceiver) } 210 | appContext.registerReceiver(moduleReceiver, IntentFilter().apply { addAction("MIUIHOME_Server") }) 211 | } 212 | 213 | fun updateInfoDate() { 214 | LogUtils.i("更新数据") 215 | handler.post { 216 | val memoryInfo = MemoryUtils().getMemoryInfo(appContext) 217 | val swapInfo = MemoryUtils().getPartitionInfo("SwapTotal", "SwapFree") 218 | val storageInfo = MemoryUtils().getStorageInfo(Environment.getExternalStorageDirectory()) 219 | TextViewMaps.forEach { (name, view) -> 220 | when (name) { 221 | "MemoryView" -> { 222 | view.text = moduleRes.getString(if (XConfig.getBoolean("CleanMode")) R.string.MemoryView2 else R.string.MemoryView).format(memoryInfo.availMem.formatSize(), memoryInfo.totalMem.formatSize(), memoryInfo.percentValue) 223 | Utils.viewColor(view, memoryInfo) 224 | } 225 | 226 | "ZarmView" -> { 227 | view.text = moduleRes.getString(if (XConfig.getBoolean("CleanMode")) R.string.ZarmView2 else R.string.ZarmView).format(swapInfo.availMem.formatSize(), swapInfo.totalMem.formatSize(), swapInfo.percentValue) 228 | Utils.viewColor(view, swapInfo) 229 | } 230 | 231 | "StorageView" -> { 232 | view.text = moduleRes.getString(if (XConfig.getBoolean("CleanMode")) R.string.StorageView2 else R.string.StorageView).format(storageInfo.availMem.formatSize(), storageInfo.totalMem.formatSize(), storageInfo.percentValue) 233 | Utils.viewColor(view, storageInfo) 234 | } 235 | 236 | "Uptime" -> { 237 | 238 | view.text = moduleRes.getString(R.string.BootTimeView).format(Utils.Uptime.get()) 239 | } 240 | 241 | "RunningAppTotal" -> { 242 | view.text = moduleRes.getString(R.string.RunningAppTotalView).format((appContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses.size) 243 | } 244 | 245 | "RunningServiceTotal" -> { 246 | view.text = moduleRes.getString(R.string.RunningServiceTotalView).format((appContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getRunningServices(999).size) 247 | } 248 | 249 | } 250 | view.width = view.paint.measureText(view.text.toString()).toInt() + 40 251 | } 252 | } 253 | } 254 | 255 | private fun getInfoTimer(): TimerTask { 256 | if (infoTimer == null) { 257 | infoTimer = object : TimerTask() { 258 | override fun run() { 259 | updateInfoDate() 260 | } 261 | } 262 | } 263 | return infoTimer as TimerTask 264 | } 265 | 266 | 267 | private fun startTimer() { 268 | val timerTask = getInfoTimer() 269 | timerQueue.forEach { task -> if (task == timerTask) return } 270 | timerQueue.add(timerTask) 271 | if (timer.isNull()) timer = Timer() 272 | timer?.schedule(timerTask, 0, 1000L) 273 | } 274 | 275 | private fun stopTimer() { 276 | timerQueue.forEach { task -> task.cancel() } 277 | infoTimer = null 278 | timerQueue = arrayListOf() 279 | timer?.cancel() 280 | timer = null 281 | } 282 | 283 | 284 | class ModuleReceiver : BroadcastReceiver() { 285 | override fun onReceive(context: Context, intent: Intent) { 286 | Utils.catchNoClass { 287 | when (intent.getStringExtra("Type")) { 288 | "copy_font" -> { 289 | LogUtils.i(moduleRes.getString(R.string.CustomFont)) 290 | val path = intent.getStringExtra("Font_Path") 291 | if (path.isNullOrEmpty()) return@catchNoClass 292 | val file = File(appContext.filesDir.path + "/font") 293 | if (file.exists() && file.canWrite()) { 294 | file.delete() 295 | } 296 | val error = FileUtils(appContext).copyFile(File(path), appContext.filesDir.path, "font") 297 | if (error.isEmpty()) { 298 | TextViewMaps.forEach { (_, view) -> 299 | view.typeface = Typeface.createFromFile(appContext.filesDir.path + "/font") 300 | } 301 | LogUtils.i(moduleRes.getString(R.string.CustomFontSuccess)) 302 | appContext.sendBroadcast(Intent().apply { 303 | action = "MIUIHOME_App_Server" 304 | putExtra("app_Type", "CopyFont") 305 | putExtra("CopyFont", true) 306 | }) 307 | } else { 308 | runCatching { 309 | val file1 = File(appContext.filesDir.path + "/font") 310 | if (file1.exists() && file1.canWrite()) { 311 | file1.delete() 312 | LogUtils.i(moduleRes.getString(R.string.CustomFontFail)) 313 | } 314 | } 315 | appContext.sendBroadcast(Intent().apply { 316 | action = "MIUIHOME_App_Server" 317 | putExtra("app_Type", "CopyFont") 318 | putExtra("font_error", error) 319 | }) 320 | } 321 | } 322 | 323 | "delete_font" -> { 324 | LogUtils.i(moduleRes.getString(R.string.DeleteFont)) 325 | var isOK = false 326 | val file = File(appContext.filesDir.path + "/font") 327 | if (file.exists() && file.canWrite()) { 328 | isOK = file.delete() 329 | } 330 | TextViewMaps.forEach { (_, view) -> 331 | view.typeface = Typeface.createFromFile("") 332 | } 333 | appContext.sendBroadcast(Intent().apply { 334 | action = "MIUIHOME_App_Server" 335 | putExtra("app_Type", "DeleteFont") 336 | putExtra("DeleteFont", isOK) 337 | }) 338 | } 339 | } 340 | } 341 | } 342 | } 343 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/HideAppIcon.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import cn.fuckhome.xiaowine.hook.BaseHook 4 | import cn.fuckhome.xiaowine.utils.Utils 5 | import com.github.kyuubiran.ezxhelper.utils.findMethod 6 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 7 | 8 | object HideAppIcon:BaseHook() { 9 | override fun init() { 10 | Utils.catchNoClass { 11 | findMethod("com.miui.home.launcher.ItemIcon") { name == "setIconImageView" }.hookBefore { 12 | it.result = null 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/HideAppName.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import cn.fuckhome.xiaowine.R 4 | import cn.fuckhome.xiaowine.hook.BaseHook 5 | import cn.fuckhome.xiaowine.utils.LogUtils 6 | import cn.fuckhome.xiaowine.utils.Utils 7 | import com.github.kyuubiran.ezxhelper.init.InitFields.moduleRes 8 | import com.github.kyuubiran.ezxhelper.utils.findMethod 9 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 10 | 11 | object HideAppName : BaseHook() { 12 | override fun init() { 13 | Utils.catchNoClass { 14 | findMethod("com.miui.home.launcher.ItemIcon") { name == "setTitle" }.hookBefore { 15 | LogUtils.i(moduleRes.getString(R.string.HideAppName)) 16 | it.result = null 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/HideSmallWindow.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import android.view.View 4 | import android.widget.TextView 5 | import cn.fuckhome.xiaowine.R 6 | import cn.fuckhome.xiaowine.hook.BaseHook 7 | import cn.fuckhome.xiaowine.utils.LogUtils 8 | import cn.fuckhome.xiaowine.utils.Utils 9 | import cn.fuckhome.xiaowine.utils.getObjectField 10 | import cn.fuckhome.xiaowine.utils.hookAfterMethod 11 | import com.github.kyuubiran.ezxhelper.init.InitFields 12 | import com.github.kyuubiran.ezxhelper.utils.findMethod 13 | 14 | object HideSmallWindow: BaseHook() { 15 | override fun init() { 16 | Utils.catchNoClass { 17 | findMethod("com.miui.home.recents.views.RecentsContainer") { name == "onFinishInflate" }.hookAfterMethod { 18 | LogUtils.i(InitFields.moduleRes.getString(R.string.HideSmallWindow)) 19 | val mTitle = it.thisObject.getObjectField("mTxtSmallWindow") as TextView 20 | mTitle.visibility = View.GONE 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/HideStatusBarWhenEnterResents.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import cn.fuckhome.xiaowine.R 4 | import cn.fuckhome.xiaowine.hook.BaseHook 5 | import cn.fuckhome.xiaowine.utils.LogUtils 6 | import cn.fuckhome.xiaowine.utils.Utils 7 | import cn.fuckhome.xiaowine.utils.Utils.XConfig 8 | import com.github.kyuubiran.ezxhelper.init.InitFields.moduleRes 9 | import com.github.kyuubiran.ezxhelper.utils.findMethod 10 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 11 | 12 | 13 | object HideStatusBarWhenEnterResents : BaseHook() { 14 | 15 | override fun init() { 16 | Utils.catchNoClass { 17 | if (XConfig.getBoolean("HideStatusBar")) LogUtils.i(moduleRes.getString(R.string.HideStatusBar)) 18 | findMethod("com.miui.home.launcher.common.DeviceLevelUtils") { name == "isHideStatusBarWhenEnterRecents" }.hookBefore { it.result = XConfig.getBoolean("HideStatusBar") } 19 | findMethod("com.miui.home.launcher.DeviceConfig") { name == "keepStatusBarShowingForBetterPerformance" }.hookBefore { it.result = !XConfig.getBoolean("HideStatusBar") } 20 | 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/RemoveSmallWindowRestriction1.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import android.content.Context 4 | import cn.fuckhome.xiaowine.R 5 | import cn.fuckhome.xiaowine.hook.BaseHook 6 | import cn.fuckhome.xiaowine.utils.LogUtils 7 | import com.github.kyuubiran.ezxhelper.init.InitFields 8 | import com.github.kyuubiran.ezxhelper.utils.findMethod 9 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 10 | import com.github.kyuubiran.ezxhelper.utils.hookReturnConstant 11 | 12 | object RemoveSmallWindowRestriction1 : BaseHook() { 13 | override fun init() { 14 | LogUtils.i(InitFields.moduleRes.getString(R.string.RemoveSmallWindowRestriction)) 15 | findMethod("com.android.server.wm.Task") { name == "isResizeable" }.hookReturnConstant(true) 16 | findMethod("android.util.MiuiMultiWindowAdapter") { name == "getFreeformBlackList" }.hookAfter { it.result = (it.result as MutableList<*>).apply { clear() } } 17 | findMethod("android.util.MiuiMultiWindowAdapter") { name == "getFreeformBlackListFromCloud" && parameterTypes[0] == Context::class.java }.hookAfter { it.result = (it.result as MutableList<*>).apply { clear() } } 18 | findMethod("android.util.MiuiMultiWindowUtils") { name == "supportFreeform" }.hookReturnConstant(true) 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/RemoveSmallWindowRestriction2.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import cn.fuckhome.xiaowine.R 4 | import cn.fuckhome.xiaowine.hook.BaseHook 5 | import cn.fuckhome.xiaowine.utils.LogUtils 6 | import com.github.kyuubiran.ezxhelper.init.InitFields 7 | import com.github.kyuubiran.ezxhelper.utils.findAllMethods 8 | import com.github.kyuubiran.ezxhelper.utils.hookReturnConstant 9 | 10 | object RemoveSmallWindowRestriction2 : BaseHook() { 11 | override fun init() { 12 | LogUtils.i(InitFields.moduleRes.getString(R.string.RemoveSmallWindowRestriction)) 13 | findAllMethods("com.miui.home.launcher.RecentsAndFSGestureUtils") { name == "canTaskEnterSmallWindow" }.hookReturnConstant(true) 14 | findAllMethods("com.miui.home.launcher.RecentsAndFSGestureUtils") { name == "canTaskEnterMiniSmallWindow" }.hookReturnConstant(true) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/RemoveSmallWindowRestriction3.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import cn.fuckhome.xiaowine.R 4 | import cn.fuckhome.xiaowine.hook.BaseHook 5 | import cn.fuckhome.xiaowine.utils.LogUtils 6 | import com.github.kyuubiran.ezxhelper.init.InitFields 7 | import com.github.kyuubiran.ezxhelper.utils.findMethod 8 | import com.github.kyuubiran.ezxhelper.utils.hookReturnConstant 9 | 10 | object RemoveSmallWindowRestriction3 : BaseHook() { 11 | override fun init() { 12 | LogUtils.i(InitFields.moduleRes.getString(R.string.RemoveSmallWindowRestriction)) 13 | findMethod("com.android.systemui.statusbar.notification.NotificationSettingsManager") { name == "canSlide" }.hookReturnConstant(true) 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/ShortcutItemCount.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import cn.fuckhome.xiaowine.R 4 | import cn.fuckhome.xiaowine.hook.BaseHook 5 | import cn.fuckhome.xiaowine.utils.LogUtils 6 | import cn.fuckhome.xiaowine.utils.Utils 7 | import cn.fuckhome.xiaowine.utils.callMethod 8 | import com.github.kyuubiran.ezxhelper.init.InitFields.moduleRes 9 | import com.github.kyuubiran.ezxhelper.utils.findMethod 10 | import com.github.kyuubiran.ezxhelper.utils.hookAfter 11 | 12 | object ShortcutItemCount : BaseHook() { 13 | override fun init() { 14 | Utils.catchNoClass { 15 | LogUtils.i(moduleRes.getString(R.string.Shortcuts)) 16 | findMethod("com.miui.home.launcher.shortcuts.AppShortcutMenu") { name == "getMaxCountInCurrentOrientation" }.hookAfter { it.result = 20 } 17 | findMethod("com.miui.home.launcher.shortcuts.AppShortcutMenu") { name == "getMaxShortcutItemCount" }.hookAfter { it.result = 20 } 18 | findMethod("com.miui.home.launcher.shortcuts.AppShortcutMenu") { name == "getMaxVisualHeight" }.hookAfter { it.result = it.thisObject.callMethod("getItemHeight") } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/ShortcutSmallWindow.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.ComponentName 5 | import android.content.Intent 6 | import android.content.res.Configuration 7 | import android.os.Bundle 8 | import android.view.View 9 | import cn.fuckhome.xiaowine.R 10 | import cn.fuckhome.xiaowine.hook.BaseHook 11 | import cn.fuckhome.xiaowine.utils.callMethod 12 | import cn.fuckhome.xiaowine.utils.callStaticMethod 13 | import cn.fuckhome.xiaowine.utils.findClass 14 | import cn.fuckhome.xiaowine.utils.getStaticObjectField 15 | import cn.fuckhome.xiaowine.utils.hookAfterAllMethods 16 | import cn.fuckhome.xiaowine.utils.hookBeforeMethod 17 | import cn.fuckhome.xiaowine.utils.setStaticObjectField 18 | import com.github.kyuubiran.ezxhelper.init.InitFields.appContext 19 | import com.github.kyuubiran.ezxhelper.init.InitFields.moduleRes 20 | import de.robv.android.xposed.XposedHelpers 21 | 22 | @SuppressLint("StaticFieldLeak", "DiscouragedApi") 23 | object ShortcutSmallWindow : BaseHook() { 24 | override fun init() { 25 | val mViewDarkModeHelper = ("com.miui.home.launcher.util.ViewDarkModeHelper").findClass() 26 | val mSystemShortcutMenu = ("com.miui.home.launcher.shortcuts.SystemShortcutMenu").findClass() 27 | val mSystemShortcutMenuItem = ("com.miui.home.launcher.shortcuts.SystemShortcutMenuItem").findClass() 28 | val mAppShortcutMenu = ("com.miui.home.launcher.shortcuts.AppShortcutMenu").findClass() 29 | val mShortcutMenuItem = ("com.miui.home.launcher.shortcuts.ShortcutMenuItem").findClass() 30 | val mAppDetailsShortcutMenuItem = ("com.miui.home.launcher.shortcuts.SystemShortcutMenuItem\$AppDetailsShortcutMenuItem").findClass() 31 | val mActivityUtilsCompat = ("com.miui.launcher.utils.ActivityUtilsCompat").findClass() 32 | mViewDarkModeHelper.hookAfterAllMethods("onConfigurationChanged") { 33 | mSystemShortcutMenuItem.callStaticMethod("createAllSystemShortcutMenuItems") 34 | } 35 | mShortcutMenuItem.hookAfterAllMethods("getShortTitle") { 36 | val appName = appContext.getString(appContext.resources.getIdentifier("system_shortcuts_more_operation", "string", "com.miui.home")) 37 | if (it.result == appName) { 38 | it.result = moduleRes.getString(R.string.AppInfo) 39 | } 40 | } 41 | mAppDetailsShortcutMenuItem.hookBeforeMethod("lambda\$getOnClickListener$0", mAppDetailsShortcutMenuItem, View::class.java) { 42 | val obj = it.args[0] 43 | val view: View = it.args[1] as View 44 | val mShortTitle = obj.callMethod("getShortTitle") as CharSequence 45 | if (mShortTitle == moduleRes.getString(R.string.SmallWindow)) { 46 | it.result = null 47 | val intent = Intent() 48 | val mComponentName = obj.callMethod("getComponentName") as ComponentName 49 | intent.action = "android.intent.action.MAIN" 50 | intent.addCategory("android.intent.category.LAUNCHER") 51 | intent.component = mComponentName 52 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 53 | val callStaticMethod = mActivityUtilsCompat.callStaticMethod("makeFreeformActivityOptions", view.context, mComponentName.packageName) 54 | if (callStaticMethod != null) { 55 | view.context.startActivity(intent, callStaticMethod.callMethod("toBundle") as Bundle) 56 | } 57 | } 58 | } 59 | mSystemShortcutMenu.hookAfterAllMethods("getMaxShortcutItemCount") { 60 | it.result = 5 61 | } 62 | mAppShortcutMenu.hookAfterAllMethods("getMaxShortcutItemCount") { 63 | it.result = 5 64 | } 65 | mSystemShortcutMenuItem.hookAfterAllMethods("createAllSystemShortcutMenuItems") { 66 | @Suppress("UNCHECKED_CAST") 67 | val mAllSystemShortcutMenuItems = mSystemShortcutMenuItem.getStaticObjectField("sAllSystemShortcutMenuItems") as Collection 68 | val mSmallWindowInstance = XposedHelpers.newInstance(mAppDetailsShortcutMenuItem) 69 | mSmallWindowInstance.callMethod("setShortTitle", moduleRes.getString(R.string.SmallWindow)) 70 | val isDarkMode = 71 | appContext.applicationContext.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES 72 | @Suppress("DEPRECATION") 73 | mSmallWindowInstance.callMethod( 74 | "setIconDrawable", 75 | if (isDarkMode) moduleRes.getDrawable(R.drawable.ic_small_window_dark) else moduleRes.getDrawable(R.drawable.ic_small_window_light) 76 | ) 77 | val sAllSystemShortcutMenuItems = ArrayList() 78 | sAllSystemShortcutMenuItems.add(mSmallWindowInstance) 79 | sAllSystemShortcutMenuItems.addAll(mAllSystemShortcutMenuItems) 80 | mSystemShortcutMenuItem.setStaticObjectField("sAllSystemShortcutMenuItems", sAllSystemShortcutMenuItems) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/UnlockGrids.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import cn.fuckhome.xiaowine.R 4 | import cn.fuckhome.xiaowine.hook.BaseHook 5 | import cn.fuckhome.xiaowine.utils.LogUtils 6 | import cn.fuckhome.xiaowine.utils.Utils 7 | import com.github.kyuubiran.ezxhelper.init.InitFields 8 | import com.github.kyuubiran.ezxhelper.utils.findMethod 9 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 10 | 11 | object UnlockGrids : BaseHook() { 12 | override fun init() { 13 | Utils.catchNoClass { 14 | LogUtils.i(InitFields.moduleRes.getString(R.string.UnlockGrids)) 15 | findMethod("com.miui.home.launcher.DeviceConfig") { name == "getHotseatMaxCount" }.hookBefore { it.result = 15 } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/UnlockHotseatIcon.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import cn.fuckhome.xiaowine.R 4 | import cn.fuckhome.xiaowine.hook.BaseHook 5 | import cn.fuckhome.xiaowine.utils.LogUtils 6 | import cn.fuckhome.xiaowine.utils.Utils 7 | import com.github.kyuubiran.ezxhelper.init.InitFields 8 | import com.github.kyuubiran.ezxhelper.utils.findMethod 9 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 10 | 11 | 12 | object UnlockHotseatIcon : BaseHook() { 13 | override fun init() { 14 | Utils.catchNoClass { 15 | LogUtils.i(InitFields.moduleRes.getString(R.string.UnlockHotseat)) 16 | findMethod("com.miui.home.launcher.DeviceConfig") { name == "getHotseatMaxCount" }.hookBefore { it.result = 20 } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/UnlockNavType.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import cn.fuckhome.xiaowine.hook.BaseHook 4 | import cn.fuckhome.xiaowine.utils.Utils 5 | import com.github.kyuubiran.ezxhelper.utils.findMethod 6 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 7 | 8 | 9 | object UnlockNavType : BaseHook() { 10 | override fun init() { 11 | Utils.catchNoClass { 12 | findMethod("com.miui.home.launcher.DeviceConfig") { name == "isShowSystemNavTypePreferenceInMiuiSettings" }.hookBefore { 13 | it.result = true 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/hook/module/modify/UnlockPad.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.hook.module.modify 2 | 3 | import cn.fuckhome.xiaowine.R 4 | import cn.fuckhome.xiaowine.hook.BaseHook 5 | import cn.fuckhome.xiaowine.utils.LogUtils 6 | import cn.fuckhome.xiaowine.utils.Utils 7 | import com.github.kyuubiran.ezxhelper.init.InitFields.moduleRes 8 | import com.github.kyuubiran.ezxhelper.utils.findMethod 9 | import com.github.kyuubiran.ezxhelper.utils.hookBefore 10 | 11 | object UnlockPad : BaseHook() { 12 | override fun init() { 13 | Utils.catchNoClass { 14 | LogUtils.i(moduleRes.getString(R.string.Pad)) 15 | findMethod("com.miui.home.launcher.common.Utilities") { name == "isPadDevice" }.hookBefore { 16 | it.result = true 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/utils/ActivityOwnSP.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.utils/* 2 | * StatusBarLyric 3 | * Copyright (C) 2021-2022 fkj@fkj233.cn 4 | * https://github.com/577fkj/StatusBarLyric 5 | * 6 | * This software is free opensource software: you can redistribute it 7 | * and/or modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version and our eula as published 10 | * by 577fkj. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * and eula along with this software. If not, see 19 | * 20 | * . 21 | */ 22 | 23 | 24 | import cn.fuckhome.xiaowine.config.Config 25 | import android.annotation.SuppressLint 26 | import android.app.Activity 27 | 28 | @SuppressLint("StaticFieldLeak") 29 | object ActivityOwnSP { 30 | lateinit var activity: Activity 31 | val ownSP by lazy { Utils.getSP(activity, "Fuck_Home_Config")!! } 32 | val ownSPConfig by lazy { Config(ownSP) } 33 | private val ownEditor by lazy { ownSP.edit() } 34 | 35 | const val version = 2 36 | 37 | fun set(key: String, any: Any) { 38 | when (any) { 39 | is Int -> ownEditor.putInt(key, any) 40 | is Float -> ownEditor.putFloat(key, any) 41 | is String -> ownEditor.putString(key, any) 42 | is Boolean -> ownEditor.putBoolean(key, any) 43 | is Long -> ownEditor.putLong(key, any) 44 | } 45 | ownEditor.apply() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/utils/ActivityUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * StatusBarLyric 3 | * Copyright (C) 2021-2022 fkj@fkj233.cn 4 | * https://github.com/577fkj/StatusBarLyric 5 | * 6 | * This software is free opensource software: you can redistribute it 7 | * and/or modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version and our eula as published 10 | * by 577fkj. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * and eula along with this software. If not, see 19 | * 20 | * . 21 | */ 22 | 23 | package cn.aodlyric.xiaowine.utils 24 | 25 | 26 | import android.content.Context 27 | import android.content.Intent 28 | import android.net.Uri 29 | import android.os.Handler 30 | import android.os.Looper 31 | import app.xiaowine.xtoast.XToast 32 | import cn.fuckhome.xiaowine.R 33 | 34 | object ActivityUtils { 35 | private val handler by lazy { Handler(Looper.getMainLooper()) } 36 | 37 | // 弹出toast 38 | @Suppress("DEPRECATION") 39 | fun showToastOnLooper(context: Context, message: String) { 40 | try { 41 | handler.post { // XToast.makeToast(context, message, toastIcon =context.resources.getDrawable(R.mipmap.ic_launcher_round)).show() 42 | XToast.makeText(context, message, toastIcon = context.resources.getDrawable(R.mipmap.ic_launcher)).show() 43 | } 44 | } catch (e: RuntimeException) { 45 | e.printStackTrace() 46 | } 47 | } 48 | 49 | fun openUrl(context: Context, url: String) { 50 | context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/utils/BackupUtils.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.utils 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.content.SharedPreferences 6 | import android.net.Uri 7 | import cn.aodlyric.xiaowine.utils.ActivityUtils.showToastOnLooper 8 | import cn.fuckhome.xiaowine.utils.Utils.isNotNull 9 | import org.json.JSONObject 10 | import java.io.BufferedReader 11 | import java.io.BufferedWriter 12 | import java.io.InputStreamReader 13 | import java.io.OutputStreamWriter 14 | import java.time.LocalDateTime 15 | 16 | 17 | object BackupUtils { 18 | const val CREATE_DOCUMENT_CODE = 255774 19 | const val OPEN_DOCUMENT_CODE = 277451 20 | 21 | private lateinit var sharedPreferences: SharedPreferences 22 | 23 | fun backup(activity: Activity, sp: SharedPreferences) { 24 | sharedPreferences = sp 25 | saveFile(activity, "Fuck_Home_${LocalDateTime.now()}.json") 26 | } 27 | 28 | fun recovery(activity: Activity, sp: SharedPreferences) { 29 | sharedPreferences = sp 30 | openFile(activity) 31 | } 32 | 33 | private fun openFile(activity: Activity) { 34 | val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) 35 | intent.addCategory(Intent.CATEGORY_OPENABLE) 36 | intent.type = "application/json" 37 | intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) 38 | activity.startActivityForResult(intent, OPEN_DOCUMENT_CODE) 39 | } 40 | 41 | 42 | private fun saveFile(activity: Activity, fileName: String) { 43 | val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) 44 | intent.addCategory(Intent.CATEGORY_OPENABLE) 45 | intent.type = "application/json" 46 | intent.putExtra(Intent.EXTRA_TITLE, fileName) 47 | activity.startActivityForResult(intent, CREATE_DOCUMENT_CODE) 48 | } 49 | 50 | fun handleReadDocument(activity: Activity, data: Uri?) { 51 | val edit = sharedPreferences.edit() 52 | val uri = data ?: return 53 | try { 54 | activity.contentResolver.openInputStream(uri)?.let { loadFile -> 55 | BufferedReader(InputStreamReader(loadFile)).apply { 56 | val sb = StringBuffer() 57 | var line = readLine() 58 | while (line.isNotNull()) { 59 | sb.append(line) 60 | line = readLine() 61 | } 62 | val read = sb.toString() 63 | JSONObject(read).apply { 64 | val key = keys() 65 | while (key.hasNext()) { 66 | val keys = key.next() 67 | when (val value = get(keys)) { 68 | is String -> { 69 | if (value.startsWith("Float:")) { 70 | edit.putFloat(keys, value.substring(value.indexOf("Float:")).toFloat() / 1000) 71 | } else { 72 | edit.putString(keys, value) 73 | } 74 | } 75 | is Boolean -> edit.putBoolean(keys, value) 76 | is Int -> edit.putInt(keys, value) 77 | } 78 | } 79 | } 80 | close() 81 | } 82 | } 83 | edit.apply() 84 | showToastOnLooper(activity, "load ok") 85 | } catch (e: Throwable) { 86 | showToastOnLooper(activity, "load fail\n$e") 87 | } 88 | } 89 | 90 | fun handleCreateDocument(activity: Activity, data: Uri?) { 91 | val uri = data ?: return 92 | try { 93 | activity.contentResolver.openOutputStream(uri)?.let { saveFile -> 94 | BufferedWriter(OutputStreamWriter(saveFile)).apply { 95 | write(JSONObject().also { 96 | for (entry: Map.Entry in sharedPreferences.all) { 97 | when (entry.value) { 98 | Float -> it.put(entry.key, "Float:" + (entry.value as Float * 1000).toInt().toString()) 99 | else -> it.put(entry.key, entry.value) 100 | } 101 | } 102 | }.toString()) 103 | close() 104 | } 105 | } 106 | showToastOnLooper(activity, "save ok") 107 | } catch (e: Throwable) { 108 | showToastOnLooper(activity, "save fail\n$e") 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/utils/ConfigUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * StatusBarLyric 3 | * Copyright (C) 2021-2022 fkj@fkj233.cn 4 | * https://github.com/577fkj/StatusBarLyric 5 | * 6 | * This software is free opensource software: you can redistribute it 7 | * and/or modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version and our eula as published 10 | * by 577fkj. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * and eula along with this software. If not, see 19 | * 20 | * . 21 | */ 22 | 23 | package cn.fuckhome.xiaowine.utils 24 | 25 | import android.annotation.SuppressLint 26 | import android.content.SharedPreferences 27 | import cn.fuckhome.xiaowine.utils.Utils.isNull 28 | import de.robv.android.xposed.XSharedPreferences 29 | 30 | class ConfigUtils { 31 | private var xSP: XSharedPreferences? = null 32 | private var mSP: SharedPreferences? = null 33 | private var mSPEditor: SharedPreferences.Editor? = null 34 | 35 | constructor(xSharedPreferences: XSharedPreferences?) { 36 | xSP = xSharedPreferences 37 | mSP = xSharedPreferences 38 | } 39 | 40 | @SuppressLint("CommitPrefEdits") 41 | constructor(sharedPreferences: SharedPreferences) { 42 | mSP = sharedPreferences 43 | mSPEditor = sharedPreferences.edit() 44 | } 45 | 46 | fun update() { 47 | if (xSP.isNull()) { 48 | xSP = Utils.getPref("Fuck_Home_Config") 49 | mSP = xSP 50 | return 51 | } 52 | xSP?.reload() 53 | } 54 | 55 | fun put(key: String?, any: Any) { 56 | when (any) { 57 | is Int -> mSPEditor?.putInt(key, any) 58 | is String -> mSPEditor?.putString(key, any) 59 | is Boolean -> mSPEditor?.putBoolean(key, any) 60 | is Float -> mSPEditor?.putFloat(key, any) 61 | } 62 | mSPEditor?.apply() 63 | } 64 | 65 | fun optInt(key: String, i: Int): Int { 66 | if (mSP.isNull()) { 67 | return i 68 | } 69 | return mSP!!.getInt(key, i) 70 | } 71 | 72 | fun optBoolean(key: String, bool: Boolean): Boolean { 73 | if (mSP.isNull()) { 74 | return bool 75 | } 76 | return mSP!!.getBoolean(key, bool) 77 | } 78 | 79 | fun optString(key: String, str: String): String { 80 | if (mSP.isNull()) { 81 | return str 82 | } 83 | return mSP!!.getString(key, str).toString() 84 | } 85 | 86 | fun optFloat(key: String, f: Float): Float { 87 | if (mSP.isNull()) { 88 | return f 89 | } 90 | return mSP!!.getFloat(key, f) 91 | } 92 | 93 | fun clearConfig() { 94 | mSPEditor?.clear()?.apply() 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/utils/FileUtils.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package cn.fuckhome.xiaowine.utils 4 | 5 | import android.content.ContentResolver 6 | import android.provider.MediaStore 7 | import android.content.CursorLoader 8 | import android.os.Build 9 | import android.provider.DocumentsContract 10 | import android.os.Environment 11 | import android.content.ContentUris 12 | import android.content.Context 13 | import android.net.Uri 14 | import cn.fuckhome.xiaowine.utils.Utils.isNotNull 15 | import cn.fuckhome.xiaowine.utils.Utils.isNull 16 | import java.io.File 17 | import java.io.FileInputStream 18 | import java.io.FileOutputStream 19 | import java.io.IOException 20 | import java.lang.Exception 21 | import java.lang.IllegalArgumentException 22 | 23 | class FileUtils(private val context: Context) { 24 | /** 25 | * 根据文件路径拷贝文件 26 | * @param src 源文件 27 | * @param destPath 目标文件路径 28 | * @return boolean 成功true、失败false 29 | */ 30 | fun copyFile(src: File?, destPath: String?, destFileName: String): String { 31 | if (src.isNull() || destPath.isNull()) { 32 | return "Param error" 33 | } 34 | val dest = File(destPath, destFileName) 35 | if (dest.exists()) { 36 | if (!dest.delete()) { 37 | return "Delete file fail" 38 | } 39 | } 40 | try { 41 | if (!dest.createNewFile()) { 42 | return "Create file fail" 43 | } 44 | } catch (e: IOException) { 45 | return e.message.toString() 46 | } 47 | try { 48 | val srcChannel = FileInputStream(src).channel 49 | val dstChannel = FileOutputStream(dest).channel 50 | srcChannel.transferTo(0, srcChannel.size(), dstChannel) 51 | try { 52 | srcChannel.close() 53 | dstChannel.close() 54 | } catch (e: IOException) { 55 | return e.message.toString() 56 | } 57 | } catch (e: IOException) { 58 | return e.message.toString() 59 | } 60 | return "" 61 | } 62 | 63 | 64 | fun getFilePathByUri(uri: Uri): String? { // 以 file:// 开头的 65 | if (ContentResolver.SCHEME_FILE == uri.scheme) { 66 | return uri.path 67 | } // 以/storage开头的也直接返回 68 | if (isOtherDocument(uri)) { 69 | return uri.path 70 | } // 版本兼容的获取! 71 | var path = getFilePathByUriBELOWAPI11(uri) 72 | if (path.isNotNull()) { 73 | return path 74 | } 75 | path = getFilePathByUriAPI11to18(uri) 76 | if (path.isNotNull()) { 77 | return path 78 | } 79 | path = getFilePathByUriAPI19(uri) 80 | return path 81 | } 82 | 83 | private fun getFilePathByUriBELOWAPI11(uri: Uri): String? { 84 | if (ContentResolver.SCHEME_CONTENT == uri.scheme) { 85 | var path: String? = null 86 | val projection = arrayOf(MediaStore.Images.Media.DATA) 87 | val cursor = context.contentResolver.query(uri, projection, null, null, null) 88 | if (cursor.isNotNull()) { 89 | if (cursor!!.moveToFirst()) { 90 | val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) 91 | if (columnIndex > -1) { 92 | path = cursor.getString(columnIndex) 93 | } 94 | } 95 | cursor.close() 96 | } 97 | return path 98 | } 99 | return null 100 | } 101 | 102 | private fun getFilePathByUriAPI11to18(contentUri: Uri): String? { 103 | val projection = arrayOf(MediaStore.Images.Media.DATA) 104 | var result: String? = null 105 | val cursorLoader = CursorLoader(context, contentUri, projection, null, null, null) 106 | val cursor = cursorLoader.loadInBackground() 107 | if (cursor.isNotNull()) { 108 | val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) 109 | cursor.moveToFirst() 110 | result = cursor.getString(columnIndex) 111 | cursor.close() 112 | } 113 | return result 114 | } 115 | 116 | private fun getFilePathByUriAPI19(uri: Uri): String? { 117 | if (ContentResolver.SCHEME_CONTENT == uri.scheme) { 118 | if (DocumentsContract.isDocumentUri(context, uri)) { 119 | if (isExternalStorageDocument(uri)) { // ExternalStorageProvider 120 | val docId = DocumentsContract.getDocumentId(uri) 121 | val split = docId.split(":").toTypedArray() 122 | val type = split[0] 123 | if ("primary".equals(type, ignoreCase = true)) { 124 | return if (split.size > 1) { 125 | Environment.getExternalStorageDirectory().toString() + "/" + split[1] 126 | } else { 127 | Environment.getExternalStorageDirectory().toString() + "/" 128 | } // This is for checking SD Card 129 | } 130 | } else if (isDownloadsDocument(uri)) { //下载内容提供者时应当判断下载管理器是否被禁用 131 | val stateCode = context.packageManager.getApplicationEnabledSetting("com.android.providers.downloads") 132 | if (stateCode != 0 && stateCode != 1) { 133 | return null 134 | } 135 | var id = DocumentsContract.getDocumentId(uri) // 如果出现这个RAW地址,我们则可以直接返回! 136 | if (id.startsWith("raw:")) { 137 | return id.replaceFirst("raw:".toRegex(), "") 138 | } 139 | if (id.contains(":")) { 140 | val tmp = id.split(":").toTypedArray() 141 | if (tmp.size > 1) { 142 | id = tmp[1] 143 | } 144 | } 145 | var contentUri = Uri.parse("content://downloads/public_downloads") 146 | try { 147 | contentUri = ContentUris.withAppendedId(contentUri!!, id.toLong()) 148 | } catch (e: Exception) { 149 | e.printStackTrace() 150 | } 151 | var path = getDataColumn(contentUri, null, null) 152 | if (path.isNotNull()) return path // 兼容某些特殊情况下的文件管理器! 153 | val fileName = getFileNameByUri(uri) 154 | if (fileName.isNotNull()) { 155 | path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName 156 | return path 157 | } 158 | } else if (isMediaDocument(uri)) { // MediaProvider 159 | val docId = DocumentsContract.getDocumentId(uri) 160 | val split = docId.split(":").toTypedArray() 161 | val type = split[0] 162 | var contentUri: Uri? = null 163 | when (type) { 164 | "image" -> { 165 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI 166 | } 167 | 168 | "video" -> { 169 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI 170 | } 171 | 172 | "audio" -> { 173 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI 174 | } 175 | } 176 | val selection = "_id=?" 177 | val selectionArgs = arrayOf(split[1]) 178 | return getDataColumn(contentUri, selection, selectionArgs) 179 | } 180 | } 181 | } 182 | return null 183 | } 184 | 185 | private fun getFileNameByUri(uri: Uri): String? { 186 | var relativePath = getFileRelativePathByUriAPI18(uri) 187 | if (relativePath.isNull()) relativePath = "" 188 | val projection = arrayOf(MediaStore.MediaColumns.DISPLAY_NAME) 189 | context.contentResolver.query(uri, projection, null, null, null).use { cursor -> 190 | if (cursor.isNotNull() && cursor!!.moveToFirst()) { 191 | val index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME) 192 | return relativePath + cursor.getString(index) 193 | } 194 | } 195 | return null 196 | } 197 | 198 | private fun getFileRelativePathByUriAPI18(uri: Uri): String? { 199 | val projection: Array 200 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 201 | projection = arrayOf(MediaStore.MediaColumns.RELATIVE_PATH) 202 | context.contentResolver.query(uri, projection, null, null, null).use { cursor -> 203 | if (cursor.isNotNull() && cursor!!.moveToFirst()) { 204 | val index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH) 205 | return cursor.getString(index) 206 | } 207 | } 208 | } 209 | return null 210 | } 211 | 212 | private fun getDataColumn(uri: Uri?, selection: String?, selectionArgs: Array?): String? { 213 | val column = MediaStore.Images.Media.DATA 214 | val projection = arrayOf(column) 215 | try { 216 | context.contentResolver.query(uri!!, projection, selection, selectionArgs, null).use { cursor -> 217 | if (cursor.isNotNull() && cursor!!.moveToFirst()) { 218 | val columnIndex = cursor.getColumnIndexOrThrow(column) 219 | return cursor.getString(columnIndex) 220 | } 221 | } 222 | } catch (iae: IllegalArgumentException) { 223 | iae.printStackTrace() 224 | } 225 | return null 226 | } 227 | 228 | private fun isExternalStorageDocument(uri: Uri): Boolean { 229 | return "com.android.externalstorage.documents" == uri.authority 230 | } 231 | 232 | private fun isOtherDocument(uri: Uri?): Boolean { // 以/storage开头的也直接返回 233 | if (uri.isNotNull() && uri!!.path.isNotNull()) { 234 | val path = uri.path 235 | if (path!!.startsWith("/storage")) { 236 | return true 237 | } 238 | if (path.startsWith("/external_files")) { 239 | return true 240 | } 241 | } 242 | return false 243 | } 244 | 245 | private fun isDownloadsDocument(uri: Uri): Boolean { 246 | return "com.android.providers.downloads.documents" == uri.authority 247 | } 248 | 249 | private fun isMediaDocument(uri: Uri): Boolean { 250 | return "com.android.providers.media.documents" == uri.authority 251 | } 252 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/utils/KotlinXposedHelper.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused", "KotlinConstantConditions") 2 | 3 | package cn.fuckhome.xiaowine.utils 4 | 5 | import android.content.res.XResources 6 | import com.github.kyuubiran.ezxhelper.init.InitFields.ezXClassLoader 7 | import com.github.kyuubiran.ezxhelper.utils.Log 8 | import dalvik.system.BaseDexClassLoader 9 | import de.robv.android.xposed.XC_MethodHook 10 | import de.robv.android.xposed.XC_MethodHook.MethodHookParam 11 | import de.robv.android.xposed.XC_MethodReplacement 12 | import de.robv.android.xposed.XposedBridge.* 13 | import de.robv.android.xposed.XposedHelpers.* 14 | import de.robv.android.xposed.callbacks.XC_LayoutInflated 15 | import java.lang.reflect.Field 16 | import java.lang.reflect.Member 17 | import java.lang.reflect.Modifier 18 | import java.util.* 19 | 20 | typealias MethodHookParam = MethodHookParam 21 | typealias Replacer = (MethodHookParam) -> Any? 22 | typealias Hooker = (MethodHookParam) -> Unit 23 | 24 | fun Class<*>.hookMethod(method: String?, vararg args: Any?) = try { 25 | findAndHookMethod(this, method, *args) 26 | } catch (e: NoSuchMethodError) { 27 | Log.ex(e) 28 | null 29 | } catch (e: ClassNotFoundError) { 30 | Log.ex(e) 31 | null 32 | } catch (e: ClassNotFoundException) { 33 | Log.ex(e) 34 | null 35 | } 36 | 37 | fun Member.hookMethod(callback: XC_MethodHook) = try { 38 | hookMethod(this, callback) 39 | } catch (e: Throwable) { 40 | Log.ex(e) 41 | null 42 | } 43 | 44 | inline fun MethodHookParam.callHooker(crossinline hooker: Hooker) = try { 45 | hooker(this) 46 | } catch (e: Throwable) { 47 | Log.ex("Error occurred calling hooker on ${this.method}") 48 | Log.ex(e) 49 | } 50 | 51 | inline fun MethodHookParam.callReplacer(crossinline replacer: Replacer) = try { 52 | replacer(this) 53 | } catch (e: Throwable) { 54 | Log.ex("Error occurred calling replacer on ${this.method}") 55 | Log.ex(e) 56 | null 57 | } 58 | 59 | inline fun Member.replaceMethod(crossinline replacer: Replacer) = 60 | hookMethod(object : XC_MethodReplacement() { 61 | override fun replaceHookedMethod(param: MethodHookParam) = param.callReplacer(replacer) 62 | }) 63 | 64 | inline fun Member.hookAfterMethod(crossinline hooker: Hooker) = 65 | hookMethod(object : XC_MethodHook() { 66 | override fun afterHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 67 | }) 68 | 69 | inline fun Member.hookBeforeMethod(crossinline hooker: (MethodHookParam) -> Unit) = 70 | hookMethod(object : XC_MethodHook() { 71 | override fun beforeHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 72 | }) 73 | 74 | inline fun Class<*>.hookBeforeMethod( 75 | method: String?, 76 | vararg args: Any?, 77 | crossinline hooker: Hooker 78 | ) = hookMethod(method, *args, object : XC_MethodHook() { 79 | override fun beforeHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 80 | }) 81 | 82 | inline fun Class<*>.hookAfterMethod( 83 | method: String?, 84 | vararg args: Any?, 85 | crossinline hooker: Hooker 86 | ) = hookMethod(method, *args, object : XC_MethodHook() { 87 | override fun afterHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 88 | }) 89 | 90 | inline fun Class<*>.replaceMethod( 91 | method: String?, 92 | vararg args: Any?, 93 | crossinline replacer: Replacer 94 | ) = hookMethod(method, *args, object : XC_MethodReplacement() { 95 | override fun replaceHookedMethod(param: MethodHookParam) = param.callReplacer(replacer) 96 | }) 97 | 98 | fun Class<*>.hookAllMethods(methodName: String?, hooker: XC_MethodHook): Set = 99 | try { 100 | hookAllMethods(this, methodName, hooker) 101 | } catch (e: NoSuchMethodError) { 102 | Log.ex(e) 103 | emptySet() 104 | } catch (e: ClassNotFoundError) { 105 | Log.ex(e) 106 | emptySet() 107 | } catch (e: ClassNotFoundException) { 108 | Log.ex(e) 109 | emptySet() 110 | } 111 | 112 | inline fun Class<*>.hookBeforeAllMethods(methodName: String?, crossinline hooker: Hooker) = 113 | hookAllMethods(methodName, object : XC_MethodHook() { 114 | override fun beforeHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 115 | }) 116 | 117 | inline fun Class<*>.hookAfterAllMethods(methodName: String?, crossinline hooker: Hooker) = 118 | hookAllMethods(methodName, object : XC_MethodHook() { 119 | override fun afterHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 120 | 121 | }) 122 | 123 | inline fun Class<*>.replaceAfterAllMethods(methodName: String?, crossinline replacer: Replacer) = 124 | hookAllMethods(methodName, object : XC_MethodReplacement() { 125 | override fun replaceHookedMethod(param: MethodHookParam) = param.callReplacer(replacer) 126 | }) 127 | 128 | fun Class<*>.hookConstructor(vararg args: Any?) = try { 129 | findAndHookConstructor(this, *args) 130 | } catch (e: NoSuchMethodError) { 131 | Log.ex(e) 132 | null 133 | } catch (e: ClassNotFoundError) { 134 | Log.ex(e) 135 | null 136 | } catch (e: ClassNotFoundException) { 137 | Log.ex(e) 138 | null 139 | } 140 | 141 | inline fun Class<*>.hookBeforeConstructor(vararg args: Any?, crossinline hooker: Hooker) = 142 | hookConstructor(*args, object : XC_MethodHook() { 143 | override fun beforeHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 144 | }) 145 | 146 | inline fun Class<*>.hookAfterConstructor(vararg args: Any?, crossinline hooker: Hooker) = 147 | hookConstructor(*args, object : XC_MethodHook() { 148 | override fun afterHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 149 | }) 150 | 151 | inline fun Class<*>.replaceConstructor(vararg args: Any?, crossinline hooker: Hooker) = 152 | hookConstructor(*args, object : XC_MethodReplacement() { 153 | override fun replaceHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 154 | }) 155 | 156 | fun Class<*>.hookAllConstructors(hooker: XC_MethodHook): Set = try { 157 | hookAllConstructors(this, hooker) 158 | } catch (e: NoSuchMethodError) { 159 | Log.ex(e) 160 | emptySet() 161 | } catch (e: ClassNotFoundError) { 162 | Log.ex(e) 163 | emptySet() 164 | } catch (e: ClassNotFoundException) { 165 | Log.ex(e) 166 | emptySet() 167 | } 168 | 169 | inline fun Class<*>.hookAfterAllConstructors(crossinline hooker: Hooker) = 170 | hookAllConstructors(object : XC_MethodHook() { 171 | override fun afterHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 172 | }) 173 | 174 | inline fun Class<*>.hookBeforeAllConstructors(crossinline hooker: Hooker) = 175 | hookAllConstructors(object : XC_MethodHook() { 176 | override fun beforeHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 177 | }) 178 | 179 | inline fun Class<*>.replaceAfterAllConstructors(crossinline hooker: Hooker) = 180 | hookAllConstructors(object : XC_MethodReplacement() { 181 | override fun replaceHookedMethod(param: MethodHookParam) = param.callHooker(hooker) 182 | }) 183 | 184 | fun String.hookMethod(method: String?, vararg args: Any?) = try { 185 | findClass().hookMethod(method, *args) 186 | } catch (e: ClassNotFoundError) { 187 | Log.ex(e) 188 | null 189 | } catch (e: ClassNotFoundException) { 190 | Log.ex(e) 191 | null 192 | } 193 | 194 | inline fun String.hookBeforeMethod( 195 | method: String?, 196 | vararg args: Any?, 197 | crossinline hooker: Hooker 198 | ) = try { 199 | findClass().hookBeforeMethod(method, *args, hooker = hooker) 200 | } catch (e: ClassNotFoundError) { 201 | Log.ex(e) 202 | null 203 | } catch (e: ClassNotFoundException) { 204 | Log.ex(e) 205 | null 206 | } 207 | 208 | inline fun String.hookAfterMethod( 209 | method: String?, 210 | vararg args: Any?, 211 | crossinline hooker: Hooker 212 | ) = try { 213 | findClass().hookAfterMethod(method, *args, hooker = hooker) 214 | } catch (e: ClassNotFoundError) { 215 | Log.ex(e) 216 | null 217 | } catch (e: ClassNotFoundException) { 218 | Log.ex(e) 219 | null 220 | } 221 | 222 | inline fun String.replaceMethod( 223 | method: String?, 224 | vararg args: Any?, 225 | crossinline replacer: Replacer 226 | ) = try { 227 | findClass().replaceMethod(method, *args, replacer = replacer) 228 | } catch (e: ClassNotFoundError) { 229 | Log.ex(e) 230 | null 231 | } catch (e: ClassNotFoundException) { 232 | Log.ex(e) 233 | null 234 | } 235 | 236 | fun MethodHookParam.invokeOriginalMethod(): Any? = invokeOriginalMethod(method, thisObject, args) 237 | 238 | inline fun T.runCatchingOrNull(func: T.() -> R?) = try { 239 | func() 240 | } catch (e: Throwable) { 241 | null 242 | } 243 | 244 | fun Any.getObjectField(field: String?): Any? = getObjectField(this, field) 245 | 246 | fun Any.getObjectFieldOrNull(field: String?): Any? = runCatchingOrNull { 247 | getObjectField(this, field) 248 | } 249 | 250 | @Suppress("UNCHECKED_CAST") 251 | fun Any.getObjectFieldAs(field: String?) = getObjectField(this, field) as T 252 | 253 | @Suppress("UNCHECKED_CAST") 254 | fun Any.getObjectFieldOrNullAs(field: String?) = runCatchingOrNull { 255 | getObjectField(this, field) as T 256 | } 257 | 258 | fun Any.getIntField(field: String?) = getIntField(this, field) 259 | 260 | fun Any.getIntFieldOrNull(field: String?) = runCatchingOrNull { 261 | getIntField(this, field) 262 | } 263 | 264 | fun Any.getLongField(field: String?) = getLongField(this, field) 265 | 266 | fun Any.getLongFieldOrNull(field: String?) = runCatchingOrNull { 267 | getLongField(this, field) 268 | } 269 | 270 | fun Any.getBooleanField(field: String?) = getBooleanField(this, field) 271 | 272 | fun Any.getBooleanFieldOrNull(field: String?) = runCatchingOrNull { 273 | getBooleanField(this, field) 274 | } 275 | 276 | fun Any.callMethod(methodName: String?, vararg args: Any?): Any? = 277 | callMethod(this, methodName, *args) 278 | 279 | fun Any.callMethodOrNull(methodName: String?, vararg args: Any?): Any? = runCatchingOrNull { 280 | callMethod(this, methodName, *args) 281 | } 282 | 283 | fun Class<*>.callStaticMethod(methodName: String?, vararg args: Any?): Any? = 284 | callStaticMethod(this, methodName, *args) 285 | 286 | fun Class<*>.callStaticMethodOrNull(methodName: String?, vararg args: Any?): Any? = 287 | runCatchingOrNull { 288 | callStaticMethod(this, methodName, *args) 289 | } 290 | 291 | @Suppress("UNCHECKED_CAST") 292 | fun Class<*>.callStaticMethodAs(methodName: String?, vararg args: Any?) = 293 | callStaticMethod(this, methodName, *args) as T 294 | 295 | @Suppress("UNCHECKED_CAST") 296 | fun Class<*>.callStaticMethodOrNullAs(methodName: String?, vararg args: Any?) = 297 | runCatchingOrNull { 298 | callStaticMethod(this, methodName, *args) as T 299 | } 300 | 301 | @Suppress("UNCHECKED_CAST") 302 | fun Class<*>.getStaticObjectFieldAs(field: String?) = getStaticObjectField(this, field) as T 303 | 304 | @Suppress("UNCHECKED_CAST") 305 | fun Class<*>.getStaticObjectFieldOrNullAs(field: String?) = runCatchingOrNull { 306 | getStaticObjectField(this, field) as T 307 | } 308 | 309 | fun Class<*>.getStaticObjectField(field: String?): Any? = getStaticObjectField(this, field) 310 | 311 | fun Class<*>.getStaticObjectFieldOrNull(field: String?): Any? = runCatchingOrNull { 312 | getStaticObjectField(this, field) 313 | } 314 | 315 | fun Class<*>.setStaticObjectField(field: String?, obj: Any?) = apply { 316 | setStaticObjectField(this, field, obj) 317 | } 318 | 319 | fun Class<*>.setStaticObjectFieldIfExist(field: String?, obj: Any?) = apply { 320 | try { 321 | setStaticObjectField(this, field, obj) 322 | } catch (ignored: Throwable) { 323 | } 324 | } 325 | 326 | inline fun Class<*>.findFieldByExactType(): Field? = 327 | findFirstFieldByExactType(this, T::class.java) 328 | 329 | fun Class<*>.findFieldByExactType(type: Class<*>): Field? = 330 | findFirstFieldByExactType(this, type) 331 | 332 | @Suppress("UNCHECKED_CAST") 333 | fun Any.callMethodAs(methodName: String?, vararg args: Any?) = 334 | callMethod(this, methodName, *args) as T 335 | 336 | @Suppress("UNCHECKED_CAST") 337 | fun Any.callMethodOrNullAs(methodName: String?, vararg args: Any?) = runCatchingOrNull { 338 | callMethod(this, methodName, *args) as T 339 | } 340 | 341 | fun Any.callMethod(methodName: String?, parameterTypes: Array>, vararg args: Any?): Any? = 342 | callMethod(this, methodName, parameterTypes, *args) 343 | 344 | fun Any.callMethodOrNull( 345 | methodName: String?, 346 | parameterTypes: Array>, 347 | vararg args: Any? 348 | ): Any? = runCatchingOrNull { 349 | callMethod(this, methodName, parameterTypes, *args) 350 | } 351 | 352 | fun Class<*>.callStaticMethod( 353 | methodName: String?, 354 | parameterTypes: Array>, 355 | vararg args: Any? 356 | ): Any? = callStaticMethod(this, methodName, parameterTypes, *args) 357 | 358 | fun Class<*>.callStaticMethodOrNull( 359 | methodName: String?, 360 | parameterTypes: Array>, 361 | vararg args: Any? 362 | ): Any? = runCatchingOrNull { 363 | callStaticMethod(this, methodName, parameterTypes, *args) 364 | } 365 | 366 | fun String.findClass(classLoader: ClassLoader = ezXClassLoader): Class<*> = findClass(this, classLoader) 367 | 368 | fun String.findClassOrNull(classLoader: ClassLoader = ezXClassLoader): Class<*>? = 369 | findClassIfExists(this, classLoader) 370 | 371 | fun Class<*>.new(vararg args: Any?): Any = newInstance(this, *args) 372 | 373 | fun Class<*>.new(parameterTypes: Array>, vararg args: Any?): Any = 374 | newInstance(this, parameterTypes, *args) 375 | 376 | fun Class<*>.findField(field: String?): Field = findField(this, field) 377 | 378 | fun Class<*>.findFieldOrNull(field: String?): Field? = findFieldIfExists(this, field) 379 | 380 | fun T.setIntField(field: String?, value: Int) = apply { 381 | setIntField(this, field, value) 382 | } 383 | 384 | fun T.setLongField(field: String?, value: Long) = apply { 385 | setLongField(this, field, value) 386 | } 387 | 388 | fun T.setFloatField(field: String?, value: Float) = apply { 389 | setFloatField(this, field, value) 390 | } 391 | 392 | fun T.setObjectField(field: String?, value: Any?) = apply { 393 | setObjectField(this, field, value) 394 | } 395 | 396 | fun T.setBooleanField(field: String?, value: Boolean) = apply { 397 | setBooleanField(this, field, value) 398 | } 399 | 400 | inline fun XResources.hookLayout( 401 | id: Int, 402 | crossinline hooker: (XC_LayoutInflated.LayoutInflatedParam) -> Unit 403 | ) { 404 | try { 405 | hookLayout(id, object : XC_LayoutInflated() { 406 | override fun handleLayoutInflated(liparam: LayoutInflatedParam) { 407 | try { 408 | hooker(liparam) 409 | } catch (e: Throwable) { 410 | Log.ex(e) 411 | } 412 | } 413 | }) 414 | } catch (e: Throwable) { 415 | Log.ex(e) 416 | } 417 | } 418 | 419 | inline fun XResources.hookLayout( 420 | pkg: String, 421 | type: String, 422 | name: String, 423 | crossinline hooker: (XC_LayoutInflated.LayoutInflatedParam) -> Unit 424 | ) { 425 | try { 426 | val id = getIdentifier(name, type, pkg) 427 | hookLayout(id, hooker) 428 | } catch (e: Throwable) { 429 | Log.ex(e) 430 | } 431 | } 432 | 433 | fun Class<*>.findFirstFieldByExactType(type: Class<*>): Field = 434 | findFirstFieldByExactType(this, type) 435 | 436 | fun Class<*>.findFirstFieldByExactTypeOrNull(type: Class<*>?): Field? = runCatchingOrNull { 437 | findFirstFieldByExactType(this, type) 438 | } 439 | 440 | fun Any.getFirstFieldByExactType(type: Class<*>): Any? = 441 | javaClass.findFirstFieldByExactType(type).get(this) 442 | 443 | @Suppress("UNCHECKED_CAST") 444 | fun Any.getFirstFieldByExactTypeAs(type: Class<*>) = 445 | javaClass.findFirstFieldByExactType(type).get(this) as? T 446 | 447 | inline fun Any.getFirstFieldByExactType() = 448 | javaClass.findFirstFieldByExactType(T::class.java).get(this) as? T 449 | 450 | fun Any.getFirstFieldByExactTypeOrNull(type: Class<*>?): Any? = runCatchingOrNull { 451 | javaClass.findFirstFieldByExactTypeOrNull(type)?.get(this) 452 | } 453 | 454 | @Suppress("UNCHECKED_CAST") 455 | fun Any.getFirstFieldByExactTypeOrNullAs(type: Class<*>?) = 456 | getFirstFieldByExactTypeOrNull(type) as? T 457 | 458 | inline fun Any.getFirstFieldByExactTypeOrNull() = 459 | getFirstFieldByExactTypeOrNull(T::class.java) as? T 460 | 461 | inline fun ClassLoader.findDexClassLoader(crossinline delegator: (BaseDexClassLoader) -> BaseDexClassLoader = { x -> x }): BaseDexClassLoader? { 462 | var classLoader = this 463 | while (classLoader !is BaseDexClassLoader) { 464 | if (classLoader.parent != null) classLoader = classLoader.parent 465 | else return null 466 | } 467 | return delegator(classLoader) 468 | } 469 | 470 | inline fun ClassLoader.allClassesList(crossinline delegator: (BaseDexClassLoader) -> BaseDexClassLoader = { x -> x }): List { 471 | return findDexClassLoader(delegator)?.getObjectField("pathList") 472 | ?.getObjectFieldAs>("dexElements") 473 | ?.flatMap { 474 | it.getObjectField("dexFile")?.callMethodAs>("entries")?.toList() 475 | .orEmpty() 476 | }.orEmpty() 477 | } 478 | 479 | val Member.isStatic: Boolean 480 | inline get() = Modifier.isStatic(this.modifiers) 481 | val Member.isNotStatic: Boolean 482 | inline get() = !this.isStatic 483 | 484 | fun Any.getFieldByClassOrObject( 485 | fieldName: String, 486 | isStatic: Boolean = false, 487 | fieldType: Class<*>? = null 488 | ): Field { 489 | if (fieldName.isEmpty()) throw IllegalArgumentException("Field name must not be null or empty!") 490 | var clz: Class<*> = if (this is Class<*>) this else this.javaClass 491 | do { 492 | clz.declaredFields 493 | .filter { !(isStatic && !Modifier.isStatic(it.modifiers)) || !(!isStatic && Modifier.isStatic(it.modifiers)) } 494 | .firstOrNull { 495 | (fieldType == null || it.type == fieldType) && (it.name == fieldName) 496 | }?.let { it.isAccessible = true;return it } 497 | } while (clz.superclass.also { clz = it } != null) 498 | throw NoSuchFieldError() 499 | } 500 | 501 | fun Class<*>.getStaticFiledByClass(fieldName: String, type: Class<*>? = null): Field { 502 | if (fieldName.isEmpty()) throw IllegalArgumentException("Field name must not be null or empty!") 503 | return this.getFieldByClassOrObject(fieldName, true, type) 504 | } -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/utils/LogUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * StatusBarLyric 3 | * Copyright (C) 2021-2022 fkj@fkj233.cn 4 | * https://github.com/577fkj/StatusBarLyric 5 | * 6 | * This software is free opensource software: you can redistribute it 7 | * and/or modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either 9 | * version 3 of the License, or any later version and our eula as published 10 | * by 577fkj. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * and eula along with this software. If not, see 19 | * 20 | * . 21 | */ 22 | 23 | package cn.fuckhome.xiaowine.utils 24 | 25 | import android.util.Log 26 | import cn.fuckhome.xiaowine.utils.Utils.XConfig 27 | import de.robv.android.xposed.XposedBridge 28 | 29 | object LogUtils { 30 | private const val maxLength = 4000 31 | const val TAG = "Fuck_Home" 32 | 33 | 34 | private fun log(obj: Any?, toXposed: Boolean = false, toLogd: Boolean = false) { 35 | if (!XConfig.getBoolean("Debug")) return 36 | val content = if (obj is Throwable) Log.getStackTraceString(obj) else obj.toString() 37 | if (content.length > maxLength) { 38 | val chunkCount = content.length / maxLength 39 | for (i in 0..chunkCount) { 40 | val max = 4000 * (i + 1) 41 | if (max >= content.length) { 42 | if (toXposed) XposedBridge.log("$TAG: " + content.substring(maxLength * i)) 43 | if (toLogd) Log.d(TAG, content.substring(maxLength * i)) 44 | } else { 45 | if (toXposed) XposedBridge.log("$TAG: " + content.substring(maxLength * i, max)) 46 | if (toLogd) Log.d(TAG, content.substring(maxLength * i, max)) 47 | } 48 | } 49 | } else { 50 | if (toXposed) XposedBridge.log("$TAG: $content") 51 | if (toLogd) Log.d(TAG, content) 52 | } 53 | } 54 | 55 | fun i(obj: Any?) { 56 | log(obj, toXposed = true) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/utils/MemoryUtils.kt: -------------------------------------------------------------------------------- 1 | package cn.fuckhome.xiaowine.utils 2 | 3 | import android.app.ActivityManager 4 | import android.content.Context 5 | import android.os.StatFs 6 | import java.io.BufferedReader 7 | import java.io.File 8 | import java.io.FileInputStream 9 | import java.io.InputStreamReader 10 | import kotlin.math.roundToInt 11 | 12 | /** 13 | * @author xiaow 14 | */ 15 | class MemoryUtils { 16 | /** 17 | * @return 内存总大小 18 | */ 19 | var totalMem: Long = 0 20 | 21 | /** 22 | * @return 已经使用大小 23 | */ 24 | var availMem: Long = 0 25 | 26 | /** 27 | * @return 可以使用/剩余大小 28 | */ 29 | var usedMem: Long = 0 30 | 31 | /** 32 | * @return 可用百分比 33 | */ 34 | var percentValue: Int = 0 35 | 36 | var threshold: Long = 0 37 | 38 | /** 39 | * @return 是否处于低内存状态 40 | */ 41 | var lowMemory = false 42 | 43 | fun getMemoryInfo(context: Context): MemoryUtils { 44 | val memoryInfo = ActivityManager.MemoryInfo() 45 | val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager 46 | activityManager.getMemoryInfo(memoryInfo) 47 | totalMem = memoryInfo.totalMem 48 | availMem = memoryInfo.availMem 49 | usedMem = totalMem - availMem 50 | threshold = memoryInfo.threshold 51 | lowMemory = memoryInfo.lowMemory 52 | percentValue = getPercent(availMem.toDouble(), totalMem.toDouble()) 53 | return this 54 | } 55 | 56 | fun getStorageInfo(file: File?): MemoryUtils { 57 | if (file != null) { 58 | val sf = getStatFs(file) 59 | if (sf != null) { 60 | val blockSize = sf.blockSizeLong 61 | val blockCount = sf.blockCountLong 62 | val availCount = sf.availableBlocksLong 63 | totalMem = blockSize * blockCount 64 | availMem = blockSize * availCount 65 | usedMem = blockSize * (blockCount - availCount) 66 | percentValue = getPercent(availMem.toDouble(), totalMem.toDouble()) 67 | } 68 | } 69 | return this 70 | } 71 | 72 | fun getPartitionInfo(totalMemKey: String, availMemKey: String): MemoryUtils { 73 | totalMem = getOthersMemory(totalMemKey) 74 | availMem = getOthersMemory(availMemKey) 75 | usedMem = totalMem - availMem 76 | percentValue = getPercent(availMem.toDouble(), totalMem.toDouble()) 77 | return this 78 | } 79 | 80 | fun getPartitionInfo(totalMemKey: String, memKey: String, used: Boolean): MemoryUtils { 81 | if (used) { 82 | totalMem = getOthersMemory(totalMemKey) 83 | usedMem = getOthersMemory(memKey) 84 | availMem = totalMem - usedMem 85 | percentValue = getPercent(availMem.toDouble(), totalMem.toDouble()) 86 | } else { 87 | getPartitionInfo(totalMemKey, memKey) 88 | } 89 | return this 90 | } 91 | 92 | fun getPartitionInfo(totalMemKey: String, availMemKey: String, usedMemKey: String): MemoryUtils { 93 | totalMem = getOthersMemory(totalMemKey) 94 | availMem = getOthersMemory(availMemKey) 95 | usedMem = getOthersMemory(usedMemKey) 96 | percentValue = getPercent(availMem.toDouble(), totalMem.toDouble()) 97 | return this 98 | } 99 | 100 | private fun getStatFs(file: File): StatFs? { 101 | try { 102 | return StatFs(file.path) 103 | } catch (e: Exception) { 104 | e.printStackTrace() 105 | } 106 | return null 107 | } 108 | 109 | private fun getOthersMemory(keyName: String): Long { 110 | try { 111 | val fileInputStream = FileInputStream("/proc/meminfo") 112 | val stringBuilder = StringBuilder() 113 | val bf = BufferedReader(InputStreamReader(fileInputStream)) 114 | var line: String? 115 | var emptyOrNewLine = "" 116 | while (bf.readLine().also { line = it } != null) { 117 | stringBuilder.append(emptyOrNewLine).append(line) 118 | emptyOrNewLine = "\n" 119 | } 120 | fileInputStream.close() 121 | bf.close() 122 | val list = stringBuilder.toString().split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 123 | for (s in list) { 124 | if (s.contains(keyName)) { 125 | return s.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1].replace("k", "").replace("B", "").replace("K", "").replace("b", "").trim { it <= ' ' }.toLong() * 1024 126 | } 127 | } 128 | } catch (e: Exception) { 129 | return 0 130 | } 131 | return 0 132 | //return Formatter.formatFileSize(getBaseContext(), initial_memory); 133 | } // Byte转换为KB或者MB,内存大小规格化 } 134 | 135 | private fun getPercent(value1: Double, value2: Double): Int { 136 | if (value2.toInt() == 0) { 137 | return 0 138 | } 139 | return ((value1 / value2) * 100).roundToInt() 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/src/main/java/cn/fuckhome/xiaowine/utils/Utils.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package cn.fuckhome.xiaowine.utils 4 | 5 | import android.annotation.SuppressLint 6 | import android.content.Context 7 | import android.content.SharedPreferences 8 | import android.graphics.Color 9 | import android.os.SystemClock 10 | import android.text.format.Formatter 11 | import android.util.Log 12 | import android.widget.TextView 13 | import cn.fuckhome.xiaowine.BuildConfig 14 | import cn.fuckhome.xiaowine.config.Config 15 | import cn.fuckhome.xiaowine.hook.module.add.Info 16 | import com.github.kyuubiran.ezxhelper.init.InitFields.appContext 17 | import de.robv.android.xposed.XSharedPreferences 18 | 19 | 20 | object Utils { 21 | val XConfig: Config by lazy { Config(getPref("Fuck_Home_Config")) } 22 | 23 | 24 | fun viewColor(view: TextView, info: MemoryUtils?) { 25 | if (info.isNotNull() && XConfig.getBoolean("Warning") && info!!.percentValue < Info.threshold) { 26 | view.setTextColor(Color.RED) 27 | } else { 28 | if (XConfig.getColor().isEmpty()) { 29 | view.setTextColor(Info.textColors) 30 | } else { 31 | view.setTextColor(Color.parseColor(XConfig.getColor())) 32 | } 33 | 34 | } 35 | } 36 | 37 | fun getPref(key: String?): XSharedPreferences? { 38 | val pref = XSharedPreferences(BuildConfig.APPLICATION_ID, key) 39 | return if (pref.file.canRead()) pref else null 40 | } 41 | 42 | @SuppressLint("WorldReadableFiles") 43 | fun getSP(context: Context, key: String?): SharedPreferences? { 44 | return context.createDeviceProtectedStorageContext().getSharedPreferences(key, Context.MODE_WORLD_READABLE) 45 | } 46 | 47 | object Uptime { 48 | fun get(): String { 49 | val time = SystemClock.elapsedRealtime() / 1000 50 | var temp: Int 51 | val sb = StringBuffer() 52 | if (time > 3600) { 53 | temp = (time / 3600).toInt() 54 | sb.append(if (time / 3600 < 10) "0$temp:" else "$temp:") 55 | temp = (time % 3600 / 60).toInt() 56 | changeSeconds(time, temp, sb) 57 | } else { 58 | temp = (time % 3600 / 60).toInt() 59 | changeSeconds(time, temp, sb) 60 | } 61 | return sb.toString() 62 | } 63 | 64 | private fun changeSeconds(seconds: Long, temp: Int, sb: StringBuffer) { 65 | var temps = temp 66 | sb.append(if (temps < 10) "0$temps:" else "$temps:") 67 | temps = (seconds % 3600 % 60).toInt() 68 | sb.append(if (temps < 10) "0$temps" else "" + temps) 69 | } 70 | 71 | } 72 | 73 | fun catchNoClass(callback: () -> Unit) { 74 | runCatching { callback() }.exceptionOrNull().let { 75 | Log.i(LogUtils.TAG, "${callback.javaClass.simpleName}错误") 76 | } 77 | } 78 | 79 | fun Any.formatSize(): String = Formatter.formatFileSize(appContext, this as Long) 80 | 81 | 82 | fun Any?.isNull(callback: () -> Unit) { 83 | if (this == null) callback() 84 | } 85 | 86 | fun Any?.isNotNull(callback: () -> Unit) { 87 | if (this != null) callback() 88 | } 89 | 90 | fun Any?.isNull() = this == null 91 | 92 | fun Any?.isNotNull() = this != null 93 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jaredrummler/ktsh/Shell.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Jared Rummler 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jaredrummler.ktsh 18 | 19 | import com.jaredrummler.ktsh.Shell.Command 20 | import java.io.* 21 | import java.util.* 22 | import java.util.concurrent.CountDownLatch 23 | import java.util.concurrent.TimeUnit 24 | import java.util.concurrent.locks.ReentrantLock 25 | import java.util.regex.Pattern 26 | 27 | //https://github.com/jaredrummler/KtSh 28 | /** Environment variable. */ 29 | typealias Variable = String 30 | /** Environment variable value. */ 31 | typealias Value = String 32 | /** A [Map] for the environment variables used in the shell. */ 33 | typealias EnvironmentMap = Map 34 | 35 | /** 36 | * A shell starts a [Process] with the provided shell and additional/optional environment variables. 37 | * The shell handles maintaining the [Process] and reads standard output and standard error streams, 38 | * returning stdout, stderr, and the last exit code as a [Command.Result] when a command is complete. 39 | * 40 | * Example usage: 41 | * 42 | * val sh = Shell("sh") 43 | * val result = sh.run("echo 'Hello, World!'") 44 | * assert(result.isSuccess) 45 | * assert(result.stdout() == "Hello, World") 46 | * 47 | * @property path The path to the shell to start. 48 | * @property environment Map of all environment variables to include with the system environment. 49 | * Default value is an empty map. 50 | * @throws Shell.NotFoundException If the shell cannot be opened this runtime exception is thrown. 51 | * @author Jared Rummler (jaredrummler@gmail.com) 52 | * @since 05-05-2021 53 | */ 54 | class Shell @Throws(NotFoundException::class) @JvmOverloads constructor(val path: String, val environment: EnvironmentMap = emptyMap()) { 55 | 56 | /** 57 | * Construct a new [Shell] with optional environment variable arguments as a [Pair]. 58 | * 59 | * @param shell The path to the shell to start. 60 | * @param environment varargs of all environment variables as a [Pair] which are included 61 | * with the system environment. 62 | */ 63 | constructor(shell: String, vararg environment: Pair) : this(shell, environment.toEnvironmentMap()) 64 | 65 | /** 66 | * Construct a new [Shell] with optional environment variable arguments as an array. 67 | * 68 | * @param shell The path to the shell to start. 69 | * @param environment varargs of all environment variables as a [Pair] which are included 70 | * with the system environment. 71 | */ 72 | constructor(shell: String, environment: Array) : this(shell, environment.toEnvironmentMap()) 73 | 74 | /** 75 | * Get the current state of the shell 76 | */ 77 | var state: State = State.Idle 78 | private set 79 | 80 | private val onResultListeners = mutableSetOf() 81 | private val onStdOutListeners = mutableSetOf() 82 | 83 | private val onStdErrListeners = mutableSetOf() 84 | private val stdin: StandardInputStream 85 | private val stdoutReader: StreamReader 86 | private val stderrReader: StreamReader 87 | private var watchdog: Watchdog? = null 88 | 89 | private val process: Process 90 | 91 | init { 92 | try { 93 | process = runWithEnv(path, environment) 94 | stdin = StandardInputStream(process.outputStream) 95 | stdoutReader = StreamReader.createAndStart(THREAD_NAME_STDOUT, process.inputStream) 96 | stderrReader = StreamReader.createAndStart(THREAD_NAME_STDERR, process.errorStream) 97 | } catch (cause: Exception) { 98 | throw NotFoundException(String.format(EXCEPTION_SHELL_CANNOT_OPEN, path), cause) 99 | } 100 | } 101 | 102 | /** 103 | * Add a listener that will be invoked each time a command finishes. 104 | * 105 | * @param listener The listener to receive callbacks when commands finish executing. 106 | * @return This shell instance for chaining calls. 107 | */ 108 | fun addOnCommandResultListener(listener: OnCommandResultListener) = apply { 109 | onResultListeners.add(listener) 110 | } 111 | 112 | /** 113 | * Remove a listener previously added to stop receiving callbacks when commands finish. 114 | * 115 | * @param listener The listener registered via [addOnCommandResultListener]. 116 | * @return This shell instance for chaining calls. 117 | */ 118 | fun removeOnCommandResultListener(listener: OnCommandResultListener) = apply { 119 | onResultListeners.remove(listener) 120 | } 121 | 122 | /** 123 | * Add a listener that will be invoked each time the STDOUT stream reads a new line. 124 | * 125 | * @param listener The listener to receive callbacks when the STDOUT stream reads a line. 126 | * @return This shell instance for chaining calls. 127 | */ 128 | fun addOnStdoutLineListener(listener: OnLineListener) = apply { 129 | onStdOutListeners.add(listener) 130 | } 131 | 132 | /** 133 | * Remove a listener previously added to stop receiving callbacks for STDOUT read lines. 134 | * 135 | * @param listener The listener registered via [addOnStdoutLineListener]. 136 | * @return This shell instance for chaining calls. 137 | */ 138 | fun removeOnStdoutLineListener(listener: OnLineListener) = apply { 139 | onStdOutListeners.remove(listener) 140 | } 141 | 142 | /** 143 | * Add a listener that will be invoked each time the STDERR stream reads a new line. 144 | * 145 | * @param listener The listener to receive callbacks when the STDERR stream reads a line. 146 | * @return This shell instance for chaining calls. 147 | */ 148 | fun addOnStderrLineListener(listener: OnLineListener) = apply { 149 | onStdErrListeners.add(listener) 150 | } 151 | 152 | /** 153 | * Remove a listener previously added to stop receiving callbacks for STDERR read lines. 154 | * 155 | * @param listener The listener registered via [addOnStderrLineListener]. 156 | * @return This shell instance for chaining calls. 157 | */ 158 | fun removeOnStderrLineListener(listener: OnLineListener) = apply { 159 | onStdErrListeners.remove(listener) 160 | } 161 | 162 | /** 163 | * Run a command in the current shell and return its [result][Command.Result]. 164 | * 165 | * @param command The command to execute. 166 | * @param config The [options][Command.Config] to set when running the command. 167 | * @return The [result][Command.Result] containing stdout, stderr, status of running the command. 168 | * @throws ClosedException if the shell was closed prior to running the command. 169 | * @see shutdown 170 | * @see run 171 | */ 172 | @Throws(ClosedException::class) @Synchronized fun run( 173 | command: String, 174 | config: Command.Config.Builder.() -> Unit, 175 | ) = run(command, Command.Config.Builder().apply(config).create()) 176 | 177 | /** 178 | * Run a command in the current shell and return its [result][Command.Result]. 179 | * 180 | * @param command The command to execute. 181 | * @param config The [options][Command.Config] to set when running the command. 182 | * @return The [result][Command.Result] containing stdout, stderr, status of running the command. 183 | * @throws ClosedException if the shell was closed prior to running the command. 184 | * @see shutdown 185 | * @see run 186 | */ 187 | @Throws(ClosedException::class) @Synchronized @JvmOverloads fun run( 188 | command: String, 189 | config: Command.Config = Command.Config.default(), 190 | ): Command.Result { 191 | // If the shell is shutdown, throw a ShellClosedException. 192 | if (state == State.Shutdown) throw ClosedException(EXCEPTION_SHELL_SHUTDOWN) 193 | 194 | val stdout = Collections.synchronizedList(mutableListOf()) 195 | val stderr = Collections.synchronizedList(mutableListOf()) 196 | 197 | val watchdog = Watchdog().also { watchdog = it } 198 | var exitCode = Command.Status.INVALID 199 | val uuid = config.uuid 200 | 201 | val onComplete = { marker: Command.Marker -> 202 | when (marker.uuid) { 203 | uuid -> try { // Reached the end of reading the stream for the command. 204 | if (marker.status != Command.Status.INVALID) { 205 | exitCode = marker.status 206 | } 207 | } finally { 208 | watchdog.signal() 209 | } 210 | } 211 | } 212 | 213 | val lock = ReentrantLock() 214 | val output = Collections.synchronizedList(mutableListOf()) 215 | 216 | // Function to process stderr and stdout streams. 217 | fun onLine( 218 | buffer: MutableList, 219 | listeners: Set, 220 | onLine: (line: String) -> Unit, 221 | ) = { line: String -> 222 | try { 223 | lock.lock() 224 | if (config.notify) { 225 | listeners.forEach { listener -> listener.onLine(line) } 226 | } 227 | buffer.add(line) 228 | output.add(line) 229 | onLine(line) 230 | } finally { 231 | lock.unlock() 232 | } 233 | } 234 | 235 | stdoutReader.onComplete = onComplete 236 | stderrReader.onComplete = onComplete 237 | stdoutReader.onReadLine = onLine(stdout, onStdOutListeners, config.onStdOut) 238 | stderrReader.onReadLine = when (config.redirectStdErr) { 239 | true -> onLine(stdout, onStdOutListeners, config.onStdOut) 240 | else -> onLine(stderr, onStdErrListeners, config.onStdErr) 241 | } 242 | 243 | val startTime = System.currentTimeMillis() 244 | try { 245 | state = State.Running 246 | // Write the command and command end marker to stdin. 247 | write(command, "echo '$uuid' $?", "echo '$uuid' >&2") 248 | // Wait for the result with a timeout, if provided. 249 | if (!watchdog.await(config.timeout)) { 250 | exitCode = Command.Status.TIMEOUT 251 | config.onTimeout() 252 | } 253 | } catch (e: InterruptedException) { 254 | exitCode = Command.Status.TERMINATED 255 | config.onCancelled() 256 | } finally { 257 | this.watchdog = null 258 | state = State.Idle 259 | } 260 | 261 | if (exitCode != Command.Status.SUCCESS) { 262 | // Exit with the error code in a subshell 263 | // This is necessary because we send commands to signal a command was completed 264 | write("$(exit $exitCode)") 265 | } 266 | 267 | // Create the result from running the command. 268 | val result = Command.Result.create(uuid, command, stdout, stderr, output, exitCode, startTime) 269 | 270 | if (config.notify) { 271 | onResultListeners.forEach { listener -> 272 | listener.onResult(result) 273 | } 274 | } 275 | 276 | return result 277 | } 278 | 279 | /** 280 | * Check if the shell is idle. 281 | * 282 | * @return True if the shell is open but not running any commands. 283 | */ 284 | fun isIdle() = state is State.Idle 285 | 286 | /** 287 | * Check if the shell is running a command. 288 | * 289 | * @return True if the shell is executing a command. 290 | */ 291 | fun isRunning() = state is State.Running 292 | 293 | /** 294 | * Check if the shell is shutdown. 295 | * 296 | * @return True if the shell is closed. 297 | * @see shutdown 298 | */ 299 | fun isShutdown() = state is State.Shutdown 300 | 301 | /** 302 | * Check if the shell is alive and able to execute commands. 303 | * 304 | * @return True if the shell is running or idle. 305 | */ 306 | fun isAlive() = try { 307 | process.exitValue(); false 308 | } catch (e: IllegalThreadStateException) { 309 | true 310 | } 311 | 312 | /** 313 | * Interrupt waiting for a command to complete. 314 | */ 315 | fun interrupt() { 316 | watchdog?.abort() 317 | } 318 | 319 | /** 320 | * Shutdown the shell instance. After a shell is shutdown it can no longer execute commands 321 | * and should be garbage collected. 322 | */ 323 | @Synchronized fun shutdown() { 324 | try { 325 | write("exit") 326 | process.waitFor() 327 | stdin.closeQuietly() 328 | onStdOutListeners.clear() 329 | onStdErrListeners.clear() 330 | stdoutReader.join() 331 | stderrReader.join() 332 | process.destroy() 333 | } catch (ignored: IOException) { 334 | } finally { 335 | state = State.Shutdown 336 | } 337 | } 338 | 339 | private fun write(vararg commands: String) = try { 340 | commands.forEach { command -> stdin.write(command) } 341 | stdin.flush() 342 | } catch (ignored: IOException) { 343 | } 344 | 345 | private fun DataOutputStream.closeQuietly() = try { 346 | close() 347 | } catch (ignored: IOException) { 348 | } 349 | 350 | /** 351 | * Contains data classes used for running commands in a [Shell]. 352 | * 353 | * @see Command.Result 354 | * @see Command.Config 355 | * @see Command.Status 356 | */ 357 | object Command { 358 | 359 | /** 360 | * The result of running a command in a shell. 361 | * 362 | * @property stdout A list of lines read from the standard input stream. 363 | * @property stderr A list of lines read from the standard error stream. 364 | * @property exitCode The status code of running the command. 365 | * @property details Additional command result details. 366 | */ 367 | data class Result(val stdout: List, val stderr: List, val output: List, val exitCode: Int, val details: Details) { 368 | 369 | /** 370 | * True when the exit code is equal to 0. 371 | */ 372 | val isSuccess: Boolean get() = exitCode == Status.SUCCESS 373 | 374 | /** 375 | * Get [stdout] and [stderr] as a string, separated by new lines. 376 | * 377 | * @return The output of running the command in a shell. 378 | */ 379 | fun output(): String = output.joinToString("\n") 380 | 381 | /** 382 | * Get [stdout] as a string, separated by new lines. 383 | * 384 | * @return The standard ouput string. 385 | */ 386 | fun stdout(): String = stdout.joinToString("\n") 387 | 388 | /** 389 | * Get [stdout] as a string, separated by new lines. 390 | * 391 | * @return The standard ouput string. 392 | */ 393 | fun stderr(): String = stderr.joinToString("\n") 394 | 395 | /** 396 | * Additional details pertaining to running a command in a shell. 397 | * 398 | * @property uuid The unique identifier associated with the command. 399 | * @property command The command sent to the shell to execute. 400 | * @property startTime The time—in milliseconds since January 1, 1970, 00:00:00 GMT—when 401 | * the command started execution. 402 | * @property endTime The time—in milliseconds since January 1, 1970, 00:00:00 GMT—when 403 | * the command completed execution. 404 | * @property elapsed The number of milliseconds it took to execute the command. 405 | */ 406 | data class Details internal constructor(val uuid: UUID, val command: String, val startTime: Long, val endTime: Long, val elapsed: Long = endTime - startTime) 407 | 408 | companion object { 409 | internal fun create( 410 | uuid: UUID, 411 | command: String, 412 | stdout: List, 413 | stderr: List, 414 | output: List, 415 | exitCode: Int, 416 | startTime: Long, 417 | endTime: Long = System.currentTimeMillis(), 418 | ) = Result(stdout, stderr, output, exitCode, Details(uuid, command, startTime, endTime)) 419 | } 420 | } 421 | 422 | /** 423 | * Optional configuration settings when running a command in a [shell][Shell]. 424 | * 425 | * @property uuid The unique identifier associated with the command. 426 | * @property redirectStdErr True to redirect STDERR to STDOUT. 427 | * @property onStdOut Callback that is invoked when reading a line from stdout. 428 | * @property onStdErr Callback that is invoked when reading a line from stderr. 429 | * @property onCancelled Callback that is invoked when the command is interrupted. 430 | * @property onTimeout Callback that is invoked when the command timed-out. 431 | * @property timeout The time to wait before killing the command. 432 | * @property notify True to notify any [OnLineListener] and [OnCommandResultListener] of the command. 433 | */ 434 | class Config private constructor(val uuid: UUID = UUID.randomUUID(), val redirectStdErr: Boolean = false, val onStdOut: (line: String) -> Unit = {}, val onStdErr: (line: String) -> Unit = {}, val onCancelled: () -> Unit = {}, val onTimeout: () -> Unit = {}, val timeout: Timeout? = null, val notify: Boolean = true) { 435 | 436 | /** 437 | * Optional configuration settings when running a command in a [shell][Shell]. 438 | * 439 | * @property uuid The unique identifier associated with the command. 440 | * @property redirectErrorStream True to redirect STDERR to STDOUT. 441 | * @property onStdOut Callback that is invoked when reading a line from stdout. 442 | * @property onStdErr Callback that is invoked when reading a line from stderr. 443 | * @property onCancelled Callback that is invoked when the command is interrupted. 444 | * @property onTimeout Callback that is invoked when the command timed-out. 445 | * @property timeout The time to wait before killing the command. 446 | * @property notify True to notify any [OnLineListener] and [OnCommandResultListener] of the command. 447 | */ 448 | class Builder { 449 | var uuid: UUID = UUID.randomUUID() 450 | var redirectErrorStream = false 451 | var onStdOut: (line: String) -> Unit = {} 452 | var onStdErr: (line: String) -> Unit = {} 453 | var onCancelled: () -> Unit = {} 454 | var onTimeout: () -> Unit = {} 455 | var timeout: Timeout? = null 456 | var notify = true 457 | 458 | /** 459 | * Create the [Config] from this builder. 460 | * 461 | * @return A new [Config] for a command. 462 | */ 463 | fun create() = Config(uuid, redirectErrorStream, onStdOut, onStdErr, onCancelled, onTimeout, timeout, notify) 464 | } 465 | 466 | companion object { 467 | 468 | /** 469 | * The default configuration for running a command in a shell. 470 | * 471 | * @return The default config. 472 | */ 473 | fun default(): Config = Builder().create() 474 | 475 | /** 476 | * Config that doesn't invoke callbacks for line and command complete listeners. 477 | */ 478 | fun silent(): Config = Builder().apply { notify = false }.create() 479 | } 480 | } 481 | 482 | /** 483 | * The command marker to process standard input/error streams. 484 | * 485 | * @property uuid The unique ID for a command. 486 | * @property status the exit code for the last run command. 487 | */ 488 | internal data class Marker(val uuid: UUID, val status: Int) 489 | 490 | /** Exit codes */ 491 | object Status { 492 | /** OK exit code value */ 493 | const val SUCCESS = 0 494 | 495 | /** Command timeout exit status */ 496 | const val TIMEOUT = 124 497 | 498 | /** Command failed exit status */ 499 | const val COMMAND_FAILED = 125 500 | 501 | /** Command not executable exit status */ 502 | const val NOT_EXECUTABLE = 126 503 | 504 | /** Command not found exit status */ 505 | const val NOT_FOUND = 127 506 | 507 | /** Command terminated exit status. */ 508 | const val TERMINATED = 128 + 30 509 | internal const val INVALID = 0x100 510 | } 511 | } 512 | 513 | /** 514 | * Interface to receive a callback when reading a line from standard output/error streams. 515 | */ 516 | interface OnLineListener { 517 | 518 | /** 519 | * Called when a line was read from standard output/error streams 520 | * 521 | * @param line The string that was read. 522 | */ 523 | fun onLine(line: String) 524 | } 525 | 526 | /** 527 | * Interface to receive a callback when a command completes. 528 | */ 529 | interface OnCommandResultListener { 530 | 531 | /** 532 | * Called when a command finishes running. 533 | * 534 | * @param result The result of running the command in a shell. 535 | */ 536 | fun onResult(result: Command.Result) 537 | } 538 | 539 | /** 540 | * A timeout used when running a command in a shell. 541 | * 542 | * @property value The value of the time based on the [unit]. 543 | * @property unit The time unit for the [value]. 544 | */ 545 | data class Timeout(val value: Long, val unit: TimeUnit) 546 | 547 | /** 548 | * The exception thrown when a command is passed to a closed shell. 549 | */ 550 | class ClosedException(message: String) : IOException(message) 551 | 552 | /** 553 | * The exception thrown when the shell could not be opened. 554 | */ 555 | class NotFoundException(message: String, cause: Throwable) : RuntimeException(message, cause) 556 | 557 | /** 558 | * Represents the possible states of the shell. 559 | */ 560 | sealed class State { 561 | /** The shell is idle; no commands are in progress. */ 562 | object Idle : State() 563 | 564 | /** The shell is currently running a command. */ 565 | object Running : State() 566 | 567 | /** The shell has been shutdown. */ 568 | object Shutdown : State() 569 | } 570 | 571 | /** 572 | * A class to cause the current thread to wait until a command completes or is aborted. 573 | */ 574 | private class Watchdog : CountDownLatch(STREAM_READER_COUNT) { 575 | 576 | private var aborted = false 577 | 578 | /** 579 | * Releases the thread immediately instead of waiting for [signal] to be invoked twice. 580 | */ 581 | fun abort() { 582 | if (count == 0L) return 583 | aborted = true 584 | while (count > 0) countDown() 585 | } 586 | 587 | /** 588 | * Signal that either standard output or standard input streams are finished processing. 589 | */ 590 | fun signal() = countDown() 591 | 592 | /** 593 | * Causes the current thread to wait until [signal] is called twice. 594 | * 595 | * @param timeout The maximum time to wait before [AbortedException] is thrown. 596 | * @throws AbortedException if the timeout completes before [signal] is called twice 597 | * or if the thread is interrupted. 598 | */ 599 | @Throws(AbortedException::class) fun await(timeout: Timeout?): Boolean { 600 | return when (timeout) { 601 | null -> { 602 | await(); true 603 | } 604 | 605 | else -> await(timeout.value, timeout.unit) 606 | } 607 | } 608 | 609 | override fun await() = super.await().also { 610 | if (aborted) throw AbortedException() 611 | } 612 | 613 | override fun await(timeout: Long, unit: TimeUnit) = super.await(timeout, unit).also { 614 | if (aborted) throw AbortedException() 615 | } 616 | 617 | companion object { 618 | /** 619 | * The number of times [signal] should be called to release the latch. 620 | */ 621 | private const val STREAM_READER_COUNT = 2 622 | 623 | /** 624 | * The exception thrown when [abort] is called and the [CountDownLatch] has not finished 625 | */ 626 | class AbortedException : InterruptedException() 627 | } 628 | } 629 | 630 | /** 631 | * The [OutputStream] for writing commands to the shell. 632 | */ 633 | private class StandardInputStream(stream: OutputStream) : DataOutputStream(stream) { 634 | 635 | /** 636 | * The helper function to write commands to the stream with an appended new line character. 637 | * 638 | * @param command The command to write. 639 | */ 640 | fun write(command: String) = write("$command\n".toByteArray(Charsets.UTF_8)) 641 | } 642 | 643 | /** 644 | * A thread that parses the standard/error streams for the shell. 645 | * 646 | * @param name The name of the stream. One of: [THREAD_NAME_STDOUT], [THREAD_NAME_STDERR] 647 | * @param stream Either the [Process.getInputStream] or [Process.getErrorStream] 648 | */ 649 | private class StreamReader private constructor(name: String, private val stream: InputStream) : Thread(name) { 650 | 651 | /** 652 | * The lambda that is invoked when a line is read from the stream. 653 | */ 654 | var onReadLine: (line: String) -> Unit = {} 655 | 656 | /** 657 | * The lambda that is invoked when a command completes. 658 | */ 659 | var onComplete: (marker: Command.Marker) -> Unit = {} 660 | 661 | override fun run() = BufferedReader(InputStreamReader(stream)).forEachLine { line -> 662 | pattern.matcher(line).let { matcher -> 663 | if (matcher.matches()) { 664 | val uuid = UUID.fromString(matcher.group(GROUP_UUID)) 665 | onComplete(when (val exitCode = matcher.group(GROUP_CODE)) { 666 | null -> Command.Marker(uuid, Command.Status.INVALID) 667 | else -> Command.Marker(uuid, exitCode.toInt()) 668 | }) 669 | } else { 670 | onReadLine(line) 671 | } 672 | } 673 | } 674 | 675 | companion object { 676 | 677 | private const val GROUP_UUID = 1 678 | 679 | private const val GROUP_CODE = 2 680 | 681 | // 682 | private val pattern: Pattern = Pattern.compile("^([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\\s?([0-9]{1,3})?$", Pattern.CASE_INSENSITIVE) 683 | 684 | internal fun createAndStart(name: String, stream: InputStream) = StreamReader(name, stream).also { reader -> reader.start() } 685 | } 686 | } 687 | 688 | companion object { 689 | 690 | private const val THREAD_NAME_STDOUT = "STDOUT" 691 | private const val THREAD_NAME_STDERR = "STDERR" 692 | 693 | private const val EXCEPTION_SHELL_CANNOT_OPEN = "Error opening shell: '%s'" 694 | private const val EXCEPTION_SHELL_SHUTDOWN = "The shell is shutdown" 695 | 696 | private val instances by lazy { mutableMapOf() } 697 | 698 | /** 699 | * Returns a [Shell] instance using the [path] as the path to the shell/executable.\ 700 | */ 701 | operator fun get(path: String): Shell = instances[path]?.takeIf { shell -> 702 | shell.isAlive() 703 | } ?: Shell(path).also { shell -> 704 | instances[path] = shell 705 | } 706 | 707 | /** The Bourne shell (sh) */ 708 | val SH: Shell get() = this["sh"] 709 | 710 | /** Switch to root, and run it as a shell */ 711 | val SU: Shell get() = this["su"] 712 | 713 | /** 714 | * Execute a command with the provided environment. 715 | * 716 | * @param command 717 | * The name of the program to execute. E.g. "su" or "sh". 718 | * @param environment 719 | * Map of all environment variables to include with the system environment. 720 | * @return The new [Process] instance. 721 | * @throws IOException 722 | * If the requested program could not be executed. 723 | */ 724 | @Throws(IOException::class) 725 | private fun runWithEnv(command: String, environment: EnvironmentMap): Process = Runtime.getRuntime().exec(command, (System.getenv() + environment).toArray()) 726 | 727 | /** 728 | * Convert an array to an [EnvironmentMap] with each variable/value separated by '='. 729 | * 730 | * @return The array converted to an [EnvironmentMap]. 731 | */ 732 | private fun Array.toEnvironmentMap(): EnvironmentMap = mutableMapOf().also { map -> 733 | forEach { str -> 734 | str.split("=").takeIf { arr -> 735 | arr.size == 2 736 | }?.let { (variable, value) -> 737 | map[variable] = value 738 | } 739 | } 740 | }.toMap() 741 | 742 | /** 743 | * Convert an array of [Pair] to an [EnvironmentMap]. 744 | * 745 | * @return The array of variable/value pairs as a new [EnvironmentMap]. 746 | */ 747 | private fun Array>.toEnvironmentMap(): EnvironmentMap = mutableMapOf().also { map -> 748 | forEach { (variable, value) -> 749 | map[variable] = value 750 | } 751 | } 752 | 753 | /** 754 | * Converts an [EnvironmentMap] to an array of strings with the variable/value 755 | * separated by an '=' character. 756 | * 757 | * @return An array of environment variables. 758 | */ 759 | private fun EnvironmentMap.toArray(): Array = mutableListOf().also { list -> 760 | forEach { (variable, value) -> 761 | list.add("$variable=$value") 762 | } 763 | }.toTypedArray() 764 | } 765 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/header_xiaowine.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaowine/Fuck_Home/8e79349d0b1c4d4fcfcb15bbc60a75a87c23df85/app/src/main/res/drawable/header_xiaowine.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_fore.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_small_window_dark.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_small_window_light.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | #03A9F4 5 | #0374A7 6 | #FFFFFF 7 | #BCBDBE 8 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values-ru-rRU/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | О приложении 4 | 5 | Добавить информацию 6 | Настройка стиля 7 | Fuck•MiuiHome 8 | Автор 9 | Цвет и прозрачность фона 10 | Сохранить настройки 11 | Закругленные углы фона 12 | 0~100 13 | Время работы 14 | Время работы %s 15 | Время компиляции 16 | По центру 17 | Отмена 18 | Выберите шрифт 19 | Цвет и прозрачность шрифта 20 | Пользовательские шрифты 21 | Не удалось установить пользовательский шрифт 22 | Успешно установлен пользовательский шрифт 23 | Кастомизация 24 | Debug режим 25 | Удалить шрифт 26 | Не удалось удалить пользовательский шрифт 27 | Успешно удален пользовательский шрифт 28 | По правому краю 29 | Настройки рабочего стола MIUI 30 | Скрыть элементы управления 31 | Выравнивание текста 32 | Скрыть иконки приложений 33 | Скрыть названия приложений 34 | Скрыть иконку на рабочем столе 35 | Скрыть строку состояния 36 | Hook не внедрен 37 | Hook внедрен 38 | Инициализируйте и добавляйте элементы управления 39 | Инициализируйте корневой элемент управления 40 | Диапазон ввода неверен! 41 | Отступ слева экрана при вертикальном экране 42 | Отступ слева экрана при горизонтальном экране 43 | Цветовой код неверен! 44 | Пожалуйста, введите шестнадцатеричный цветовой код, например: #C0C0C0 45 | Главный переключатель 46 | Диапазон:-100%~100% 47 | Диапазон:-2000~2000 48 | Отображать RAM 49 | RAM %s | %s | %s%% 50 | %s | %s 51 | Меню 52 | Имя пакета модуля 53 | Версия модуля 54 | Этот модуль не активирован, пожалуйста, активируйте этот модуль в LSPosed 55 | Этот модуль поддерживает только последнюю версию LSPosed 56 | Ок 57 | Снять ограничение режима планшета (MiuiHome Pad) 58 | Пиксели 59 | Перезагрузить рабочий стол 60 | Восстановление настроек 61 | Сброс настроек 62 | Сброс модуля 63 | Вы хотите сбросить модуль? 64 | Модуль в порядке, пожалуйста, не сбрасывайте его по желанию. 65 | Сброс успешно выполнен! 66 | Перезапустить приложение 67 | Отображение общего количества запущенного ПО 68 | Приложений %s 69 | Отображение общего количества запущенных служб 70 | Служб %s 71 | По левому краю 72 | Соотношение экрана 73 | Добавить небольшое окно в контекстное меню 74 | Удалить ограничения на ярлыки 75 | Маленькое окно 76 | Информация 77 | Отображать ROM 78 | ROM %s | %s | %s%% 79 | %s | %s 80 | Список благодарностей 81 | Подсказка 82 | Отступ сверху экрана при вертикальном экране 83 | Отступ сверху экрана при горизонтальном экране 84 | Блок выравнивания 85 | Снять ограничения на макет рабочего стола 86 | Снять ограничения на количество значков в нижней панели 87 | Обновить контрольные данные 88 | Управление дисплеем 89 | Измените сведения о RAM в меню недавних рабочего стола MIUI на реальное значение и добавление некоторой информации 90 | Отображать zRAM 91 | zRAM %s | %s | %s%% 92 | %s | %s 93 | Предупреждение при остатке памяти менее 20% 94 | Чистый режим 95 | Удаление кнопки приложения виджета 96 | Снятие ограничения на открытие окна 97 | UnlockNavType 98 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 墨•桌 3 | Hook失败 4 | Hook成功 5 | 将MIUI桌面后台内存详细改为真实值并添加部分信息 6 | 提示 7 | 本模块未激活,请在 LSPosed 内激活本模块\n本模块仅支持最新版 LSPosed 8 | 重启 App 9 | 菜单 10 | 隐藏桌面图标 11 | Debug 模式 12 | 重置模块 13 | 是否要重置模块 14 | 模块没问题请不要随意重置 15 | 确定 16 | 取消 17 | 自定义字体 18 | 刪除字体 19 | 设置自定义字体成功 20 | 设置自定义字体失败 21 | 刪除自定义字体成功 22 | 删除自定义字体失败 23 | 选择字体 24 | 恢复默认 25 | 添加信息 26 | 关于 27 | 感谢名单(不分先后) 28 | 摸鱼 29 | 作者 30 | 信息样式自定义 31 | 功能调整区 32 | 自定义 33 | 重置成功 34 | 重启桌面 35 | 备份配置 36 | 恢复配置 37 | 模块版本 38 | 模块包名 39 | 编译时间 40 | 总开关 41 | 显示运存 42 | 显示虚拟 43 | 显示储存 44 | 显示开机时长 45 | 显示运行软件总数 46 | 显示运行服务总数 47 | 低于20%警告 48 | 简洁模式 49 | 竖屏上边距 50 | 竖屏左边距 51 | 横屏上边距 52 | 横屏左边距 53 | 范围:-100%~100% 54 | 范围:-2000~2000 55 | 输入范围不正确! 56 | 解除平板模式限制(需要安装平板的桌面) 57 | 解锁底栏图标数量限制 58 | 解除Shortcuts限制 59 | 解除桌面布局限制 60 | 后台界面隐藏状态栏 61 | 隐藏应用名字 62 | 隐藏应用图标 63 | 字体颜色和透明度 64 | 颜色代码不正确! 65 | 请输入 16 进制颜色代码,例如: #C0C0C0 66 | 背景颜色和透明度 67 | 背景圆角 68 | 0~100 69 | 内部对齐方式 70 | 居中对齐 71 | 靠左对齐 72 | 对齐方式单位 73 | 屏幕比例 74 | 像素点 75 | 靠右对齐 76 | 初始化根控件 77 | 初始化添加控件 78 | 更新控件数据 79 | 隐藏控件 80 | 显示控件 81 | 小窗 82 | 信息 83 | 快捷菜单添加小窗 84 | 运存 %s | %s 剩余 %s%% 85 | %s | %s 86 | 虚拟 %s | %s 剩余 %s%% 87 | %s | %s 88 | 存储 %s | %s 剩余 %s%% 89 | %s | %s 90 | 已开机时长 %s 91 | 运行中应用总数 %s 92 | 运行中服务总数 %s 93 | 移除小窗应用按钮 94 | 移除小窗打开限制 95 | 解锁系统导航方式 96 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rHK/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 墨•桌 4 | Hook 失敗 5 | Hook 成功 6 | 將 MIUI 桌面後台記憶體詳細改為真實值並添加部分資訊 7 | 提示 8 | 本模組未激活,請在 LSPosed 內激活本模組\n本模組僅支援最新版 LSPosed 9 | 重新啟動 App 10 | 菜單 11 | 隱藏桌面圖標 12 | Debug 模式 13 | 重置模組 14 | 是否要重置模組 15 | 模組沒問題請不要隨意重置 16 | 確定 17 | 取消 18 | 自定義字體 19 | 刪除字體 20 | 設定自定義字體成功 21 | 設定自定義字體失敗 22 | 刪除自定義字體成功 23 | 刪除自定義字體失敗 24 | 選擇字體 25 | 重置默認 26 | 添加資訊 27 | 關於 28 | 感謝名單(不分先後) 29 | 摸魚 30 | 作者 31 | 資訊樣式自定義 32 | 功能調整區 33 | 自定義 34 | 重置成功 35 | 重新啟動桌面 36 | 備份配置 37 | 恢復配置 38 | 模組版本 39 | 模組包名 40 | 編譯時間 41 | 總開關 42 | 顯示運行記憶體 43 | 顯示虛擬運行記憶體 44 | 顯示儲存空間 45 | 顯示開機時長 46 | 顯示運行軟體總數 47 | 顯示運行服務總數 48 | 低於 20% 警告 49 | 簡潔模式 50 | 豎屏上邊距 51 | 豎屏左邊距 52 | 橫屏上邊距 53 | 橫屏左邊距 54 | 範圍:-100%~100% 55 | 範圍:-2000~2000 56 | 輸入範圍不正確! 57 | 解除平板模式限制 (需要安裝平板的桌面) 58 | 解鎖底欄圖標數量限制 59 | 解除 Shortcuts 限制 60 | 解除桌面佈局限制 61 | 後台介面隱藏狀態列 62 | 隱藏應用名稱 63 | 隱藏應用圖標 64 | 字體顏色和透明度 65 | 顏色代碼不正確! 66 | 請輸入 16 進位顏色代碼,例如: #C0C0C0 67 | 背景顏色和透明度 68 | 背景圓角 69 | 0~100 70 | 內部對齊方式 71 | 居中對齊 72 | 靠左對齊 73 | 對齊方式單位 74 | 屏幕比例 75 | 像素點 76 | 靠右對齊 77 | 初始化根控制項 78 | 初始化添加控制項 79 | 更新控制項數據 80 | 隱藏控制項 81 | 顯示控制項 82 | 小窗 83 | 資訊 84 | 快捷菜單添加小窗 85 | 運行記憶體 %s | %s 剩餘 %s%% 86 | %s | %s 87 | 虛擬運行記憶體 %s | %s 剩餘 %s%% 88 | %s | %s 89 | 存儲空間 %s | %s 剩餘 %s%% 90 | %s | %s 91 | 目前開機時長 %s 92 | 正在運行的應用總數 %s 93 | 正在運行的服務總數 %s 94 | 拿掉小窗應用按鈕 95 | 拿掉小窗打開限制 96 | 解鎖導航類型 97 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 墨•桌 4 | Hook 失敗 5 | Hook 成功 6 | 將 MIUI 桌面後台記憶體詳細改為真實值並添加部分資訊 7 | 提示 8 | 本模組未激活,請在 LSPosed 內激活本模組\n本模組僅支援最新版 LSPosed 9 | 重新啟動 App 10 | 菜單 11 | 隱藏桌面圖標 12 | Debug 模式 13 | 重置模組 14 | 是否要重置模組 15 | 模組沒問題請不要隨意重置 16 | 確定 17 | 取消 18 | 自定義字體 19 | 刪除字體 20 | 設定自定義字體成功 21 | 設定自定義字體失敗 22 | 刪除自定義字體成功 23 | 刪除自定義字體失敗 24 | 選擇字體 25 | 重置默認 26 | 添加資訊 27 | 關於 28 | 感謝名單(不分先後) 29 | 摸魚 30 | 作者 31 | 資訊樣式自定義 32 | 功能調整區 33 | 自定義 34 | 重置成功 35 | 重新啟動桌面 36 | 備份配置 37 | 恢復配置 38 | 模組版本 39 | 模組包名 40 | 編譯時間 41 | 總開關 42 | 顯示運行記憶體 43 | 顯示虛擬運行記憶體 44 | 顯示儲存空間 45 | 顯示開機時長 46 | 顯示運行軟體總數 47 | 顯示運行服務總數 48 | 低於 20% 警告 49 | 簡潔模式 50 | 豎屏上邊距 51 | 豎屏左邊距 52 | 橫屏上邊距 53 | 橫屏左邊距 54 | 範圍:-100%~100% 55 | 範圍:-2000~2000 56 | 輸入範圍不正確! 57 | 解除平板模式限制 (需要安裝平板的桌面) 58 | 解鎖底欄圖標數量限制 59 | 解除 Shortcuts 限制 60 | 解除桌面佈局限制 61 | 後台介面隱藏狀態列 62 | 隱藏應用名稱 63 | 隱藏應用圖標 64 | 字體顏色和透明度 65 | 顏色代碼不正確! 66 | 請輸入 16 進位顏色代碼,例如: #C0C0C0 67 | 背景顏色和透明度 68 | 背景圓角 69 | 0~100 70 | 內部對齊方式 71 | 居中對齊 72 | 靠左對齊 73 | 對齊方式單位 74 | 屏幕比例 75 | 像素點 76 | 靠右對齊 77 | 初始化根控制項 78 | 初始化添加控制項 79 | 更新控制項數據 80 | 隱藏控制項 81 | 顯示控制項 82 | 小窗 83 | 資訊 84 | 快捷菜單添加小窗 85 | 運行記憶體 %s | %s 剩餘 %s%% 86 | %s | %s 87 | 虛擬運行記憶體 %s | %s 剩餘 %s%% 88 | %s | %s 89 | 存儲空間 %s | %s 剩餘 %s%% 90 | %s | %s 91 | 目前開機時長 %s 92 | 正在運行的應用總數 %s 93 | 正在運行的服務總數 %s 94 | 拿掉小窗應用按鈕 95 | 拿掉小窗打開限制 96 | 解鎖導航類型 97 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.miui.home 5 | com.mi.android.globallauncher 6 | android 7 | com.android.systemui 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #03A9F4 5 | #0374A7 6 | #000000 7 | #000000 8 | #FFFFFF 9 | #4D4D4D 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | About the app 4 | 5 | Add Information 6 | Customizing the style 7 | Fuck•MiuiHome 8 | Author 9 | Background color and transparency 10 | Save Settings 11 | Rounded corners of the background 12 | 0~100 13 | Uptime 14 | Uptime %s 15 | Compilation time 16 | Centered 17 | Cancel 18 | Choose a font 19 | Font color and transparency 20 | Custom Fonts 21 | Couldn\'t install custom font 22 | The custom font has been successfully installed 23 | Customization 24 | Debug mode 25 | Remove Font 26 | Couldn\'t delete custom font 27 | Successfully removed the custom font 28 | On the right edge 29 | MIUI Desktop Settings 30 | Hide Controls 31 | Text alignment 32 | Hide application icons 33 | Hide App Names 34 | Hide the icon on the desktop 35 | Hide the status bar 36 | Hook is not implemented 37 | Hook is implemented 38 | Initialize and add controls 39 | Initialize the root control 40 | The input range is incorrect! 41 | Indent to the left of the screen when the screen is vertical 42 | Indent to the left of the screen when the screen is horizontal 43 | The color code is incorrect! 44 | Please enter a hexadecimal color code, for example: #C0C0C0 45 | Main switch 46 | Range:-100%~100% 47 | Range:-2000~2000 48 | Display RAM 49 | RAM %s | %s | %s%% 50 | %s | %s 51 | Menu 52 | Module package name 53 | Module version 54 | This module is not activated, please activate this module in LSPosed 55 | This module only supports the latest version of LSPosed 56 | OK 57 | Remove the restriction of the tablet mode (MiuiHome Pad) 58 | Pixels 59 | Restart the desktop 60 | Restoring Settings 61 | Reset Settings 62 | Resetting the module 63 | Do you want to reset the module? 64 | The module is OK, please do not reset it at will. 65 | Reset successfully completed! 66 | Restart the application 67 | Displaying the total number of running software 68 | Apps %s 69 | Displaying the total number of running services 70 | Services %s 71 | On the left edge 72 | Screen Ratio 73 | Add a small window to the context menu 74 | Remove restrictions on shortcuts 75 | Small window 76 | Info 77 | Display ROM 78 | ROM %s | %s | %s%% 79 | %s | %s 80 | List of thanks 81 | Hint 82 | Indentation at the top of the screen when the screen is vertical 83 | Indentation at the top of the screen when the screen is horizontal 84 | Alignment Unit 85 | Remove restrictions on the desktop layout 86 | Remove restrictions on the number of icons in the lower panel 87 | Update control data 88 | Display Management 89 | Change the RAM information in the recent MIUI desktop menu to the real value and add some information 90 | Display zRAM 91 | zRAM %s | %s | %s%% 92 | %s | %s 93 | Warning if the remaining memory is less than 20% 94 | Clean mode 95 | Remove the \"Floating windows\" button 96 | Remove small window restriction 97 | UnlockNavType 98 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") version "8.1.1" apply false 3 | id("com.android.library") version "8.1.1" apply false 4 | id("org.jetbrains.kotlin.android") version "1.9.0" apply false 5 | } 6 | 7 | tasks.register("clean").configure { 8 | delete(rootProject.buildDir) 9 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | android.experimental.enableNewResourceShrinker.preciseShrinking=true 23 | android.enableAppCompileTimeRClass=true 24 | org.gradle.parallel=true 25 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaowine/Fuck_Home/8e79349d0b1c4d4fcfcb15bbc60a75a87c23df85/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Feb 27 13:34:13 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | google() 7 | mavenCentral() 8 | } 9 | } 10 | dependencyResolutionManagement { 11 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 12 | repositories { 13 | google() 14 | mavenCentral() 15 | maven("https://maven.aliyun.com/repository/public") 16 | maven("https://api.xposed.info/") 17 | } 18 | } 19 | 20 | include(":app") 21 | include(":blockmiui") 22 | include(":xtoast") 23 | rootProject.name = "Fuck_Home" 24 | --------------------------------------------------------------------------------