diff --git a/.idea/misc.xml b/.idea/misc.xml index d22d7e5..0682c95 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -9,7 +9,7 @@ - + diff --git a/README.md b/README.md index cfab435..3521350 100644 --- a/README.md +++ b/README.md @@ -103,4 +103,5 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . ``` +Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)

版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com) diff --git a/app/build.gradle b/app/build.gradle index 90473bb..13ed32b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'kotlin-android' + id 'com.google.devtools.ksp' version '1.6.10-1.0.2' } android { @@ -19,17 +20,17 @@ android { defaultConfig { applicationId "com.fankes.miui.notify" - minSdk 26 - targetSdk 26 - versionCode 8 - versionName "1.36" + minSdk 23 + targetSdk 31 + versionCode rootProject.ext.appVersionCode + versionName rootProject.ext.appVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { - minifyEnabled true + minifyEnabled false signingConfig signingConfigs.debug proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } @@ -43,21 +44,29 @@ android { } } +/** 移除无效耗时 lint Task */ +tasks.whenTaskAdded { + task -> if (task.name == "lintVitalRelease") task.enabled = false +} +tasks.whenTaskAdded { + task -> if (task.name == "lintVitalAnalyzeRelease") task.enabled = false +} +tasks.whenTaskAdded { + task -> if (task.name == "lintVitalReportRelease") task.enabled = false +} + dependencies { - compileOnly 'de.robv.android.xposed:api:82' implementation "com.github.topjohnwu.libsu:core:3.1.2" - // 基础依赖包 - implementation 'com.gyf.immersionbar:immersionbar:3.0.0' - // Fragment 快速实现 - implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' - // Kotlin 扩展 - implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0' + compileOnly 'de.robv.android.xposed:api:82' + implementation 'com.highcapable.yukihookapi:api:1.0' + ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0' + implementation 'com.geyifeng.immersionbar:immersionbar:3.2.0' + implementation 'com.geyifeng.immersionbar:immersionbar-ktx:3.2.0' implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - //noinspection GradleDynamicVersion - testImplementation 'junit:junit:4.+' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a6b334e..e84fd70 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,20 +13,17 @@ android:theme="@style/Theme.MIUINativeNotifyIcon" tools:ignore="AllowBackup"> - - - + android:value="93" /> * - * This file is Created by zpp0196 on 2018/4/11. + * This file is Created by fankes on 2022/1/24. */ -package com.fankes.miui.notify.utils +@file:Suppress("DEPRECATION", "SetWorldReadable") -import com.fankes.miui.notify.BuildConfig -import de.robv.android.xposed.XSharedPreferences +package com.fankes.miui.notify.hook -object XPrefUtils { +object HookConst { - private var xPrefCacheKeyValueBooleans = HashMap() + const val ENABLE_MODULE = "_enable_module" + const val ENABLE_MODULE_LOG = "_enable_module_log" + const val ENABLE_HIDE_ICON = "_hide_icon" + const val ENABLE_COLOR_ICON_HOOK = "_color_icon_hook" + const val ENABLE_NOTIFY_ICON_HOOK = "_notify_icon_hook" - fun getBoolean(key: String, default: Boolean = false) = - xPrefCacheKeyValueBooleans[key].let { - it ?: pref.getBoolean(key, default).let { e -> - xPrefCacheKeyValueBooleans[key] = e - e - } - } - - private val pref: XSharedPreferences - get() { - val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID) - preferences.makeWorldReadable() - preferences.reload() - return preferences - } + const val SYSTEMUI_PACKAGE_NAME = "com.android.systemui" } \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/hook/HookEntry.kt b/app/src/main/java/com/fankes/miui/notify/hook/HookEntry.kt new file mode 100644 index 0000000..0c6454f --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/hook/HookEntry.kt @@ -0,0 +1,508 @@ +/* + * MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/fankes/MIUINativeNotifyIcon + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version and our eula as published + * by ferredoxin. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/2/15. + */ +package com.fankes.miui.notify.hook + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.Outline +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.service.notification.StatusBarNotification +import android.view.View +import android.view.ViewOutlineProvider +import android.widget.ImageView +import androidx.core.graphics.drawable.toBitmap +import com.fankes.miui.notify.hook.HookConst.ENABLE_COLOR_ICON_HOOK +import com.fankes.miui.notify.hook.HookConst.ENABLE_MODULE +import com.fankes.miui.notify.hook.HookConst.ENABLE_MODULE_LOG +import com.fankes.miui.notify.hook.HookConst.ENABLE_NOTIFY_ICON_HOOK +import com.fankes.miui.notify.hook.HookConst.SYSTEMUI_PACKAGE_NAME +import com.fankes.miui.notify.hook.factory.isAppNotifyHookAllOf +import com.fankes.miui.notify.hook.factory.isAppNotifyHookOf +import com.fankes.miui.notify.params.IconPackParams +import com.fankes.miui.notify.utils.* +import com.highcapable.yukihookapi.YukiHookAPI.configs +import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed +import com.highcapable.yukihookapi.hook.bean.VariousClass +import com.highcapable.yukihookapi.hook.factory.* +import com.highcapable.yukihookapi.hook.log.loggerW +import com.highcapable.yukihookapi.hook.param.PackageParam +import com.highcapable.yukihookapi.hook.type.android.ContextClass +import com.highcapable.yukihookapi.hook.type.android.DrawableClass +import com.highcapable.yukihookapi.hook.type.android.ImageViewClass +import com.highcapable.yukihookapi.hook.type.java.IntType +import com.highcapable.yukihookapi.hook.xposed.proxy.YukiHookXposedInitProxy + +@InjectYukiHookWithXposed +class HookEntry : YukiHookXposedInitProxy { + + companion object { + + /** MIUI 新版本存在的类 */ + private const val SystemUIApplicationClass = "$SYSTEMUI_PACKAGE_NAME.SystemUIApplication" + + /** MIUI 新版本存在的类 */ + private const val NotificationHeaderViewWrapperInjectorClass = + "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapperInjector" + + /** MIUI 新版本存在的类 */ + private const val NotificationHeaderViewWrapperClass = + "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationHeaderViewWrapper" + + /** MIUI 新版本存在的类 */ + private const val NotificationViewWrapperClass = + "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationViewWrapper" + + /** 原生存在的类 */ + private const val StatusBarIconViewClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.StatusBarIconView" + + /** 原生存在的类 */ + private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil" + + /** 未确定是否只有旧版本存在的类 */ + private const val ExpandableNotificationRowClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.ExpandableNotificationRow" + + /** 根据多个版本存在不同的包名相同的类 */ + private val NotificationUtilClass = VariousClass( + "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationUtil", + "$SYSTEMUI_PACKAGE_NAME.miui.statusbar.notification.NotificationUtil" + ) + + /** 根据多个版本存在不同的包名相同的类 */ + private val ExpandedNotificationClass = VariousClass( + "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.ExpandedNotification", + "$SYSTEMUI_PACKAGE_NAME.miui.statusbar.ExpandedNotification" + ) + } + + /** + * - 这个是修复彩色图标的关键核心代码判断 + * + * 判断是否为灰度图标 - 反射执行系统方法 + * @param context 实例 + * @param drawable 要判断的图标 + * @return [Boolean] + */ + private fun PackageParam.isGrayscaleIcon(context: Context, drawable: Drawable) = safeOfFalse { + ContrastColorUtilClass.clazz.let { + it.obtainMethod(name = "isGrayscaleIcon", DrawableClass) + ?.invokeAny(it.obtainMethod(name = "getInstance", ContextClass)?.invokeStatic(context), drawable) ?: false + } + } + + /** + * 获取当前通知栏的样式 + * @return [Boolean] + */ + private fun PackageParam.isShowMiuiStyle() = safeOfFalse { + NotificationUtilClass.clazz.obtainMethod(name = "showMiuiStyle")?.invokeStatic() ?: false + } + + /** + * 是否为新版本 MIUI 方案 + * + * 拥有状态栏图标颜色检查功能 + * @return [Boolean] + */ + private fun PackageParam.hasIgnoreStatusBarIconColor() = safeOfFalse { + NotificationUtilClass.clazz.hasMethod(name = "ignoreStatusBarIconColor", ExpandedNotificationClass.clazz) + } + + /** + * 获取 [ExpandedNotificationClass] 的应用名称 + * @param instance 通知实例 + * @return [String] + */ + private fun PackageParam.findAppName(instance: Any?) = safeOf(default = "") { + ExpandedNotificationClass.clazz.obtainMethod(name = "getAppName")?.invokeAny(instance) ?: "" + } + + /** + * 判断通知是否来自 MIPUSH + * @return [Boolean] + */ + private val StatusBarNotification.isXmsf get() = opPkgName == "com.xiaomi.xmsf" + + /** + * 获取全局上下文 + * @return [Context] or null + */ + private val PackageParam.globalContext + get() = safeOfNull { + SystemUIApplicationClass.clazz.obtainMethod(name = "getContext")?.invokeStatic() + } + + /** + * Hook 状态栏小图标 + * + * 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook + * @param context 实例 + * @param expandedNf 通知实例 + * @param iconDrawable 小图标 [Drawable] + * @param it 回调小图标 - ([Bitmap] 小图标) + */ + private fun PackageParam.hookSmallIconOnSet( + context: Context, + expandedNf: StatusBarNotification?, + iconDrawable: Drawable?, + it: (Bitmap) -> Unit + ) = safeRun(msg = "GetSmallIconOnSet") { + if (iconDrawable == null) return@safeRun + /** 判断是否不是灰度图标 */ + val isNotGrayscaleIcon = !isGrayscaleIcon(context, iconDrawable) + /** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */ + expandedNf?.also { notifyInstance -> + /** 目标彩色通知 APP 图标 */ + var customIcon: Bitmap? = null + if (prefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)) + run { + IconPackParams.iconDatas.forEach { + if ((notifyInstance.opPkgName == it.packageName || + findAppName(notifyInstance) == it.appName) && + isAppNotifyHookOf(it) + ) { + if (isNotGrayscaleIcon || isAppNotifyHookAllOf(it)) + customIcon = it.iconBitmap + return@run + } + } + } + when { + /** 如果开启了修复 APP 的彩色图标 */ + customIcon != null && prefs.getBoolean(ENABLE_NOTIFY_ICON_HOOK, default = true) -> it(customIcon!!) + /** 若不是灰度图标自动处理为圆角 */ + isNotGrayscaleIcon -> it(iconDrawable.toBitmap().round(15.dp(context))) + } + } + } + + /** + * Hook 通知栏小图标 + * + * 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook + * @param context 实例 + * @param expandedNf 通知实例 + * @param iconImageView 通知图标实例 + */ + private fun PackageParam.hookNotifyIconOnSet(context: Context, expandedNf: StatusBarNotification?, iconImageView: ImageView) = + safeRun(msg = "AutoSetAppIconOnSet") { + /** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */ + expandedNf?.let { notifyInstance -> + /** 是否 Hook 彩色通知图标 */ + val isHookColorIcon = prefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true) + + /** 新版风格反色 */ + val newStyle = if (context.isSystemInDarkMode) 0xFF2D2D2D.toInt() else Color.WHITE + + /** 旧版风格反色 */ + val oldStyle = if (context.isNotSystemInDarkMode) 0xFF707070.toInt() else Color.WHITE + + /** 获取通知小图标 */ + val iconDrawable = notifyInstance.notification.smallIcon.loadDrawable(context) + + /** 判断图标风格 */ + val isGrayscaleIcon = isGrayscaleIcon(context, iconDrawable) + + /** 自定义默认小图标 */ + var customIcon: Bitmap? = null + if (isHookColorIcon) run { + IconPackParams.iconDatas.forEach { + if ((notifyInstance.opPkgName == it.packageName || + findAppName(notifyInstance) == it.appName) && + isAppNotifyHookOf(it) + ) { + if (!isGrayscaleIcon || isAppNotifyHookAllOf(it)) + customIcon = it.iconBitmap + return@run + } + } + } + /** 如果开启了修复 APP 的彩色图标 */ + if (customIcon != null && prefs.getBoolean(ENABLE_NOTIFY_ICON_HOOK, default = true)) + iconImageView.apply { + /** 设置自定义小图标 */ + setImageBitmap(customIcon) + /** 上色 */ + setColorFilter(if (isUpperOfAndroidS) newStyle else oldStyle) + } + else { + /** 重新设置图标 - 防止系统更改它 */ + iconImageView.setImageDrawable(iconDrawable) + /** 判断是否开启 Hook 彩色图标 */ + if (isHookColorIcon) { + /** 判断如果是灰度图标就给他设置一个白色颜色遮罩 */ + if (isGrayscaleIcon) + iconImageView.setColorFilter(if (isUpperOfAndroidS) newStyle else oldStyle) + else + iconImageView.apply { + clipToOutline = true + /** 设置一个圆角轮廓裁切 */ + outlineProvider = object : ViewOutlineProvider() { + override fun getOutline(view: View, out: Outline) { + out.setRoundRect( + 0, 0, + view.width, view.height, 5.dp(context) + ) + } + } + /** 清除原生的背景边距设置 */ + if (isUpperOfAndroidS) setPadding(0, 0, 0, 0) + /** 清除原生的主题色背景圆圈颜色 */ + if (isUpperOfAndroidS) background = null + } + /** 否则一律设置灰度图标 */ + } else iconImageView.setColorFilter(if (isUpperOfAndroidS) newStyle else oldStyle) + } + } + } + + /** + * Hook 通知栏小图标颜色 + * + * 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook + * @param context 实例 + * @param expandedNf 状态栏实例 + * @return [Boolean] 是否忽略通知图标颜色 + */ + private fun PackageParam.hookIgnoreStatusBarIconColor(context: Context, expandedNf: StatusBarNotification?) = + if (prefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true)) safeOfFalse { + /** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */ + expandedNf?.let { notifyInstance -> + /** 获取通知小图标 */ + val iconDrawable = + notifyInstance.notification.smallIcon.loadDrawable(context) + + /** 判断是否不是灰度图标 */ + val isNotGrayscaleIcon = !isGrayscaleIcon(context, iconDrawable) + + /** 获取目标修复彩色图标的 APP */ + var isTargetApp = false + run { + IconPackParams.iconDatas.forEach { + if ((notifyInstance.opPkgName == it.packageName || + findAppName(notifyInstance) == it.appName) && + isAppNotifyHookOf(it) + ) { + if (isNotGrayscaleIcon || isAppNotifyHookAllOf(it)) isTargetApp = true + return@run + } + } + } + /** + * 如果开启了修复 APP 的彩色图标 + * 只要不是灰度就返回彩色图标 + * 否则不对颜色进行反色处理防止一些系统图标出现异常 + */ + if (isTargetApp && prefs.getBoolean(ENABLE_NOTIFY_ICON_HOOK, default = true)) + false + else isNotGrayscaleIcon + } ?: true + } else false + + override fun onHook() = encase { + configs { + debugTag = "MIUINativeNotifyIcon" + isDebug = prefs.getBoolean(ENABLE_MODULE_LOG) + } + loadApp(SYSTEMUI_PACKAGE_NAME) { + when { + /** 不是 MIUI 系统停止 Hook */ + isNotMIUI -> loggerW(msg = "Aborted Hook -> This System is not MIUI") + /** 系统版本低于 Android P 停止 Hook */ + isLowerAndroidP -> loggerW(msg = "Aborted Hook -> This System is lower than Android P") + /** 不是支持的 MIUI 系统停止 Hook */ + isNotSupportMiuiVersion -> loggerW(msg = "Aborted Hook -> This MIUI Version $miuiVersion not supported") + /** Hook 被手动关闭停止 Hook */ + !prefs.getBoolean(ENABLE_MODULE, default = true) -> loggerW(msg = "Aborted Hook -> Hook Closed") + /** 开始 Hook */ + else -> { + findClass(NotificationUtilClass).hook { + /** 强制回写系统的状态栏图标样式为原生 */ + injectMember { + method { + name = "shouldSubstituteSmallIcon" + param(ExpandedNotificationClass.clazz) + } + /** + * 因为之前的 MIUI 版本的状态栏图标颜色会全部设置为白色的 - 找不到修复的地方就直接判断版本了 + * 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook + */ + replaceAny { if (hasIgnoreStatusBarIconColor()) false else isShowMiuiStyle() } + } + if (hasIgnoreStatusBarIconColor()) + injectMember { + method { + name = "ignoreStatusBarIconColor" + param(ExpandedNotificationClass.clazz) + } + replaceAny { + hookIgnoreStatusBarIconColor( + context = globalContext ?: error("GlobalContext got null"), + expandedNf = args[0] as? StatusBarNotification? + ) + } + } + /** 强制回写系统的状态栏图标样式为原生 */ + injectMember { + var isUseLegacy = false + method { + name = "getSmallIcon" + param(ExpandedNotificationClass.clazz, IntType) + }.remedys { + method { + name = "getSmallIcon" + param(ExpandedNotificationClass.clazz) + } + method { + isUseLegacy = true + name = "getSmallIcon" + param(ContextClass, ExpandedNotificationClass.clazz) + } + } + afterHook { + /** 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook */ + if (hasIgnoreStatusBarIconColor() || !isShowMiuiStyle()) + (globalContext ?: args[0] as Context).also { context -> + hookSmallIconOnSet( + context = context, + args[if (isUseLegacy) 1 else 0] as? StatusBarNotification?, + (result as Icon).loadDrawable(context) + ) { icon -> result = Icon.createWithBitmap(icon) } + } + } + } + } + findClass(StatusBarIconViewClass).hook { + /** 修复通知图标为彩色 - MIPUSH 修复 */ + injectMember { + method { name = "updateIconColor" } + afterHook { + /** 获取自身 */ + val iconImageView = instance() ?: return@afterHook + + /** 获取通知实例 */ + val expandedNf = thisClass.obtainFieldAny(instance, name = "mNotification") + + /** + * 强制设置图标 - 防止 MIPUSH 不生效 + * 由于之前版本没有 [hasIgnoreStatusBarIconColor] 判断 - MIPUSH 的图标颜色也是白色的 + * 所以之前的版本取消这个 Hook - 实在找不到设置图标的地方 - 状态栏图标就彩色吧 + */ + if (hasIgnoreStatusBarIconColor() && expandedNf?.isXmsf == true) + hookSmallIconOnSet( + context = iconImageView.context, + expandedNf, + expandedNf.notification?.smallIcon?.loadDrawable(iconImageView.context) + ) { icon -> iconImageView.setImageBitmap(icon) } + + /** + * 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook + * 新版本不需要下面的代码设置颜色 - 同样停止 Hook + */ + if (hasIgnoreStatusBarIconColor() || isShowMiuiStyle()) return@afterHook + + /** 是否忽略图标颜色 */ + val isIgnoredColor = hookIgnoreStatusBarIconColor(iconImageView.context, expandedNf) + + /** 当前着色颜色 */ + val currentColor = field { + name = "mCurrentSetColor" + type = IntType + }.of(instance) ?: Color.WHITE + /** 判断并设置颜色 */ + if (isIgnoredColor) + iconImageView.colorFilter = null + else iconImageView.setColorFilter(currentColor) + } + } + } + if (NotificationHeaderViewWrapperInjectorClass.hasClass) + findClass(NotificationHeaderViewWrapperInjectorClass).hook { + /** 修复下拉通知图标自动设置回 APP 图标的方法 */ + injectMember { + var isUseLegacy = false + method { + name = "setAppIcon" + param(ContextClass, ImageViewClass, ExpandedNotificationClass.clazz) + }.remedys { + method { + isUseLegacy = true + name = "setAppIcon" + param(ImageViewClass, ExpandedNotificationClass.clazz) + } + } + replaceUnit { + if (isUseLegacy) + hookNotifyIconOnSet( + context = globalContext ?: error("GlobalContext got null"), + args[1] as? StatusBarNotification?, + args[0] as ImageView + ) + else + hookNotifyIconOnSet( + context = args[0] as? Context ?: globalContext ?: error("GlobalContext got null"), + args[2] as? StatusBarNotification?, + args[1] as ImageView + ) + + } + } + /** 干掉下拉通知图标自动设置回 APP 图标的方法 - Android 12 */ + if (isUpperOfAndroidS) + injectMember { + method { + name = "resetIconBgAndPaddings" + param(ImageViewClass, ExpandedNotificationClass.clazz) + } + intercept() + } + } + else + findClass(NotificationHeaderViewWrapperClass).hook { + /** 之前的版本解决方案 */ + injectMember { + method { name = "handleHeaderViews" } + afterHook { + /** 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook */ + if (!hasIgnoreStatusBarIconColor() && isShowMiuiStyle()) return@afterHook + /** 获取小图标 */ + val iconImageView = NotificationHeaderViewWrapperClass.clazz + .obtainFieldAny(any = instance, name = "mIcon") ?: return@afterHook + /** 从父类中得到 mRow 变量 - [ExpandableNotificationRowClass] */ + NotificationViewWrapperClass.clazz.obtainFieldAny(instance, name = "mRow").apply { + /** 获取其中的得到通知方法 */ + val expandedNf = + ExpandableNotificationRowClass.clazz.obtainMethod(name = "getStatusBarNotification") + ?.invokeAny(any = this) + /** 执行 Hook */ + hookNotifyIconOnSet(iconImageView.context, expandedNf, iconImageView) + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/hook/HookMain.kt b/app/src/main/java/com/fankes/miui/notify/hook/HookMain.kt deleted file mode 100644 index 6131d6b..0000000 --- a/app/src/main/java/com/fankes/miui/notify/hook/HookMain.kt +++ /dev/null @@ -1,841 +0,0 @@ -/* - * MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team. - * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) - * https://github.com/fankes/MIUINativeNotifyIcon - * - * This software is non-free but opensource software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation; either - * version 3 of the License, or any later version and our eula as published - * by ferredoxin. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * and eula along with this software. If not, see - * - * - * This file is Created by fankes on 2022/1/24. - */ -@file:Suppress("SameParameterValue", "DEPRECATION") - -package com.fankes.miui.notify.hook - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Color -import android.graphics.Outline -import android.graphics.drawable.Drawable -import android.graphics.drawable.Icon -import android.service.notification.StatusBarNotification -import android.util.Log -import android.view.View -import android.view.ViewOutlineProvider -import android.widget.ImageView -import androidx.annotation.Keep -import androidx.core.graphics.drawable.toBitmap -import com.fankes.miui.notify.hook.HookMedium.SELF_PACKAGE_NAME -import com.fankes.miui.notify.hook.HookMedium.SYSTEMUI_PACKAGE_NAME -import com.fankes.miui.notify.params.IconPackParams -import com.fankes.miui.notify.utils.* -import de.robv.android.xposed.* -import de.robv.android.xposed.callbacks.XC_LoadPackage - -@Keep -class HookMain : IXposedHookLoadPackage { - - companion object { - - /** 一直存在的类 */ - private const val SystemUIApplicationClass = "$SYSTEMUI_PACKAGE_NAME.SystemUIApplication" - - /** MIUI 新版本存在的类 */ - private const val NotificationHeaderViewWrapperInjectorClass = - "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.row.wrapper.NotificationHeaderViewWrapperInjector" - - /** 一直存在的类 */ - private const val NotificationHeaderViewWrapperClass = - "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationHeaderViewWrapper" - - /** 一直存在的类 */ - private const val NotificationViewWrapperClass = - "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationViewWrapper" - - /** 原生存在的类 */ - private const val StatusBarIconViewClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.StatusBarIconView" - - /** 原生存在的类 */ - private const val ContrastColorUtilClass = "com.android.internal.util.ContrastColorUtil" - - /** 未确定是否只有旧版本存在的类 */ - private const val ExpandableNotificationRowClass = "$SYSTEMUI_PACKAGE_NAME.statusbar.ExpandableNotificationRow" - - /** 根据多个版本存在不同的包名相同的类 */ - private val NotificationUtilClass = Pair( - "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.NotificationUtil", - "$SYSTEMUI_PACKAGE_NAME.miui.statusbar.notification.NotificationUtil" - ) - - /** 根据多个版本存在不同的包名相同的类 */ - private val ExpandedNotificationClass = Pair( - "$SYSTEMUI_PACKAGE_NAME.statusbar.notification.ExpandedNotification", - "$SYSTEMUI_PACKAGE_NAME.miui.statusbar.ExpandedNotification" - ) - } - - /** 仅作用于替换的 Hook 方法体 */ - private val replaceToNull = object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam?): Any? { - return null - } - } - - /** 仅作用于替换的 Hook 方法体 */ - private val replaceToTrue = object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam?): Any { - return true - } - } - - /** 仅作用于替换的 Hook 方法体 */ - @Suppress("unused") - private val replaceToFalse = object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam?): Any { - return false - } - } - - /** - * 忽略异常运行 - * @param error 错误信息 - * @param it 正常回调 - */ - private fun runWithoutError(error: String = "", it: () -> Unit) { - try { - it() - } catch (e: Throwable) { - logE(content = "hookFailed: $error", e) - } - } - - /** - * Print the log - * @param content - * @param it 继续执行的方法 - */ - private fun logD(content: String, it: () -> Unit = {}) { - it() - if (!HookMedium.getBoolean(HookMedium.ENABLE_MODULE_LOG, default = false)) return - XposedBridge.log("[MIUINativeNotifyIcon][D]>$content") - Log.d("MIUINativeNotifyIcon", content) - } - - /** - * Print the log - * @param content - * @param it 继续执行的方法 - */ - private fun logW(content: String, it: () -> Unit = {}) { - it() - if (!HookMedium.getBoolean(HookMedium.ENABLE_MODULE_LOG, default = false)) return - XposedBridge.log("[MIUINativeNotifyIcon][W]>$content") - Log.d("MIUINativeNotifyIcon", content) - } - - /** - * Print the log - * @param content - * @param e 异常 - * @param it 继续执行的方法 - */ - private fun logE(content: String, e: Throwable? = null, it: () -> Unit = {}) { - it() - XposedBridge.log("[MIUINativeNotifyIcon][E]>$content") - XposedBridge.log(e) - Log.e("MIUINativeNotifyIcon", content, e) - } - - /** - * 目标类是否存在 - * @param name 类名 - * @return [Boolean] - */ - private fun XC_LoadPackage.LoadPackageParam.isClassExist(name: String) = try { - classLoader.loadClass(name) - true - } catch (_: Throwable) { - false - } - - /** - * 目标方法是否存在 - * @param classPair 类数组 - * @param name 方法名 - * @param param 方法参数类型数组 - * @return [Boolean] - */ - private fun XC_LoadPackage.LoadPackageParam.isMethodExist( - classPair: Pair, - name: String, vararg param: Class<*> - ) = try { - (try { - classLoader.loadClass(classPair.first) - } catch (_: Throwable) { - try { - classLoader.loadClass(classPair.second) - } catch (_: Throwable) { - null - } - })?.getDeclaredMethod(name, *param) - true - } catch (_: Throwable) { - false - } - - /** - * 目标方法是否存在 - * @param className 类名 - * @param name 方法名 - * @param param 方法参数类型数组 - * @return [Boolean] - */ - private fun XC_LoadPackage.LoadPackageParam.isMethodExist(className: String, name: String, vararg param: Class<*>) = - try { - (try { - classLoader.loadClass(className) - } catch (_: Throwable) { - null - })?.getDeclaredMethod(name, *param) - true - } catch (_: Throwable) { - false - } - - /** - * 查找目标类 - * @param name 类名 - * @return [Class] - */ - private fun XC_LoadPackage.LoadPackageParam.findClass(name: String) = - classLoader.loadClass(name) - - /** - * 查找目标类 - 两个类都没找到才会报错 - * @param pair 类名数组 - * @return [Class] - */ - private fun XC_LoadPackage.LoadPackageParam.findClass(pair: Pair) = try { - classLoader.loadClass(pair.first) - } catch (_: Throwable) { - try { - classLoader.loadClass(pair.second) - } catch (e: Throwable) { - logE(content = "Cannot find Class ${pair.first} and ${pair.second}", e) - error("[Throwable] Cannot find Class ${pair.first} and ${pair.second}") - } - } - - /** - * 存在目标类的类名 - 两个类都没找到会抛出异常 - * @param pair 类名数组 - * @return [String] 目标类名 - */ - private fun XC_LoadPackage.LoadPackageParam.existClass(pair: Pair) = try { - classLoader.loadClass(pair.first) - pair.first - } catch (_: Throwable) { - try { - classLoader.loadClass(pair.second) - pair.second - } catch (_: Throwable) { - logE(content = "Cannot find Class ${pair.first} and ${pair.second}") - error("[Throwable] Cannot find Class ${pair.first} and ${pair.second}") - } - } - - /** - * - ⚡这个是修复彩色图标的关键核心代码判断 - * - * 判断是否为灰度图标 - 反射执行系统方法 - * @param context 实例 - * @param icon 要判断的图标 - * @return [Boolean] - */ - private fun XC_LoadPackage.LoadPackageParam.isGrayscaleIcon(context: Context, icon: Drawable) = - findClass(ContrastColorUtilClass).let { - val instance = it.getDeclaredMethod("getInstance", Context::class.java) - .apply { isAccessible = true }.invoke(null, context) - it.getDeclaredMethod("isGrayscaleIcon", Drawable::class.java) - .apply { isAccessible = true }.invoke(instance, icon) as Boolean - } - - /** - * 获取当前通知栏的样式 - * @return [Boolean] - */ - private fun XC_LoadPackage.LoadPackageParam.isShowMiuiStyle() = try { - findClass(NotificationUtilClass).let { - it.getDeclaredMethod("showMiuiStyle") - .apply { isAccessible = true }.invoke(null) as Boolean - } - } catch (_: Throwable) { - false - } - - /** - * 是否为新版本 MIUI 方案 - * - * 拥有状态栏图标颜色检查功能 - * @return [Boolean] - */ - private fun XC_LoadPackage.LoadPackageParam.hasIgnoreStatusBarIconColor() = try { - isMethodExist(NotificationUtilClass, name = "ignoreStatusBarIconColor", findClass(ExpandedNotificationClass)) - } catch (_: Throwable) { - false - } - - /** - * 获取 [ExpandedNotificationClass] 的应用名称 - * @param instance 通知实例 - * @return [String] - */ - private fun XC_LoadPackage.LoadPackageParam.findAppName(instance: Any?) = try { - findClass(ExpandedNotificationClass).getDeclaredMethod("getAppName").let { - it.isAccessible = true - it.invoke(instance) as? String ?: "" - } - } catch (e: Throwable) { - "" - } - - /** - * 判断通知是否来自 MIPUSH - * @return [Boolean] - */ - private val StatusBarNotification.isXmsf get() = opPkgName == "com.xiaomi.xmsf" - - /** - * 获取全局上下文 - * @return [Context] or null - */ - private val XC_LoadPackage.LoadPackageParam.globalContext - get() = try { - findClass(SystemUIApplicationClass) - .getDeclaredMethod("getContext").apply { isAccessible = true } - .invoke(null) as? Context? - } catch (_: Throwable) { - null - } - - /** - * Hook 状态栏小图标 - * - * 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook - * @param context 实例 - * @param expandedNf 通知实例 - * @param iconDrawable 小图标 [Drawable] - * @param isLegacyWay 旧版本 Hook 方式 - * @param it 回调小图标 - ([Bitmap] 小图标) - */ - private fun XC_LoadPackage.LoadPackageParam.hookSmallIconOnSet( - context: Context, - expandedNf: StatusBarNotification?, - iconDrawable: Drawable?, - isLegacyWay: Boolean, - it: (Bitmap) -> Unit - ) { - runWithoutError(error = "GetSmallIconOnSet") { - if (iconDrawable == null) { - logD(content = "GetSmallIconOnSet -> icon is null") - return@runWithoutError - } - /** 判断是否不是灰度图标 */ - val isNotGrayscaleIcon = !isGrayscaleIcon(context, iconDrawable) - /** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */ - expandedNf?.also { notifyInstance -> - /** 目标彩色通知 APP 图标 */ - var customIcon: Bitmap? = null - if (HookMedium.getBoolean(HookMedium.ENABLE_COLOR_ICON_HOOK, default = true)) - run { - IconPackParams.iconDatas.forEach { - if ((notifyInstance.opPkgName == it.packageName || - findAppName(notifyInstance) == it.appName) && - HookMedium.isAppNotifyHookOf(it) - ) { - if (isNotGrayscaleIcon || HookMedium.isAppNotifyHookAllOf(it)) - customIcon = it.iconBitmap - return@run - } - } - } - when { - /** 如果开启了修复 APP 的彩色图标 */ - customIcon != null && HookMedium.getBoolean(HookMedium.ENABLE_NOTIFY_ICON_HOOK, default = true) -> - logD( - content = "GetSmallIconOnSet -> " + - "hook Custom AppIcon [pkgName] ${notifyInstance.opPkgName} " + - "[appName] ${findAppName(notifyInstance)} " + - "[legacyWay] $isLegacyWay" - ) { it(customIcon!!) } - /** 若不是灰度图标自动处理为圆角 */ - isNotGrayscaleIcon -> - logD( - content = "GetSmallIconOnSet -> " + - "hook Color AppIcon [pkgName] ${notifyInstance.opPkgName} " + - "[appName] ${findAppName(notifyInstance)} " + - "[legacyWay] $isLegacyWay" - ) { - it(iconDrawable.toBitmap().round(15.dp(context))) - } - } - } ?: logW(content = "GetSmallIconOnSet -> StatusBarNotification got null [legacyWay] $isLegacyWay") - } - } - - /** - * Hook 通知栏小图标 - * - * 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook - * @param context 实例 - * @param expandedNf 通知实例 - * @param iconImageView 通知图标实例 - */ - private fun XC_LoadPackage.LoadPackageParam.hookNotifyIconOnSet( - context: Context, - expandedNf: StatusBarNotification?, - iconImageView: ImageView - ) { - runWithoutError(error = "AutoSetAppIconOnSet") { - /** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */ - expandedNf?.let { notifyInstance -> - /** 是否 Hook 彩色通知图标 */ - val isHookColorIcon = HookMedium.getBoolean(HookMedium.ENABLE_COLOR_ICON_HOOK, default = true) - - /** 新版风格反色 */ - val newStyle = if (context.isSystemInDarkMode) 0xFF2D2D2D.toInt() else Color.WHITE - - /** 旧版风格反色 */ - val oldStyle = if (context.isNotSystemInDarkMode) 0xFF707070.toInt() else Color.WHITE - - /** 获取通知小图标 */ - val iconDrawable = notifyInstance.notification.smallIcon.loadDrawable(context) - - /** 判断图标风格 */ - val isGrayscaleIcon = isGrayscaleIcon(context, iconDrawable) - - /** 自定义默认小图标 */ - var customIcon: Bitmap? = null - if (isHookColorIcon) run { - IconPackParams.iconDatas.forEach { - if ((notifyInstance.opPkgName == it.packageName || - findAppName(notifyInstance) == it.appName) && - HookMedium.isAppNotifyHookOf(it) - ) { - if (!isGrayscaleIcon || HookMedium.isAppNotifyHookAllOf(it)) - customIcon = it.iconBitmap - return@run - } - } - } - /** 如果开启了修复 APP 的彩色图标 */ - if (customIcon != null && HookMedium.getBoolean(HookMedium.ENABLE_NOTIFY_ICON_HOOK, default = true)) - iconImageView.apply { - /** 设置自定义小图标 */ - setImageBitmap(customIcon) - /** 上色 */ - setColorFilter(if (isUpperOfAndroidS) newStyle else oldStyle) - /** 输出调试日志 */ - logD( - content = "AutoSetAppIconOnSet -> " + - "hook Custom AppIcon [pkgName] ${notifyInstance.opPkgName} " + - "[appName] ${findAppName(notifyInstance)}" - ) - } - else { - /** 重新设置图标 - 防止系统更改它 */ - iconImageView.setImageDrawable(iconDrawable) - /** 判断是否开启 Hook 彩色图标 */ - if (isHookColorIcon) { - /** 判断如果是灰度图标就给他设置一个白色颜色遮罩 */ - if (isGrayscaleIcon) - logD( - content = "AutoSetAppIconOnSet -> " + - "hook Grayscale AppIcon [pkgName] ${notifyInstance.opPkgName} " + - "[appName] ${findAppName(notifyInstance)}" - ) { iconImageView.setColorFilter(if (isUpperOfAndroidS) newStyle else oldStyle) } - else - iconImageView.apply { - clipToOutline = true - /** 设置一个圆角轮廓裁切 */ - outlineProvider = object : ViewOutlineProvider() { - override fun getOutline(view: View, out: Outline) { - out.setRoundRect( - 0, 0, - view.width, view.height, 5.dp(context) - ) - } - } - /** 清除原生的背景边距设置 */ - if (isUpperOfAndroidS) setPadding(0, 0, 0, 0) - /** 清除原生的主题色背景圆圈颜色 */ - if (isUpperOfAndroidS) background = null - /** 输出调试日志 */ - logD( - content = "AutoSetAppIconOnSet -> " + - "hook Color AppIcon [pkgName] ${notifyInstance.opPkgName} " + - "[appName] ${findAppName(notifyInstance)}" - ) - } - /** 否则一律设置灰度图标 */ - } else - logD( - content = "AutoSetAppIconOnSet -> " + - "hook NonColor AppIcon [pkgName] ${notifyInstance.opPkgName} " + - "[appName] ${findAppName(notifyInstance)}" - ) { iconImageView.setColorFilter(if (isUpperOfAndroidS) newStyle else oldStyle) } - } - } ?: logW(content = "AutoSetAppIconOnSet -> StatusBarNotification got null") - } - } - - /** - * Hook 通知栏小图标颜色 - * - * 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook - * @param context 实例 - * @param expandedNf 状态栏实例 - * @return [Boolean] 是否忽略通知图标颜色 - */ - private fun XC_LoadPackage.LoadPackageParam.hookIgnoreStatusBarIconColor( - context: Context, - expandedNf: StatusBarNotification? - ) = if (HookMedium.getBoolean(HookMedium.ENABLE_COLOR_ICON_HOOK, default = true)) - try { - /** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */ - expandedNf?.let { notifyInstance -> - /** 获取通知小图标 */ - val iconDrawable = - notifyInstance.notification.smallIcon.loadDrawable(context) - - /** 判断是否不是灰度图标 */ - val isNotGrayscaleIcon = !isGrayscaleIcon(context, iconDrawable) - - /** 获取目标修复彩色图标的 APP */ - var isTargetApp = false - run { - IconPackParams.iconDatas.forEach { - if ((notifyInstance.opPkgName == it.packageName || - findAppName(notifyInstance) == it.appName) && - HookMedium.isAppNotifyHookOf(it) - ) { - if (isNotGrayscaleIcon || HookMedium.isAppNotifyHookAllOf(it)) isTargetApp = true - return@run - } - } - } - /** 如果开启了修复 APP 的彩色图标 */ - if (isTargetApp && HookMedium.getBoolean(HookMedium.ENABLE_NOTIFY_ICON_HOOK, default = true)) let { - logD( - content = "IgnoreStatusBarIconColor -> " + - "hook Color AppIcon [pkgName] ${notifyInstance.opPkgName} " + - "[appName] ${findAppName(notifyInstance)}" - ) - false - } - else let { - logD( - content = "IgnoreStatusBarIconColor -> " + - "hook Grayscale[${!isNotGrayscaleIcon}] AppIcon " + - "[pkgName] ${notifyInstance.opPkgName} " + - "[appName] ${findAppName(notifyInstance)}" - ) - /** 只要不是灰度就返回彩色图标 */ - isNotGrayscaleIcon - } - } ?: let { - logW(content = "IgnoreStatusBarIconColor -> StatusBarNotification got null") - /** 否则不对颜色进行反色处理防止一些系统图标出现异常 */ - true - } - } catch (e: Exception) { - logE("Failed to hook ignoreStatusBarIconColor", e) - false - } - else let { - logD(content = "IgnoreStatusBarIconColor -> hook NonColor AppIcon") - false - } - - override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) { - if (lpparam == null) return - when (lpparam.packageName) { - /** Hook 自身 */ - SELF_PACKAGE_NAME -> - runWithoutError(error = "HookModuleSelf") { - XposedHelpers.findAndHookMethod( - "$SELF_PACKAGE_NAME.hook.HookMedium", - lpparam.classLoader, - "isHooked", - replaceToTrue - ) - } - /** Hook 系统 UI */ - SYSTEMUI_PACKAGE_NAME -> - when { - /** 不是 MIUI 系统停止 Hook */ - isNotMIUI -> - logW(content = "Aborted Hook -> This System is not MIUI") - /** 系统版本低于 Android P 停止 Hook */ - isLowerAndroidP -> - logW(content = "Aborted Hook -> This System is lower than Android P") - /** 不是支持的 MIUI 系统停止 Hook */ - isNotSupportMiuiVersion -> - logW(content = "Aborted Hook -> This MIUI Version $miuiVersion not supported") - /** Hook 被手动关闭停止 Hook */ - !HookMedium.getBoolean(HookMedium.ENABLE_MODULE, default = true) -> - logW(content = "Aborted Hook -> Hook Closed") - else -> { - /** 强制回写系统的状态栏图标样式为原生 */ - runWithoutError(error = "SubstituteSmallIcon") { - XposedHelpers.findAndHookMethod( - lpparam.existClass(NotificationUtilClass), - lpparam.classLoader, - "shouldSubstituteSmallIcon", - lpparam.findClass(ExpandedNotificationClass), - object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam) = - /** - * 因为之前的 MIUI 版本的状态栏图标颜色会全部设置为白色的 - 找不到修复的地方就直接判断版本了 - * 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook - */ - if (lpparam.hasIgnoreStatusBarIconColor()) false else lpparam.isShowMiuiStyle() - } - ) - } - /** 修复通知图标为彩色 */ - if (lpparam.hasIgnoreStatusBarIconColor()) - runWithoutError(error = "IgnoreStatusBarIconColor") { - XposedHelpers.findAndHookMethod( - lpparam.existClass(NotificationUtilClass), - lpparam.classLoader, - "ignoreStatusBarIconColor", - lpparam.findClass(ExpandedNotificationClass), - object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam) = - lpparam.hookIgnoreStatusBarIconColor( - context = lpparam.globalContext ?: error("GlobalContext got null"), - param.args?.get(0) as? StatusBarNotification? - ) - } - ) - } - /** 修复通知图标为彩色 - MIPUSH 修复 */ - runWithoutError(error = "UpdateIconColor") { - XposedHelpers.findAndHookMethod( - StatusBarIconViewClass, - lpparam.classLoader, "updateIconColor", - object : XC_MethodHook() { - override fun afterHookedMethod(param: MethodHookParam) = - runWithoutError(error = "UpdateIconColorOnSet") hook@{ - /** 获取自身 */ - val iconImageView = param.thisObject as? ImageView ?: return@hook - - /** 获取通知实例 */ - val expandedNf = - param.thisObject.javaClass.getDeclaredField("mNotification").apply { - isAccessible = true - }[param.thisObject] as? StatusBarNotification? - - /** - * 强制设置图标 - 防止 MIPUSH 不生效 - * 由于之前版本没有 [hasIgnoreStatusBarIconColor] 判断 - MIPUSH 的图标颜色也是白色的 - * 所以之前的版本取消这个 Hook - 实在找不到设置图标的地方 - 状态栏图标就彩色吧 - */ - if (lpparam.hasIgnoreStatusBarIconColor() && expandedNf?.isXmsf == true) - lpparam.hookSmallIconOnSet( - context = iconImageView.context, - expandedNf, - expandedNf.notification?.smallIcon?.loadDrawable(iconImageView.context), - isLegacyWay = true - ) { icon -> iconImageView.setImageBitmap(icon) } - - /** - * 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook - * 新版本不需要下面的代码设置颜色 - 同样停止 Hook - */ - if (lpparam.hasIgnoreStatusBarIconColor() || lpparam.isShowMiuiStyle()) return@hook - - /** 是否忽略图标颜色 */ - val isIgnoredColor = - lpparam.hookIgnoreStatusBarIconColor(iconImageView.context, expandedNf) - - /** 当前着色颜色 */ - val currentColor = - param.thisObject.javaClass.getDeclaredField("mCurrentSetColor").apply { - isAccessible = true - }[param.thisObject] as? Int ?: Color.WHITE - /** 判断并设置颜色 */ - if (isIgnoredColor) - iconImageView.colorFilter = null - else iconImageView.setColorFilter(currentColor) - logD(content = "IgnoreStatusBarIconColor[UseOldWay] -> isIgnored[$isIgnoredColor]") - } - } - ) - } - /** 强制回写系统的状态栏图标样式为原生 */ - runWithoutError(error = "GetSmallIcon") { - var isTooOld: Boolean - try { - isTooOld = false - /** 新版方法 */ - lpparam.findClass(NotificationUtilClass) - .getDeclaredMethod( - "getSmallIcon", - lpparam.findClass(ExpandedNotificationClass), - Int::class.java - ).apply { isAccessible = true } - } catch (_: Throwable) { - try { - isTooOld = false - /** 旧版方法 */ - lpparam.findClass(NotificationUtilClass) - .getDeclaredMethod("getSmallIcon", lpparam.findClass(ExpandedNotificationClass)) - .apply { isAccessible = true } - } catch (_: Throwable) { - isTooOld = true - /** 超旧版方法 */ - lpparam.findClass(NotificationUtilClass) - .getDeclaredMethod( - "getSmallIcon", - Context::class.java, - lpparam.findClass(ExpandedNotificationClass) - ).apply { isAccessible = true } - } - }.also { - XposedBridge.hookMethod(it, object : XC_MethodHook() { - override fun afterHookedMethod(param: MethodHookParam) { - /** 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook */ - if (!lpparam.hasIgnoreStatusBarIconColor() && lpparam.isShowMiuiStyle()) return - runWithoutError(error = "GetSmallIconDoing") { - (lpparam.globalContext ?: param.args[0] as Context).also { context -> - lpparam.hookSmallIconOnSet( - context = context, - param.args?.get(if (isTooOld) 1 else 0) as? StatusBarNotification?, - (param.result as Icon).loadDrawable(context), - isLegacyWay = isTooOld - ) { icon -> param.result = Icon.createWithBitmap(icon) } - } - } - } - }) - } - } - /** 修复下拉通知图标自动设置回 APP 图标的方法 */ - if (lpparam.isClassExist(NotificationHeaderViewWrapperInjectorClass)) - runWithoutError(error = "AutoSetAppIcon") { - var isNewWay = true - try { - /** 新版方法 */ - lpparam.findClass(NotificationHeaderViewWrapperInjectorClass) - .getDeclaredMethod( - "setAppIcon", - Context::class.java, - ImageView::class.java, - lpparam.findClass(ExpandedNotificationClass) - ).apply { isAccessible = true } - } catch (_: Throwable) { - isNewWay = false - /** 旧版方法 */ - lpparam.findClass(NotificationHeaderViewWrapperInjectorClass) - .getDeclaredMethod( - "setAppIcon", - ImageView::class.java, - lpparam.findClass(ExpandedNotificationClass) - ).apply { isAccessible = true } - }.also { - XposedBridge.hookMethod(it, object : XC_MethodReplacement() { - override fun replaceHookedMethod(param: MethodHookParam): Any? { - runWithoutError(error = "AutoSetAppIconDoing") { - if (isNewWay) - lpparam.hookNotifyIconOnSet( - context = param.args?.get(0) as? Context ?: lpparam.globalContext - ?: error("GlobalContext got null"), - param.args?.get(2) as? StatusBarNotification?, - param.args?.get(1) as ImageView - ) - else - lpparam.hookNotifyIconOnSet( - context = lpparam.globalContext ?: error("GlobalContext got null"), - param.args?.get(1) as? StatusBarNotification?, - param.args?.get(0) as ImageView - ) - } - return null - } - }) - } - } - /** 之前的版本解决方案 */ - else runWithoutError(error = "AutoSetAppIconOldWay") { - XposedHelpers.findAndHookMethod( - NotificationHeaderViewWrapperClass, - lpparam.classLoader, "handleHeaderViews", - object : XC_MethodHook() { - override fun afterHookedMethod(param: MethodHookParam) { - runWithoutError(error = "AutoSetAppIconOldWayOnSet") hook@{ - /** 对于之前没有通知图标色彩判断功能的版本判断是 MIUI 样式就停止 Hook */ - if (!lpparam.hasIgnoreStatusBarIconColor() && lpparam.isShowMiuiStyle()) return@hook - /** 从父类中得到 mRow 变量 - [ExpandableNotificationRowClass] */ - lpparam.findClass(NotificationViewWrapperClass).getDeclaredField("mRow") - .apply { - isAccessible = true - }[param.thisObject].apply { - /** 获取小图标 */ - val iconImageView = lpparam.findClass(NotificationHeaderViewWrapperClass) - .getDeclaredField("mIcon") - .apply { - isAccessible = true - }[param.thisObject] as ImageView - - /** 获取其中的得到通知方法 */ - val expandedNf = - javaClass.getDeclaredMethod("getStatusBarNotification").apply { - isAccessible = true - }.invoke(this) as? StatusBarNotification? - /** 执行 Hook */ - lpparam.hookNotifyIconOnSet( - iconImageView.context, - expandedNf, iconImageView - ) - } - } - } - } - ) - } - /** 干掉下拉通知图标自动设置回 APP 图标的方法 - Android 12 */ - if (isUpperOfAndroidS && - lpparam.isMethodExist( - NotificationHeaderViewWrapperInjectorClass, - name = "resetIconBgAndPaddings" - ) - ) runWithoutError(error = "ResetIconBgAndPaddings") { - XposedHelpers.findAndHookMethod( - NotificationHeaderViewWrapperInjectorClass, - lpparam.classLoader, - "resetIconBgAndPaddings", - ImageView::class.java, - lpparam.findClass(ExpandedNotificationClass), - replaceToNull - ) - } - logD(content = "hook Completed!") - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/hook/HookMedium.kt b/app/src/main/java/com/fankes/miui/notify/hook/HookMedium.kt deleted file mode 100644 index 8c24333..0000000 --- a/app/src/main/java/com/fankes/miui/notify/hook/HookMedium.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team. - * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) - * https://github.com/fankes/MIUINativeNotifyIcon - * - * This software is non-free but opensource software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation; either - * version 3 of the License, or any later version and our eula as published - * by ferredoxin. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * and eula along with this software. If not, see - * - * - * This file is Created by fankes on 2022/1/24. - */ -@file:Suppress("DEPRECATION", "SetWorldReadable") - -package com.fankes.miui.notify.hook - -import android.content.Context -import android.os.Handler -import android.util.Log -import android.widget.Toast -import androidx.annotation.Keep -import com.fankes.miui.notify.application.MNNApplication.Companion.appContext -import com.fankes.miui.notify.application.MNNApplication.Companion.isMineStarted -import com.fankes.miui.notify.bean.IconDataBean -import com.fankes.miui.notify.utils.FileUtils -import com.fankes.miui.notify.utils.XPrefUtils -import java.io.File - -@Keep -object HookMedium { - - const val ENABLE_MODULE = "_enable_module" - const val ENABLE_MODULE_LOG = "_enable_module_log" - const val ENABLE_HIDE_ICON = "_hide_icon" - const val ENABLE_COLOR_ICON_HOOK = "_color_icon_hook" - const val ENABLE_NOTIFY_ICON_HOOK = "_notify_icon_hook" - - const val SELF_PACKAGE_NAME = "com.fankes.miui.notify" - const val SYSTEMUI_PACKAGE_NAME = "com.android.systemui" - - /** - * 判断模块是否激活 - * - * 在 [HookMain] 中 Hook 掉此方法 - * @return [Boolean] 激活状态 - */ - fun isHooked(): Boolean { - Log.d("MIUINativeNotifyIcon", "isHooked: true") - return false - } - - /** - * 获取此 APP 的通知图标是否被 Hook - * @param bean 图标 bean - */ - fun isAppNotifyHookOf(bean: IconDataBean) = getBoolean(key = bean.toEnabledName(), default = bean.isEnabled) - - /** - * 设置 Hook 此 APP 的通知图标 - * @param bean 图标 bean - * @param isHook 是否 Hook - */ - fun putAppNotifyHookOf(bean: IconDataBean, isHook: Boolean) = putBoolean(key = bean.toEnabledName(), bool = isHook) - - /** - * 获取此 APP 的通知图标是否被全部 Hook - * @param bean 图标 bean - */ - fun isAppNotifyHookAllOf(bean: IconDataBean) = getBoolean(key = bean.toEnabledAllName(), default = bean.isEnabledAll) - - /** - * 设置全部 Hook 此 APP 的通知图标 - * @param bean 图标 bean - * @param isHook 是否 Hook - */ - fun putAppNotifyHookAllOf(bean: IconDataBean, isHook: Boolean) = putBoolean(key = bean.toEnabledAllName(), bool = isHook) - - /** - * 获取保存的值 - * @param key 名称 - * @param default 默认值 - * @return [Boolean] 保存的值 - */ - fun getBoolean(key: String, default: Boolean = false) = - if (isMineStarted) - appContext.getSharedPreferences( - appContext.packageName + "_preferences", - Context.MODE_PRIVATE - ).getBoolean(key, default) - else XPrefUtils.getBoolean(key, default) - - /** - * 保存值 - * @param key 名称 - * @param bool 值 - */ - fun putBoolean(key: String, bool: Boolean) { - appContext.getSharedPreferences( - appContext.packageName + "_preferences", - Context.MODE_PRIVATE - ).edit().putBoolean(key, bool).apply() - setWorldReadable(appContext) - /** 延迟继续设置强制允许 SP 可读可写 */ - Handler().postDelayed({ setWorldReadable(appContext) }, 500) - Handler().postDelayed({ setWorldReadable(appContext) }, 1000) - Handler().postDelayed({ setWorldReadable(appContext) }, 1500) - } - - /** - * 强制设置 Sp 存储为全局可读可写 - * - * 以供模块使用 - * @param context 实例 - */ - fun setWorldReadable(context: Context) { - try { - if (FileUtils.getDefaultPrefFile(context).exists()) { - for (file in arrayOf( - FileUtils.getDataDir(context), - FileUtils.getPrefDir(context), - FileUtils.getDefaultPrefFile(context) - )) { - file.setReadable(true, false) - file.setExecutable(true, false) - } - } - } catch (_: Exception) { - Toast.makeText(context, "无法写入模块设置,请检查权限\n如果此提示一直显示,请不要双开模块", Toast.LENGTH_SHORT).show() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/hook/factory/DataFactory.kt b/app/src/main/java/com/fankes/miui/notify/hook/factory/DataFactory.kt new file mode 100644 index 0000000..e53531a --- /dev/null +++ b/app/src/main/java/com/fankes/miui/notify/hook/factory/DataFactory.kt @@ -0,0 +1,70 @@ +/* + * MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/fankes/MIUINativeNotifyIcon + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version and our eula as published + * by ferredoxin. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/2/15. + */ +package com.fankes.miui.notify.hook.factory + +import android.content.Context +import com.fankes.miui.notify.bean.IconDataBean +import com.highcapable.yukihookapi.hook.factory.modulePrefs +import com.highcapable.yukihookapi.hook.param.PackageParam + +/** + * 获取此 APP 的通知图标是否被 Hook + * @param bean 图标 bean + */ +fun PackageParam.isAppNotifyHookOf(bean: IconDataBean) = prefs.getBoolean(key = bean.toEnabledName(), default = bean.isEnabled) + +/** + * 获取此 APP 的通知图标是否被 Hook + * @param bean 图标 bean + */ +fun Context.isAppNotifyHookOf(bean: IconDataBean) = modulePrefs.getBoolean(key = bean.toEnabledName(), default = bean.isEnabled) + +/** + * 设置 Hook 此 APP 的通知图标 + * @param bean 图标 bean + * @param isHook 是否 Hook + */ +fun Context.putAppNotifyHookOf(bean: IconDataBean, isHook: Boolean) = + modulePrefs.putBoolean(key = bean.toEnabledName(), value = isHook) + +/** + * 获取此 APP 的通知图标是否被全部 Hook + * @param bean 图标 bean + */ +fun PackageParam.isAppNotifyHookAllOf(bean: IconDataBean) = + prefs.getBoolean(key = bean.toEnabledAllName(), default = bean.isEnabledAll) + +/** + * 获取此 APP 的通知图标是否被全部 Hook + * @param bean 图标 bean + */ +fun Context.isAppNotifyHookAllOf(bean: IconDataBean) = + modulePrefs.getBoolean(key = bean.toEnabledAllName(), default = bean.isEnabledAll) + +/** + * 设置全部 Hook 此 APP 的通知图标 + * @param bean 图标 bean + * @param isHook 是否 Hook + */ +fun Context.putAppNotifyHookAllOf(bean: IconDataBean, isHook: Boolean) = + modulePrefs.putBoolean(key = bean.toEnabledAllName(), value = isHook) \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/ui/ConfigureActivity.kt b/app/src/main/java/com/fankes/miui/notify/ui/ConfigureActivity.kt index 8eb55fa..3e5acf9 100644 --- a/app/src/main/java/com/fankes/miui/notify/ui/ConfigureActivity.kt +++ b/app/src/main/java/com/fankes/miui/notify/ui/ConfigureActivity.kt @@ -36,7 +36,10 @@ import android.widget.TextView import android.widget.Toast import androidx.constraintlayout.utils.widget.ImageFilterView import com.fankes.miui.notify.R -import com.fankes.miui.notify.hook.HookMedium +import com.fankes.miui.notify.hook.factory.isAppNotifyHookAllOf +import com.fankes.miui.notify.hook.factory.isAppNotifyHookOf +import com.fankes.miui.notify.hook.factory.putAppNotifyHookAllOf +import com.fankes.miui.notify.hook.factory.putAppNotifyHookOf import com.fankes.miui.notify.params.IconPackParams import com.fankes.miui.notify.ui.base.BaseActivity import com.fankes.miui.notify.utils.SystemUITool @@ -87,20 +90,20 @@ class ConfigureActivity : BaseActivity() { holder.appName.text = it.appName holder.pkgName.text = it.packageName holder.cbrName.text = "贡献者:" + it.contributorName - HookMedium.isAppNotifyHookOf(it).also { e -> + isAppNotifyHookOf(it).also { e -> holder.switchOpen.isChecked = e holder.switchAll.isEnabled = e } holder.switchOpen.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - HookMedium.putAppNotifyHookOf(it, b) + putAppNotifyHookOf(it, b) holder.switchAll.isEnabled = b SystemUITool.showNeedRestartSnake(context = this@ConfigureActivity) } - holder.switchAll.isChecked = HookMedium.isAppNotifyHookAllOf(it) + holder.switchAll.isChecked = isAppNotifyHookAllOf(it) holder.switchAll.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - HookMedium.putAppNotifyHookAllOf(it, b) + putAppNotifyHookAllOf(it, b) SystemUITool.showNeedRestartSnake(context = this@ConfigureActivity) } } @@ -119,14 +122,14 @@ class ConfigureActivity : BaseActivity() { } /** 设置点击事件 */ findViewById(R.id.config_cbr_button).setOnClickListener { - try { + runCatching { startActivity(Intent().apply { action = "android.intent.action.VIEW" data = Uri.parse("https://github.com/fankes/MIUINativeNotifyIcon") /** 防止顶栈一样重叠在自己的 APP 中 */ flags = Intent.FLAG_ACTIVITY_NEW_TASK }) - } catch (e: Exception) { + }.onFailure { Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show() } } diff --git a/app/src/main/java/com/fankes/miui/notify/ui/MainActivity.kt b/app/src/main/java/com/fankes/miui/notify/ui/MainActivity.kt index 2e9e82c..a5b7ebd 100644 --- a/app/src/main/java/com/fankes/miui/notify/ui/MainActivity.kt +++ b/app/src/main/java/com/fankes/miui/notify/ui/MainActivity.kt @@ -38,9 +38,15 @@ import androidx.constraintlayout.utils.widget.ImageFilterView import androidx.core.view.isVisible import com.fankes.miui.notify.BuildConfig import com.fankes.miui.notify.R -import com.fankes.miui.notify.hook.HookMedium +import com.fankes.miui.notify.hook.HookConst.ENABLE_COLOR_ICON_HOOK +import com.fankes.miui.notify.hook.HookConst.ENABLE_HIDE_ICON +import com.fankes.miui.notify.hook.HookConst.ENABLE_MODULE +import com.fankes.miui.notify.hook.HookConst.ENABLE_MODULE_LOG +import com.fankes.miui.notify.hook.HookConst.ENABLE_NOTIFY_ICON_HOOK import com.fankes.miui.notify.ui.base.BaseActivity import com.fankes.miui.notify.utils.* +import com.highcapable.yukihookapi.hook.factory.modulePrefs +import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus class MainActivity : BaseActivity() { @@ -113,27 +119,27 @@ class MainActivity : BaseActivity() { /** 设置旧版本警告 */ findViewById(R.id.config_notify_app_icon_warn).isVisible = miuiVersion == "12" /** 获取 Sp 存储的信息 */ - notifyIconConfigItem.isVisible = getBoolean(HookMedium.ENABLE_COLOR_ICON_HOOK, default = true) - moduleEnableLogSwitch.isVisible = getBoolean(HookMedium.ENABLE_MODULE, default = true) - moduleEnableSwitch.isChecked = getBoolean(HookMedium.ENABLE_MODULE, default = true) - moduleEnableLogSwitch.isChecked = getBoolean(HookMedium.ENABLE_MODULE_LOG, default = false) - hideIconInLauncherSwitch.isChecked = getBoolean(HookMedium.ENABLE_HIDE_ICON) - colorIconHookSwitch.isChecked = getBoolean(HookMedium.ENABLE_COLOR_ICON_HOOK, default = true) - notifyIconHookSwitch.isChecked = getBoolean(HookMedium.ENABLE_NOTIFY_ICON_HOOK, default = true) + notifyIconConfigItem.isVisible = modulePrefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true) + moduleEnableLogSwitch.isVisible = modulePrefs.getBoolean(ENABLE_MODULE, default = true) + moduleEnableSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE, default = true) + moduleEnableLogSwitch.isChecked = modulePrefs.getBoolean(ENABLE_MODULE_LOG, default = false) + hideIconInLauncherSwitch.isChecked = modulePrefs.getBoolean(ENABLE_HIDE_ICON) + colorIconHookSwitch.isChecked = modulePrefs.getBoolean(ENABLE_COLOR_ICON_HOOK, default = true) + notifyIconHookSwitch.isChecked = modulePrefs.getBoolean(ENABLE_NOTIFY_ICON_HOOK, default = true) moduleEnableSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.ENABLE_MODULE, b) + modulePrefs.putBoolean(ENABLE_MODULE, b) moduleEnableLogSwitch.isVisible = b SystemUITool.showNeedRestartSnake(context = this) } moduleEnableLogSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.ENABLE_MODULE_LOG, b) + modulePrefs.putBoolean(ENABLE_MODULE_LOG, b) SystemUITool.showNeedRestartSnake(context = this) } hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.ENABLE_HIDE_ICON, b) + modulePrefs.putBoolean(ENABLE_HIDE_ICON, b) packageManager.setComponentEnabledSetting( ComponentName(this@MainActivity, "com.fankes.miui.notify.Home"), if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED, @@ -142,13 +148,13 @@ class MainActivity : BaseActivity() { } colorIconHookSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.ENABLE_COLOR_ICON_HOOK, b) + modulePrefs.putBoolean(ENABLE_COLOR_ICON_HOOK, b) notifyIconConfigItem.isVisible = b SystemUITool.showNeedRestartSnake(context = this) } notifyIconHookSwitch.setOnCheckedChangeListener { btn, b -> if (!btn.isPressed) return@setOnCheckedChangeListener - putBoolean(HookMedium.ENABLE_NOTIFY_ICON_HOOK, b) + modulePrefs.putBoolean(ENABLE_NOTIFY_ICON_HOOK, b) SystemUITool.showNeedRestartSnake(context = this) } /** 重启按钮点击事件 */ @@ -159,7 +165,7 @@ class MainActivity : BaseActivity() { } /** 恰饭! */ findViewById(R.id.link_with_follow_me).setOnClickListener { - try { + runCatching { startActivity(Intent().apply { setPackage("com.coolapk.market") action = "android.intent.action.VIEW" @@ -167,20 +173,20 @@ class MainActivity : BaseActivity() { /** 防止顶栈一样重叠在自己的 APP 中 */ flags = Intent.FLAG_ACTIVITY_NEW_TASK }) - } catch (e: Exception) { + }.onFailure { Toast.makeText(this, "你可能没有安装酷安", Toast.LENGTH_SHORT).show() } } /** 项目地址点击事件 */ findViewById(R.id.link_with_project_address).setOnClickListener { - try { + runCatching { startActivity(Intent().apply { action = "android.intent.action.VIEW" data = Uri.parse("https://github.com/fankes/MIUINativeNotifyIcon") /** 防止顶栈一样重叠在自己的 APP 中 */ flags = Intent.FLAG_ACTIVITY_NEW_TASK }) - } catch (e: Exception) { + }.onFailure { Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show() } } @@ -204,20 +210,5 @@ class MainActivity : BaseActivity() { * 判断模块是否激活 * @return [Boolean] 激活状态 */ - private fun isHooked() = HookMedium.isHooked() - - /** - * 获取保存的值 - * @param key 名称 - * @param default 默认值 - * @return [Boolean] 保存的值 - */ - private fun getBoolean(key: String, default: Boolean = false) = HookMedium.getBoolean(key, default) - - /** - * 保存值 - * @param key 名称 - * @param bool 值 - */ - private fun putBoolean(key: String, bool: Boolean) = HookMedium.putBoolean(key, bool) + private fun isHooked() = YukiHookModuleStatus.isActive() } \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/ui/base/BaseActivity.kt b/app/src/main/java/com/fankes/miui/notify/ui/base/BaseActivity.kt index 5122084..5e60b4c 100644 --- a/app/src/main/java/com/fankes/miui/notify/ui/base/BaseActivity.kt +++ b/app/src/main/java/com/fankes/miui/notify/ui/base/BaseActivity.kt @@ -25,7 +25,6 @@ package com.fankes.miui.notify.ui.base import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.fankes.miui.notify.R -import com.fankes.miui.notify.hook.HookMedium import com.fankes.miui.notify.utils.isNotSystemInDarkMode import com.gyf.immersionbar.ktx.immersionBar @@ -45,24 +44,4 @@ abstract class BaseActivity : AppCompatActivity() { fitsSystemWindows(true) } } - - override fun onResume() { - super.onResume() - HookMedium.setWorldReadable(this) - } - - override fun onRestart() { - super.onRestart() - HookMedium.setWorldReadable(this) - } - - override fun onPause() { - super.onPause() - HookMedium.setWorldReadable(this) - } - - override fun onBackPressed() { - HookMedium.setWorldReadable(this) - super.onBackPressed() - } } \ No newline at end of file diff --git a/app/src/main/java/com/fankes/miui/notify/utils/FileUtils.java b/app/src/main/java/com/fankes/miui/notify/utils/FileUtils.java deleted file mode 100755 index ac91b7e..0000000 --- a/app/src/main/java/com/fankes/miui/notify/utils/FileUtils.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * MIUINativeNotifyIcon - Fix the native notification bar icon function abandoned by the MIUI development team. - * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) - * https://github.com/fankes/MIUINativeNotifyIcon - * - * This software is non-free but opensource software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation; either - * version 3 of the License, or any later version and our eula as published - * by ferredoxin. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * and eula along with this software. If not, see - * - * - * This file is Created by zpp0196 on 2019/2/9. - */ -package com.fankes.miui.notify.utils; - -import android.content.Context; - -import com.fankes.miui.notify.BuildConfig; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -@SuppressWarnings("ALL") -public class FileUtils { - - private static final String FILE_PREF_NAME = BuildConfig.APPLICATION_ID + "_preferences.xml"; - - public static boolean copyFile(File srcFile, File targetFile) { - FileInputStream ins = null; - FileOutputStream out = null; - try { - if (targetFile.exists()) { - targetFile.delete(); - } - File targetParent = targetFile.getParentFile(); - if (!targetParent.exists()) { - targetParent.mkdirs(); - } - targetFile.createNewFile(); - ins = new FileInputStream(srcFile); - out = new FileOutputStream(targetFile); - byte[] b = new byte[1024]; - int n; - while ((n = ins.read(b)) != -1) { - out.write(b, 0, n); - } - } catch (IOException e) { - e.printStackTrace(); - return false; - } finally { - try { - if (ins != null) { - ins.close(); - } - if (out != null) { - out.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - return true; - } - - public static File getDataDir(Context context) { - return new File(context.getApplicationInfo().dataDir); - } - - public static File getPrefDir(Context context) { - return new File(getDataDir(context), "shared_prefs"); - } - - public static File getDefaultPrefFile(Context context) { - return new File(getPrefDir(context), FILE_PREF_NAME); - } -} diff --git a/app/src/main/java/com/fankes/miui/notify/utils/Utils.kt b/app/src/main/java/com/fankes/miui/notify/utils/Utils.kt index 3c8d564..2d4009f 100644 --- a/app/src/main/java/com/fankes/miui/notify/utils/Utils.kt +++ b/app/src/main/java/com/fankes/miui/notify/utils/Utils.kt @@ -35,6 +35,12 @@ import android.provider.Settings import android.service.notification.StatusBarNotification import android.util.Base64 import com.fankes.miui.notify.application.MNNApplication.Companion.appContext +import com.highcapable.yukihookapi.hook.factory.classOf +import com.highcapable.yukihookapi.hook.factory.hasClass +import com.highcapable.yukihookapi.hook.factory.invokeStatic +import com.highcapable.yukihookapi.hook.factory.obtainMethod +import com.highcapable.yukihookapi.hook.log.loggerE +import com.highcapable.yukihookapi.hook.type.java.StringType import com.topjohnwu.superuser.Shell /** @@ -60,12 +66,7 @@ val Context.isSystemInDarkMode get() = (resources.configuration.uiMode and Confi * 通知栏是否为 MIUI 样式 * @return [Boolean] 是否符合条件 */ -val Context.isMiuiNotifyStyle - get() = try { - Settings.System.getInt(contentResolver, "status_bar_notification_style") == 0 - } catch (_: Throwable) { - false - } +val Context.isMiuiNotifyStyle get() = safeOfFalse { Settings.System.getInt(contentResolver, "status_bar_notification_style") == 0 } /** * 系统深色模式是否没开启 @@ -89,14 +90,7 @@ inline val isLowerAndroidP get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P * 当前设备是否是 MIUI 定制 Android 系统 * @return [Boolean] 是否符合条件 */ -val isMIUI by lazy { - try { - Class.forName("android.miui.R") - true - } catch (_: Exception) { - false - } -} +val isMIUI by lazy { ("android.miui.R").hasClass } /** * 当前设备是否不是 MIUI 定制 Android 系统 @@ -168,16 +162,13 @@ val Context.packageInfo get() = packageManager?.getPackageInfo(packageName, 0) ? * @return [Boolean] */ val String.isInstall - get() = - try { - appContext.packageManager.getPackageInfo( - this, - PackageManager.GET_UNINSTALLED_PACKAGES - ) - true - } catch (e: Exception) { - false - } + get() = safeOfFalse { + appContext.packageManager.getPackageInfo( + this, + PackageManager.GET_UNINSTALLED_PACKAGES + ) + true + } /** * 得到版本信息 @@ -253,26 +244,116 @@ fun Bitmap.round(radius: Float): Bitmap = * @param default 默认值 * @return [String] */ -fun findPropString(key: String, default: String = "") = - try { - (Class.forName("android.os.SystemProperties").getDeclaredMethod( - "get", - String::class.java, - String::class.java - ).apply { isAccessible = true }.invoke(null, key, default)) as? String? ?: default - } catch (_: Exception) { - default - } +fun findPropString(key: String, default: String = "") = safeOf(default) { + (classOf(name = "android.os.SystemProperties").obtainMethod( + name = "get", StringType, StringType + )?.invokeStatic(key, default)) ?: default +} /** * 执行命令 - su * @param cmd 命令 * @return [String] 执行结果 */ -fun execShellSu(cmd: String) = try { +fun execShellSu(cmd: String) = safeOfNothing { Shell.su(cmd).exec().out.let { if (it.isNotEmpty()) it[0].trim() else "" } -} catch (_: Throwable) { - "" +} + +/** + * 忽略异常返回值 + * @param it 回调 - 如果异常为空 + * @return [T] 发生异常时返回设定值否则返回正常值 + */ +inline fun safeOfNull(it: () -> T): T? = safeOf(null, it) + +/** + * 忽略异常返回值 + * @param it 回调 - 如果异常为 false + * @return [Boolean] 发生异常时返回设定值否则返回正常值 + */ +inline fun safeOfFalse(it: () -> Boolean) = safeOf(default = false, it) + +/** + * 忽略异常返回值 + * @param it 回调 - 如果异常为 true + * @return [Boolean] 发生异常时返回设定值否则返回正常值 + */ +inline fun safeOfTrue(it: () -> Boolean) = safeOf(default = true, it) + +/** + * 忽略异常返回值 + * @param it 回调 - 如果异常为 false + * @return [String] 发生异常时返回设定值否则返回正常值 + */ +inline fun safeOfNothing(it: () -> String) = safeOf(default = "", it) + +/** + * 忽略异常返回值 + * @param it 回调 - 如果异常为 false + * @return [Int] 发生异常时返回设定值否则返回正常值 + */ +inline fun safeOfNan(it: () -> Int) = safeOf(default = 0, it) + +/** + * 忽略异常返回值 + * @param default 异常返回值 + * @param it 正常回调值 + * @return [T] 发生异常时返回设定值否则返回正常值 + */ +inline fun safeOf(default: T, it: () -> T): T { + return try { + it() + } catch (t: NullPointerException) { + default + } catch (t: UnsatisfiedLinkError) { + default + } catch (t: UnsupportedOperationException) { + default + } catch (t: ClassNotFoundException) { + default + } catch (t: IllegalStateException) { + default + } catch (t: NoSuchMethodError) { + default + } catch (t: NoSuchFieldError) { + default + } catch (t: Error) { + default + } catch (t: Exception) { + default + } catch (t: Throwable) { + default + } +} + +/** + * 忽略异常运行 + * @param msg 出错输出的消息 - 默认为空 + * @param it 正常回调 + */ +inline fun safeRun(msg: String = "", it: () -> Unit) { + try { + it() + } catch (e: NullPointerException) { + if (msg.isNotBlank()) loggerE(msg = msg, e = e) + } catch (e: UnsatisfiedLinkError) { + if (msg.isNotBlank()) loggerE(msg = msg, e = e) + } catch (e: UnsupportedOperationException) { + if (msg.isNotBlank()) loggerE(msg = msg, e = e) + } catch (e: ClassNotFoundException) { + if (msg.isNotBlank()) loggerE(msg = msg, e = e) + } catch (e: IllegalStateException) { + if (msg.isNotBlank()) loggerE(msg = msg, e = e) + } catch (e: NoSuchMethodError) { + if (msg.isNotBlank()) loggerE(msg = msg, e = e) + } catch (e: NoSuchFieldError) { + if (msg.isNotBlank()) loggerE(msg = msg, e = e) + } catch (e: Error) { + if (msg.isNotBlank()) loggerE(msg = msg, e = e) + } catch (e: Exception) { + if (msg.isNotBlank()) loggerE(msg = msg, e = e) + } catch (e: Throwable) { + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a40650f..f3555fc 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -489,6 +489,20 @@ android:textColor="@color/colorTextGray" android:textSize="16sp" /> + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 185268c..888932e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,30 +1,12 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - ext.kotlin_version = "1.6.10" - repositories { - google() - maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } - maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' } - maven { url "https://www.jitpack.io" } - mavenCentral() - } - dependencies { - classpath "com.android.tools.build:gradle:7.0.4" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } +plugins { + id 'com.android.application' version '7.1.1' apply false + id 'com.android.library' version '7.1.1' apply false + id 'org.jetbrains.kotlin.android' version '1.6.10' apply false } -allprojects { - repositories { - google() - maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } - maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' } - maven { url "https://www.jitpack.io" } - mavenCentral() - } +ext { + appVersionName = "1.36" + appVersionCode = 8 } task clean(type: Delete) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dbe6c02..c44e0d1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jan 24 04:31:03 CST 2022 +#Tue Feb 15 02:09:05 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index 5ebc41d..ac7af30 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,18 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + maven { url "https://api.xposed.info/" } + maven { url 'https://www.jitpack.io' } + mavenCentral() + } +} rootProject.name = "MIUINativeNotifyIcon" include ':app'