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 index bd83d81..8aca107 100644 --- a/app/src/main/java/com/fankes/miui/notify/hook/HookEntry.kt +++ b/app/src/main/java/com/fankes/miui/notify/hook/HookEntry.kt @@ -39,7 +39,11 @@ import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit object HookEntry : IYukiHookXposedInit { override fun onInit() = configs { - debugLog { tag = "MIUINativeNotifyIcon" } + debugLog { + tag = "MIUINativeNotifyIcon" + isRecord = true + elements(PRIORITY) + } isDebug = false } diff --git a/app/src/main/java/com/fankes/miui/notify/hook/entity/SystemUIHooker.kt b/app/src/main/java/com/fankes/miui/notify/hook/entity/SystemUIHooker.kt index 53f892d..0ad577c 100644 --- a/app/src/main/java/com/fankes/miui/notify/hook/entity/SystemUIHooker.kt +++ b/app/src/main/java/com/fankes/miui/notify/hook/entity/SystemUIHooker.kt @@ -231,22 +231,22 @@ object SystemUIHooker : YukiBaseHooker() { * 打印日志 * @param tag 标识 * @param context 实例 - * @param expandedNf 通知实例 + * @param nf 通知实例 * @param isCustom 是否为通知优化生效图标 * @param isGrayscale 是否为灰度图标 */ - private fun printLogcat( - tag: String, - context: Context, - expandedNf: StatusBarNotification?, - isCustom: Boolean, - isGrayscale: Boolean - ) { + private fun loggerDebug(tag: String, context: Context, nf: StatusBarNotification?, isCustom: Boolean, isGrayscale: Boolean) { if (ConfigData.isEnableModuleLog) loggerD( - msg = "$tag --> [${context.appNameOf(packageName = expandedNf?.nfPkgName ?: "")}][${expandedNf?.nfPkgName}] " + - "custom [$isCustom] " + - "grayscale [$isGrayscale] " + - "xmsf [${expandedNf?.isXmsf}]" + msg = "(Processing $tag) ↓\n" + + "[Title]: ${nf?.notification?.extras?.getString(Notification.EXTRA_TITLE)}\n" + + "[Content]: ${nf?.notification?.extras?.getString(Notification.EXTRA_TEXT)}\n" + + "[App Name]: ${context.appNameOf(packageName = nf?.packageName ?: "")}\n" + + "[Package Name]: ${nf?.packageName}\n" + + "[Sender Package Name]: ${nf?.compatOpPkgName}\n" + + "[Custom Icon]: $isCustom\n" + + "[Grayscale Icon]: $isGrayscale\n" + + "[From Xmsf]: ${nf?.isXmsf}\n" + + "[String]: ${nf?.notification}" ) } @@ -372,53 +372,55 @@ object SystemUIHooker : YukiBaseHooker() { * * 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook * @param context 实例 - * @param expandedNf 通知实例 + * @param nf 通知实例 * @param iconDrawable 小图标 [Drawable] * @return [Pair] 回调小图标 - ([Drawable] 小图标,[Boolean] 是否替换) */ - private fun compatStatusIcon(context: Context, expandedNf: StatusBarNotification?, iconDrawable: Drawable?) = - expandedNf?.let { notifyInstance -> - if (iconDrawable == null) return@let Pair(null, false) - /** 判断是否不是灰度图标 */ - val isGrayscaleIcon = notifyInstance.isXmsf.not() && isGrayscaleIcon(context, iconDrawable, notifyInstance) + private fun compatStatusIcon(context: Context, nf: StatusBarNotification?, iconDrawable: Drawable?) = nf?.let { notifyInstance -> + if (iconDrawable == null) return@let Pair(null, false) + /** 判断是否不是灰度图标 */ + val isGrayscaleIcon = notifyInstance.isXmsf.not() && isGrayscaleIcon(context, iconDrawable, notifyInstance) - /** 目标彩色通知 APP 图标 */ - val customIcon = compatCustomIcon(context, isGrayscaleIcon, notifyInstance.nfPkgName).first - /** 打印日志 */ - printLogcat(tag = "StatusIcon", context, notifyInstance, isCustom = customIcon != null, isGrayscaleIcon) - when { - /** 处理自定义通知图标优化 */ - customIcon != null -> Pair(customIcon, true) - /** 若不是灰度图标自动处理为圆角 */ - isGrayscaleIcon.not() -> Pair(notifyInstance.compatPushingIcon(context, iconDrawable).rounded(context), true) - /** 否则返回原始小图标 */ - else -> Pair(notifyInstance.notification.smallIcon.loadDrawable(context), false) - } - } ?: Pair(null, false) + /** 目标彩色通知 APP 图标 */ + val customTriple = compatCustomIcon(context, isGrayscaleIcon, notifyInstance.nfPkgName) + + /** 是否为通知优化生效图标 */ + val isCustom = customTriple.first != null && customTriple.third.not() + /** 打印日志 */ + loggerDebug(tag = "Status Bar Icon", context, notifyInstance, isCustom = isCustom, isGrayscaleIcon) + when { + /** 处理自定义通知图标优化 */ + customTriple.first != null -> Pair(customTriple.first, true) + /** 若不是灰度图标自动处理为圆角 */ + isGrayscaleIcon.not() -> Pair(notifyInstance.compatPushingIcon(context, iconDrawable).rounded(context), true) + /** 否则返回原始小图标 */ + else -> Pair(notifyInstance.notification.smallIcon.loadDrawable(context), false) + } + } ?: Pair(null, false) /** * Hook 通知栏小图标 * * 区分系统版本 - 由于每个系统版本的方法不一样这里单独拿出来进行 Hook * @param context 实例 - * @param expandedNf 通知实例 - * @param iconImageView 通知图标实例 + * @param nf 通知实例 + * @param iconView 通知图标实例 * @param isExpanded 通知是否展开 - 可做最小化通知处理 - 默认:是 * @param isUseMaterial3Style 是否使用 Material 3 通知图标风格 - 默认跟随系统版本决定 */ private fun compatNotifyIcon( context: Context, - expandedNf: StatusBarNotification?, - iconImageView: ImageView, + nf: StatusBarNotification?, + iconView: ImageView, isExpanded: Boolean = true, - isUseMaterial3Style: Boolean = isUpperOfAndroidS, + isUseMaterial3Style: Boolean = isUpperOfAndroidS ) = runInSafe(msg = "compatNotifyIcon") { /** * 设置默认通知图标 * @param drawable 通知图标 */ fun setDefaultNotifyIcon(drawable: Drawable?) { - iconImageView.apply { + iconView.apply { /** 重新设置图标 */ setImageDrawable(drawable) /** 设置裁切到边界 */ @@ -443,7 +445,7 @@ object SystemUIHooker : YukiBaseHooker() { } } /** 获取通知对象 - 由于 MIUI 的版本迭代不规范性可能是空的 */ - expandedNf?.let { notifyInstance -> + nf?.let { notifyInstance -> /** 新版风格反色 */ val newStyle = if (context.isSystemInDarkMode) 0xFF2D2D2D.toInt() else Color.WHITE @@ -486,7 +488,7 @@ object SystemUIHooker : YukiBaseHooker() { (it.second.takeIf { e -> e != 0 } ?: (if (isUseMaterial3Style) context.systemAccentColor else 0)) else 0 } /** 打印日志 */ - printLogcat(tag = "NotifyIcon", context, notifyInstance, isCustom = customIcon != null, isGrayscaleIcon) + loggerDebug(tag = "Notification Panel Icon", context, notifyInstance, isCustom = customIcon != null, isGrayscaleIcon) /** 处理自定义通知图标优化 */ when { ConfigData.isEnableNotifyIconForceAppIcon -> { @@ -494,7 +496,7 @@ object SystemUIHooker : YukiBaseHooker() { val miuiAppIcon = notifyInstance.notification?.extras?.getParcelable("miui.appIcon") setDefaultNotifyIcon(drawable = miuiAppIcon?.loadDrawable(context) ?: context.appIconOf(notifyInstance.nfPkgName)) } - customIcon != null -> iconImageView.apply { + customIcon != null -> iconView.apply { /** 设置不要裁切到边界 */ clipToOutline = false /** 设置自定义小图标 */ @@ -513,9 +515,9 @@ object SystemUIHooker : YukiBaseHooker() { } else -> { /** 重新设置图标 - 防止系统更改它 */ - iconImageView.setImageDrawable(iconDrawable) + iconView.setImageDrawable(iconDrawable) /** 判断如果是灰度图标就给他设置一个白色颜色遮罩 */ - if (isGrayscaleIcon) iconImageView.apply { + if (isGrayscaleIcon) iconView.apply { /** 设置不要裁切到边界 */ clipToOutline = false /** 设置图标着色 */ @@ -559,7 +561,6 @@ object SystemUIHooker : YukiBaseHooker() { * 只要不是灰度就返回彩色图标 * 否则不对颜色进行反色处理防止一些系统图标出现异常 */ - printLogcat(tag = "IconColor", context, notifyInstance, isTargetFixApp, isGrayscaleIcon) return isTargetFixApp || isGrayscaleIcon } @@ -930,8 +931,8 @@ object SystemUIHooker : YukiBaseHooker() { field { name = "mAppIcon" }.get(instance).cast()?.clone { compatNotifyIcon( context = context, - expandedNf = instance.getRowPair().second.getSbn(), - iconImageView = this, + nf = instance.getRowPair().second.getSbn(), + iconView = this, isUseMaterial3Style = true ) } @@ -950,7 +951,7 @@ object SystemUIHooker : YukiBaseHooker() { field { name = "mAppIcon" }.get(instance).cast()?.apply { compatNotifyIcon(context, NotificationChildrenContainerClass.toClassOrNull()?.field { name = "mContainingNotification" - }?.get(instance)?.any()?.getSbn(), iconImageView = this, isUseMaterial3Style = true) + }?.get(instance)?.any()?.getSbn(), iconView = this, isUseMaterial3Style = true) } } } diff --git a/app/src/main/java/com/fankes/miui/notify/ui/activity/MainActivity.kt b/app/src/main/java/com/fankes/miui/notify/ui/activity/MainActivity.kt index f0ced49..c3412ca 100644 --- a/app/src/main/java/com/fankes/miui/notify/ui/activity/MainActivity.kt +++ b/app/src/main/java/com/fankes/miui/notify/ui/activity/MainActivity.kt @@ -164,6 +164,7 @@ class MainActivity : BaseActivity() { binding.moduleEnableSwitch.bind(ConfigData.ENABLE_MODULE) { onInitialize { binding.moduleEnableLogSwitch.isVisible = it + binding.expAllDebugLogButton.isVisible = it && ConfigData.isEnableModuleLog binding.colorIconHookItem.isVisible = it binding.statusIconCountItem.isVisible = isLowerAndroidR.not() && it binding.notifyStyleConfigItem.isVisible = it @@ -176,7 +177,11 @@ class MainActivity : BaseActivity() { } } binding.moduleEnableLogSwitch.bind(ConfigData.ENABLE_MODULE_LOG) { - onChanged { SystemUITool.showNeedRestartSnake(context = this@MainActivity) } + onInitialize { binding.expAllDebugLogButton.isVisible = it && ConfigData.isEnableModule } + onChanged { + reinitialize() + SystemUITool.refreshSystemUI(context = this@MainActivity, isRefreshCacheOnly = true) + } } binding.statusIconCountSwitch.bind(ConfigData.ENABLE_LIFTED_STATUS_ICON_COUNT) { onInitialize { binding.statusIconCountChildItem.isVisible = it } @@ -275,6 +280,8 @@ class MainActivity : BaseActivity() { binding.notifyIconCustomCornerSeekbar.bind(ConfigData.NOTIFY_ICON_CORNER_SIZE, binding.notifyIconCustomCornerText, suffix = " dp") { SystemUITool.refreshSystemUI(context = this) } + /** 导出全部日志按钮点击事件 */ + binding.expAllDebugLogButton.setOnClickListener { SystemUITool.obtainAndExportDebugLogs(context = this) } /** MIUI 通知显示设置按钮点击事件 */ binding.miuiNotifyStyleButton.setOnClickListener { SystemUITool.openMiuiNotificationDisplaySettings(context = this) } /** 通知图标优化名单按钮点击事件 */ @@ -339,6 +346,8 @@ class MainActivity : BaseActivity() { if (btn.isPressed.not()) return@setOnCheckedChangeListener hideOrShowLauncherIcon(b) } + /** 注册导出调试日志启动器 */ + SystemUITool.registerExportDebugLogsLauncher(activity = this) } /** 前往项目地址 */ diff --git a/app/src/main/java/com/fankes/miui/notify/utils/tool/SystemUITool.kt b/app/src/main/java/com/fankes/miui/notify/utils/tool/SystemUITool.kt index e8d505b..124bd94 100644 --- a/app/src/main/java/com/fankes/miui/notify/utils/tool/SystemUITool.kt +++ b/app/src/main/java/com/fankes/miui/notify/utils/tool/SystemUITool.kt @@ -25,19 +25,22 @@ package com.fankes.miui.notify.utils.tool import android.content.ComponentName import android.content.Context import android.content.Intent +import android.os.Build +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import com.fankes.miui.notify.const.PackageName import com.fankes.miui.notify.data.ConfigData import com.fankes.miui.notify.ui.activity.MainActivity -import com.fankes.miui.notify.utils.factory.delayedRun -import com.fankes.miui.notify.utils.factory.execShell -import com.fankes.miui.notify.utils.factory.showDialog -import com.fankes.miui.notify.utils.factory.snake +import com.fankes.miui.notify.utils.factory.* import com.google.android.material.snackbar.Snackbar import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.hook.factory.dataChannel +import com.highcapable.yukihookapi.hook.log.YukiHookLogger +import com.highcapable.yukihookapi.hook.log.YukiLoggerData import com.highcapable.yukihookapi.hook.param.PackageParam import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData +import java.util.* /** * 系统界面工具 @@ -47,6 +50,12 @@ object SystemUITool { private val CALL_HOST_REFRESH_CACHING = ChannelData("call_host_refresh_caching", false) private val CALL_MODULE_REFRESH_RESULT = ChannelData("call_module_refresh_result", false) + /** 当前全部调试日志 */ + private var debugLogs = ArrayList() + + /** 当前启动器实例 */ + private var launcher: ActivityResultLauncher? = null + /** * 宿主注册监听 */ @@ -70,6 +79,61 @@ object SystemUITool { fun checkingActivated(context: Context, result: (Boolean) -> Unit) = context.dataChannel(PackageName.SYSTEMUI).checkingVersionEquals(result = result) + /** + * 注册导出调试日志启动器到 [AppCompatActivity] + * @param activity 实例 + */ + fun registerExportDebugLogsLauncher(activity: AppCompatActivity) { + launcher = activity.registerForActivityResult(ActivityResultContracts.CreateDocument("*/application")) { result -> + runCatching { + result?.let { e -> + val content = "" + + "================================================================\n" + + " Generated by MIUINativeNotifyIcon\n" + + " Project Url: https://github.com/fankes/MIUINativeNotifyIcon\n" + + "================================================================\n\n" + + "[Device Brand]: ${Build.BRAND}\n" + + "[Device Model]: ${Build.MODEL}\n" + + "[Display]: ${Build.DISPLAY}\n" + + "[Android Version]: ${Build.VERSION.RELEASE}\n" + + "[Android API Level]: ${Build.VERSION.SDK_INT}\n" + + "[MIUI Version]: $miuiFullVersion\n" + + "[System Locale]: ${Locale.getDefault()}\n\n" + YukiHookLogger.contents(debugLogs).trim() + activity.contentResolver?.openOutputStream(e)?.apply { write(content.toByteArray()) }?.close() + activity.snake(msg = "导出完成") + } ?: activity.snake(msg = "已取消操作") + }.onFailure { activity.snake(msg = "导出过程发生错误") } + } + } + + /** + * 获取并导出全部调试日志 + * @param context 实例 + */ + fun obtainAndExportDebugLogs(context: Context) { + /** 执行导出操作 */ + fun doExport() { + context.dataChannel(PackageName.SYSTEMUI).obtainLoggerInMemoryData { + if (it.isNotEmpty()) { + debugLogs = it + runCatching { launcher?.launch("miui_notification_icons_processing_logs.log") } + .onFailure { context.snake(msg = "启动系统文件选择器失败") } + } else context.snake(msg = "暂无调试日志") + } + } + if (YukiHookAPI.Status.isXposedModuleActive) + context.showDialog { + title = "导出全部调试日志" + msg = "调试日志中会包含当前系统推送的全部通知内容,其中可能包含你的个人隐私," + + "你可以在导出后的日志文件中选择将这些敏感信息模糊化处理再进行共享," + + "开发者使用并查看你导出的调试日志仅为排查与修复问题,并且在之后会及时销毁这些日志。\n\n" + + "继续导出即代表你已阅读并知悉上述内容。" + confirmButton(text = "继续") { doExport() } + cancelButton() + } + else context.snake(msg = "模块没有激活,请先激活模块") + } + /** 当 Root 权限获取失败时显示对话框 */ private fun Context.showWhenAccessRootFail() = showDialog { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2016f99..c0d8e09 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -211,13 +211,13 @@ android:elevation="0dp" android:gravity="center" android:orientation="vertical" - android:paddingLeft="15dp" - android:paddingTop="15dp" - android:paddingRight="15dp"> + android:paddingTop="15dp"> @@ -259,14 +261,33 @@ android:id="@+id/module_enable_log_switch" android:layout_width="match_parent" android:layout_height="35dp" + android:layout_marginLeft="15dp" + android:layout_marginRight="15dp" android:layout_marginBottom="5dp" android:text="启用调试日志" android:textColor="@color/colorTextGray" android:textSize="15sp" /> + +