This commit is contained in:
2022-02-15 05:14:06 +08:00
parent b8b23c3398
commit c865b39c97
19 changed files with 806 additions and 1235 deletions

2
.idea/misc.xml generated
View File

@@ -9,7 +9,7 @@
<entry key="app/src/main/res/drawable/permotion_round.xml" value="0.256" />
<entry key="app/src/main/res/drawable/white_round.xml" value="0.256" />
<entry key="app/src/main/res/layout/activity_config.xml" value="0.42168674698795183" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.4036346245815399" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.37516748548459133" />
<entry key="app/src/main/res/layout/adapter_config.xml" value="0.4375" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.44871794871794873" />
</map>

View File

@@ -103,4 +103,5 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)<br/><br/>
版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)

View File

@@ -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'
}

View File

@@ -13,20 +13,17 @@
android:theme="@style/Theme.MIUINativeNotifyIcon"
tools:ignore="AllowBackup">
<!-- 是否是xposed模块 -->
<meta-data
android:name="xposedmodule"
android:value="true" />
<!-- 模块描述 -->
<meta-data
android:name="xposeddescription"
android:value="MIUI 状态栏原生图标,修复 12.5、13 后期被破坏的彩色图标。\n开发者酷安 @星夜不荟" />
<!-- 最低xposed版本号 -->
<meta-data
android:name="xposedminversion"
android:value="82" />
android:value="93" />
<activity
android:name="com.fankes.miui.notify.ui.MainActivity"

View File

@@ -1 +1 @@
com.fankes.miui.notify.hook.HookMain
com.fankes.miui.notify.hook.HookEntry_YukiHookXposedInit

View File

@@ -18,30 +18,19 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* 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<String, Boolean>()
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"
}

View File

@@ -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
* <https://www.gnu.org/licenses/>
*
* 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<Boolean>(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 = "<unknown>") {
ExpandedNotificationClass.clazz.obtainMethod(name = "getAppName")?.invokeAny(instance) ?: "<empty>"
}
/**
* 判断通知是否来自 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<Context?>()
}
/**
* 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<ImageView?>() ?: return@afterHook
/** 获取通知实例 */
val expandedNf = thisClass.obtainFieldAny<StatusBarNotification?>(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<ImageView?>(any = instance, name = "mIcon") ?: return@afterHook
/** 从父类中得到 mRow 变量 - [ExpandableNotificationRowClass] */
NotificationViewWrapperClass.clazz.obtainFieldAny<Any>(instance, name = "mRow").apply {
/** 获取其中的得到通知方法 */
val expandedNf =
ExpandableNotificationRowClass.clazz.obtainMethod(name = "getStatusBarNotification")
?.invokeAny<StatusBarNotification?>(any = this)
/** 执行 Hook */
hookNotifyIconOnSet(iconImageView.context, expandedNf, iconImageView)
}
}
}
}
}
}
}
}
}

View File

@@ -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
* <https://www.gnu.org/licenses/>
*
* 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<String, String>,
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<String, String>) = 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<String, String>) = 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 ?: "<empty>"
}
} catch (e: Throwable) {
"<unknown>"
}
/**
* 判断通知是否来自 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!")
}
}
}
}
}

View File

@@ -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
* <https://www.gnu.org/licenses/>
*
* 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<File>(
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()
}
}
}

View File

@@ -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
* <https://www.gnu.org/licenses/>
*
* 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)

View File

@@ -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<View>(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()
}
}

View File

@@ -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<View>(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<View>(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<View>(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()
}

View File

@@ -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()
}
}

View File

@@ -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
* <https://www.gnu.org/licenses/>
*
* 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);
}
}

View File

@@ -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 <T> 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 <T> 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) {
}
}

View File

@@ -489,6 +489,20 @@
android:textColor="@color/colorTextGray"
android:textSize="16sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="10dp"
android:autoLink="web"
android:background="@drawable/permotion_round"
android:lineSpacingExtra="6dp"
android:padding="10dp"
android:text="此模块使用 YukiHookAPI 构建。\n点击这里了解更多 https://github.com/fankes/YukiHookAPI"
android:textColor="@color/colorTextGray"
android:textSize="12sp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@@ -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) {

View File

@@ -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

View File

@@ -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'