From 183531f63cf832b470eb89b4c9fee7bb8ea6b911 Mon Sep 17 00:00:00 2001 From: notXX Date: Wed, 29 Jan 2020 14:22:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、让recastNotification能正常工作,不能正常工作的原因很傻逼; 2、删掉SystemUI部分的代码。 --- .../decorators/wechat/WeChatDecorator.java | 687 +++++++----------- .../nevo/sdk/NevoDecoratorService.java | 225 ++---- .../com/oasisfeng/nevo/xposed/MainHook.java | 128 +--- 3 files changed, 297 insertions(+), 743 deletions(-) diff --git a/src/main/java/com/oasisfeng/nevo/decorators/wechat/WeChatDecorator.java b/src/main/java/com/oasisfeng/nevo/decorators/wechat/WeChatDecorator.java index f49e27d..2b39949 100644 --- a/src/main/java/com/oasisfeng/nevo/decorators/wechat/WeChatDecorator.java +++ b/src/main/java/com/oasisfeng/nevo/decorators/wechat/WeChatDecorator.java @@ -83,483 +83,288 @@ * */ @Decorator(title = R.string.decorator_wechat_title, description = R.string.decorator_wechat_description, priority = -20) -public class WeChatDecorator extends NevoDecoratorService { - public static class WeChatLocalDecorator extends NevoDecoratorService.LocalDecorator implements HookSupport { - - private static long now() { return System.currentTimeMillis(); } +public class WeChatDecorator extends NevoDecoratorService implements HookSupport { - private final WeChatDecorator decorator; + public static final String WECHAT_PACKAGE = "com.tencent.mm"; + private static final long GROUP_CHAT_SORT_KEY_SHIFT = 24 * 60 * 60 * 1000L; // Sort group chat like one day older message. + private static final String CHANNEL_MESSAGE = "message_channel_new_id"; // Channel ID used by WeChat for all message notifications + private static final String OLD_CHANNEL_MESSAGE = "message"; // old name for migration + private static final String CHANNEL_MISC = "reminder_channel_id"; // Channel ID used by WeChat for misc. notifications + private static final String OLD_CHANNEL_MISC = "misc"; // old name for migration + private static final String CHANNEL_DND = "message_dnd_mode_channel_id"; // Channel ID used by WeChat for its own DND mode + private static final String CHANNEL_GROUP_CONVERSATION = "group"; // WeChat has no separate group for group conversation + private static final String RECALL_PATTERN = "(\"(?[^\"]+)\" )?撤回了?一条消息"; + private static final Pattern pattern = Pattern.compile(RECALL_PATTERN); // [2条]"🦉 " 撤回了一条消息 / [2条]撤回一条消息 - private String mPath; - private long mCreated, mClosed; - - public WeChatLocalDecorator(String prefKey, WeChatDecorator decorator) { - super(prefKey); - this.decorator = decorator; - } + private static final @ColorInt int PRIMARY_COLOR = 0xFF33B332; + private static final @ColorInt int LIGHT_COLOR = 0xFF00FF00; + static final String ACTION_SETTINGS_CHANGED = "SETTINGS_CHANGED"; + static final String ACTION_DEBUG_NOTIFICATION = "DEBUG"; + private static final String KEY_SILENT_REVIVAL = "nevo.wechat.revival"; + private static final String EXTRA_RECALL = "nevo.wechat.recall"; + private static final String EXTRA_RECALLER = "nevo.wechat.recaller"; + public static final String EXTRA_PICTURE_PATH = "nevo.wechat.picturePath"; + private static final String EXTRA_PICTURE = "nevo.wechat.picture"; + private static final String STORAGE_PREFIX = "/storage/emulated/0/"; - /** - * - * Created by Oasis on 2018-11-30. - * Modify by Kr328 on 2019-1-5 - * Modify by notXX on 2019-8-5 - * - * @param loadPackageParam - */ - @Override public void hook(XC_LoadPackage.LoadPackageParam loadPackageParam) { - Class clazz = java.io.FileOutputStream.class; - XposedHelpers.findAndHookConstructor(clazz, String.class, boolean.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - String path = (String)param.args[0]; - if (path == null || !path.contains("/image2/")) return; - long created = now(); - XposedHelpers.setAdditionalInstanceField(param.thisObject, "path", path); - XposedHelpers.setAdditionalInstanceField(param.thisObject, "created", created); - if (BuildConfig.DEBUG) Log.d(TAG, created + " " + path); - } - }); - XposedHelpers.findAndHookMethod(clazz, "close", new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) { - String path = (String)XposedHelpers.getAdditionalInstanceField(param.thisObject, "path"); - if (path == null) return; - long created = (Long)XposedHelpers.getAdditionalInstanceField(param.thisObject, "created"); - long closed = now(); - if (BuildConfig.DEBUG) Log.d(TAG, created + "=>" + closed + " " + path); - synchronized (this) { - mPath = path; - mCreated = created; - mClosed = closed; - } + static final String TAG = "WeChatDecorator"; + + public static interface ModifyNotification { void modify(Notification n); } + + private static long now() { return System.currentTimeMillis(); } + + private String mPath; + private long mCreated, mClosed; + + /** + * + * Created by Oasis on 2018-11-30. + * Modify by Kr328 on 2019-1-5 + * Modify by notXX on 2019-8-5 + * + * @param loadPackageParam + */ + @Override public void hook(XC_LoadPackage.LoadPackageParam loadPackageParam) { + Class clazz = java.io.FileOutputStream.class; + XposedHelpers.findAndHookConstructor(clazz, String.class, boolean.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) { + String path = (String)param.args[0]; + if (path == null || !path.contains("/image2/")) return; + long created = now(); + XposedHelpers.setAdditionalInstanceField(param.thisObject, "path", path); + XposedHelpers.setAdditionalInstanceField(param.thisObject, "created", created); + if (BuildConfig.DEBUG) Log.d(TAG, created + " " + path); + } + }); + XposedHelpers.findAndHookMethod(clazz, "close", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) { + String path = (String)XposedHelpers.getAdditionalInstanceField(param.thisObject, "path"); + if (path == null) return; + long created = (Long)XposedHelpers.getAdditionalInstanceField(param.thisObject, "created"); + long closed = now(); + if (BuildConfig.DEBUG) Log.d(TAG, created + "=>" + closed + " " + path); + synchronized (this) { + mPath = path; + mCreated = created; + mClosed = closed; } - }); + } + }); + } + + private MessagingBuilder mMessagingBuilder; + private String channelGroupMessage, channelMessage, channelMisc; + private boolean mWeChatTargetingO; + private final ConversationManager mConversationManager = new ConversationManager(); + + @Override public void onCreate(SharedPreferences pref) { + super.onCreate(pref); + + mMessagingBuilder = new MessagingBuilder(getAppContext(), getPackageContext(), this::modifyNotification); // Must be called after loadPreferences(). + channelGroupMessage = getString(R.string.channel_group_message); + channelMessage = getString(R.string.channel_message); + channelMisc = getString(R.string.channel_misc); + } + + @Override public Decorating apply(NotificationManager nm, String tag, int id, Notification n) { + mWeChatTargetingO = isWeChatTargeting26OrAbove(); + Log.d(TAG, "apply tag " + tag + " id " + id); + cache(id, n); + + final Bundle extras = n.extras; + CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE); + if (title == null || title.length() == 0) { + Log.e(TAG, "Title is missing: " + n); + return Decorating.Unprocessed; } - - private boolean mWeChatTargetingO; - private MessagingBuilder mMessagingBuilder; - private final ConversationManager mConversationManager = new ConversationManager(); - private String channelGroupMessage, channelMessage, channelMisc; - - @Override public void onCreate(SharedPreferences pref) { - super.onCreate(pref); - - mMessagingBuilder = new MessagingBuilder(getAppContext(), getPackageContext(), decorator::modifyNotification); // Must be called after loadPreferences(). - channelGroupMessage = getString(R.string.channel_group_message); - channelMessage = getString(R.string.channel_message); - channelMisc = getString(R.string.channel_misc); + if (title != (title = EmojiTranslator.translate(title))) extras.putCharSequence(Notification.EXTRA_TITLE, title); + n.color = PRIMARY_COLOR; // Tint the small icon + + String channel_id = SDK_INT >= O ? n.getChannelId() : null; + if (CHANNEL_MISC.equals(channel_id)) return Decorating.Unprocessed; // Misc. notifications on Android 8+. + + final CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT); + String content = text != null ? text.toString() : null; + // [2条]... + if (content != null && content.startsWith("[")) { + if (BuildConfig.DEBUG) Log.d(TAG, "content " + content); + final int end = content.indexOf(']'); + if (content.charAt(end - 1) == '条') { + n.number = Integer.parseInt(content.substring(1, end - 1)); + if (BuildConfig.DEBUG) Log.d(TAG, "n.number " + n.number); + content = content.substring(end + 1); + } } - - @Override public Decorating apply(NotificationManager nm, String tag, int id, Notification n) { - mWeChatTargetingO = isWeChatTargeting26OrAbove(); - Log.d(TAG, "apply tag " + tag + " id " + id); - cache0(id, n); - - final Bundle extras = n.extras; - CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE); - if (title == null || title.length() == 0) { - Log.e(TAG, "Title is missing: " + n); + // 撤回... + int type = Conversation.TYPE_UNKNOWN; + String recaller = null; + boolean is_recall = false; + if (content != null && content.contains("撤回")) { + if (BuildConfig.DEBUG) Log.d(TAG, "content " + content); + if (CHANNEL_MISC.equals(channel_id)) { // Misc. notifications on Android 8+. return Decorating.Unprocessed; - } - if (title != (title = EmojiTranslator.translate(title))) extras.putCharSequence(Notification.EXTRA_TITLE, title); - n.color = PRIMARY_COLOR; // Tint the small icon - - String channel_id = SDK_INT >= O ? n.getChannelId() : null; - if (CHANNEL_MISC.equals(channel_id)) return Decorating.Unprocessed; // Misc. notifications on Android 8+. - - final CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT); - String content = text != null ? text.toString() : null; - // [2条]... - if (content != null && content.startsWith("[")) { - if (BuildConfig.DEBUG) Log.d(TAG, "content " + content); - final int end = content.indexOf(']'); - if (content.charAt(end - 1) == '条') { - n.number = Integer.parseInt(content.substring(1, end - 1)); - if (BuildConfig.DEBUG) Log.d(TAG, "n.number " + n.number); - content = content.substring(end + 1); - } - } - // 撤回... - int type = Conversation.TYPE_UNKNOWN; - String recaller = null; - boolean is_recall = false; - if (content != null && content.contains("撤回")) { - if (BuildConfig.DEBUG) Log.d(TAG, "content " + content); - if (CHANNEL_MISC.equals(channel_id)) { // Misc. notifications on Android 8+. + } else if (n.tickerText == null) { // Legacy misc. notifications. + if (SDK_INT >= O && channel_id == null) setChannelId(n, CHANNEL_MISC); + Matcher matcher = pattern.matcher(content); + if (BuildConfig.DEBUG) Log.d(TAG, "matcher " + matcher.matches()); + if (matcher.matches()) { + // 撤回 + // Log.d(TAG, matcher.group(0) + ", " + matcher.group(1) + ", " + matcher.group(2) + ", " + matcher.group(3)); + is_recall = true; + recaller = matcher.group("recaller"); + extras.putBoolean(EXTRA_RECALL, true); + extras.putString(EXTRA_RECALLER, recaller); + if (BuildConfig.DEBUG) Log.d(TAG, "recaller " + recaller); + } else { + Log.d(TAG, "Skip further process for non-conversation notification: " + title); // E.g. web login confirmation notification. return Decorating.Unprocessed; - } else if (n.tickerText == null) { // Legacy misc. notifications. - if (SDK_INT >= O && channel_id == null) setChannelId(n, CHANNEL_MISC); - Matcher matcher = pattern.matcher(content); - if (BuildConfig.DEBUG) Log.d(TAG, "matcher " + matcher.matches()); - if (matcher.matches()) { - // 撤回 - // Log.d(TAG, matcher.group(0) + ", " + matcher.group(1) + ", " + matcher.group(2) + ", " + matcher.group(3)); - is_recall = true; - recaller = matcher.group("recaller"); - extras.putBoolean(EXTRA_RECALL, true); - extras.putString(EXTRA_RECALLER, recaller); - if (BuildConfig.DEBUG) Log.d(TAG, "recaller " + recaller); - } else { - Log.d(TAG, "Skip further process for non-conversation notification: " + title); // E.g. web login confirmation notification. - return Decorating.Unprocessed; - } - } - if (is_recall) type = (recaller == null) ? Conversation.TYPE_DM_RECALL : Conversation.TYPE_GC_RECALL; - } - if (content == null || content.isEmpty()) return Decorating.Unprocessed; - - extras.putCharSequence(Notification.EXTRA_TEXT, content); - - int sep = content.indexOf(WeChatMessage.SENDER_MESSAGE_SEPARATOR); - if (sep > 0) { - String person = content.substring(0, sep); - String msg = content.substring(sep + WeChatMessage.SENDER_MESSAGE_SEPARATOR.length()); - if (BuildConfig.DEBUG) Log.d(TAG, person + "|" + msg); - if ("[图片]".equals(msg) && mPath != null && mClosed - now() < 1000) { - synchronized (this) { - if (BuildConfig.DEBUG) Log.d(TAG, "putString " + mPath); - extras.putString(EXTRA_PICTURE_PATH, mPath); // 保存图片地址 - mPath = null; - } - } - } - - if (type == Conversation.TYPE_UNKNOWN) type = WeChatMessage.guessConversationType(content, n.tickerText.toString().trim(), title); - final boolean is_group_chat = Conversation.isGroupChat(type); - if (SDK_INT >= O) { - if (extras.containsKey(KEY_SILENT_REVIVAL)) { - setGroup(n, "nevo.group.auto"); // Special group name to let Nevolution auto-group it as if not yet grouped. (To be standardized in SDK) - setGroupAlertBehavior(n, Notification.GROUP_ALERT_SUMMARY); // This trick makes notification silent } - if (is_group_chat && ! CHANNEL_DND.equals(channel_id)) setChannelId(n, CHANNEL_GROUP_CONVERSATION); - else if (channel_id == null) setChannelId(n, CHANNEL_MESSAGE); // WeChat versions targeting O+ have its own channel for message } - - // WeChat previously uses dynamic counter starting from 4097 as notification ID, which is reused after cancelled by WeChat itself, - // causing conversation duplicate or overwritten notifications. - final Conversation conversation = mConversationManager.getConversation(id); - - conversation.setTitle(title); - conversation.summary = content; - conversation.ticker = n.tickerText; - conversation.timestamp = n.when; - if (is_recall) - conversation.setType((recaller == null) ? Conversation.TYPE_DM_RECALL : Conversation.TYPE_GC_RECALL); - else if (conversation.getType() == Conversation.TYPE_UNKNOWN) - conversation.setType(WeChatMessage.guessConversationType(conversation)); - - extras.putBoolean(Notification.EXTRA_SHOW_WHEN, true); - // if (mPreferences.getBoolean(mPrefKeyWear, false)) n.flags &= ~ Notification.FLAG_LOCAL_ONLY; // TODO - setSortKey(n, String.valueOf(Long.MAX_VALUE - n.when + (is_group_chat ? GROUP_CHAT_SORT_KEY_SHIFT : 0))); // Place group chat below other messages - - MessagingStyle messaging = mMessagingBuilder.buildFromExtender(conversation, id, n, title, getArchivedNotifications0(id)); // build message from android auto - if (messaging == null) // EXTRA_TEXT will be written in buildFromArchive() - messaging = mMessagingBuilder.buildFromArchive(conversation, n, title, getArchivedNotifications0(id)); - if (messaging == null) return Decorating.Unprocessed; - final List messages = messaging.getMessages(); - if (messages.isEmpty()) return Decorating.Unprocessed; - - if (is_group_chat) messaging.setGroupConversation(true).setConversationTitle(title); - MessagingBuilder.flatIntoExtras(messaging, extras); - extras.putString(Notification.EXTRA_TEMPLATE, TEMPLATE_MESSAGING); - - if (SDK_INT >= N && extras.getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY) != null) - n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; // No more alert for direct-replied notification. - - // 维护NotificationChannel - channel_id = n.getChannelId(); - if (channel_id != null) { // 确保NotificationChannel存在 - NotificationChannel channel = nm.getNotificationChannel(channel_id); - if (BuildConfig.DEBUG) Log.d(TAG, channel_id + " " + channel); - if (channel != null) return Decorating.Processed; - switch (channel_id) { - case CHANNEL_GROUP_CONVERSATION: - channel = makeChannel(CHANNEL_GROUP_CONVERSATION, channelGroupMessage, false); - break; - case CHANNEL_MESSAGE: - channel = migrate(nm, OLD_CHANNEL_MESSAGE, CHANNEL_MESSAGE, channelMessage, false); - break; - case CHANNEL_MISC: - channel = migrate(nm, OLD_CHANNEL_MISC, CHANNEL_MISC, channelMisc, true); - break; + if (is_recall) type = (recaller == null) ? Conversation.TYPE_DM_RECALL : Conversation.TYPE_GC_RECALL; + } + if (content == null || content.isEmpty()) return Decorating.Unprocessed; + + extras.putCharSequence(Notification.EXTRA_TEXT, content); + + int sep = content.indexOf(WeChatMessage.SENDER_MESSAGE_SEPARATOR); + if (sep > 0) { + String person = content.substring(0, sep); + String msg = content.substring(sep + WeChatMessage.SENDER_MESSAGE_SEPARATOR.length()); + if (BuildConfig.DEBUG) Log.d(TAG, person + "|" + msg); + if ("[图片]".equals(msg) && mPath != null && mClosed - now() < 1000) { + synchronized (this) { + if (BuildConfig.DEBUG) Log.d(TAG, "putString " + mPath); + extras.putString(EXTRA_PICTURE_PATH, mPath); // 保存图片地址 + mPath = null; } - if (BuildConfig.DEBUG) Log.d(TAG, channel_id + " " + channel); - nm.createNotificationChannel(channel); - channel = nm.getNotificationChannel(channel_id); - if (BuildConfig.DEBUG) Log.d(TAG, channel_id + " " + channel); } - - return Decorating.Processed; } - private void reviveNotificationAfterChannelDeletion(final int id) { - Log.d(TAG, ("Revive silently: ") + id); - decorator.modifyNotification(id, n -> { - n.extras.putBoolean(KEY_SILENT_REVIVAL, true); - }); - } - - @RequiresApi(O) private NotificationChannel migrate(NotificationManager nm, final String old_id, final String new_id, final String new_name, final boolean silent) { - final NotificationChannel channel_message = nm.getNotificationChannel(old_id); - nm.deleteNotificationChannel(old_id); - if (channel_message != null) return cloneChannel(channel_message, new_id, new_name); - else return makeChannel(new_id, new_name, silent); - } - - @RequiresApi(O) private NotificationChannel makeChannel(final String channel_id, final String name, final boolean silent) { - final NotificationChannel channel = new NotificationChannel(channel_id, name, NotificationManager.IMPORTANCE_HIGH/* Allow heads-up (by default) */); - if (silent) channel.setSound(null, null); - else channel.setSound(getDefaultSound(), new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT).build()); - channel.enableLights(true); - channel.setLightColor(LIGHT_COLOR); - return channel; - } - - @RequiresApi(O) private NotificationChannel cloneChannel(final NotificationChannel channel, final String id, final String new_name) { - final NotificationChannel clone = new NotificationChannel(id, new_name, channel.getImportance()); - clone.setGroup(channel.getGroup()); - clone.setDescription(channel.getDescription()); - clone.setLockscreenVisibility(channel.getLockscreenVisibility()); - clone.setSound(Optional.ofNullable(channel.getSound()).orElse(getDefaultSound()), channel.getAudioAttributes()); - clone.setBypassDnd(channel.canBypassDnd()); - clone.setLightColor(channel.getLightColor()); - clone.setShowBadge(channel.canShowBadge()); - clone.setVibrationPattern(channel.getVibrationPattern()); - return clone; - } - - @Nullable private Uri getDefaultSound() { // Before targeting O, WeChat actually plays sound by itself (not via Notification). - return mWeChatTargetingO ? Settings.System.DEFAULT_NOTIFICATION_URI : null; - } - - private boolean isWeChatTargeting26OrAbove() { - try { - return getPackageManager().getApplicationInfo(WECHAT_PACKAGE, PackageManager.GET_UNINSTALLED_PACKAGES).targetSdkVersion >= O; - } catch (final PackageManager.NameNotFoundException e) { - return false; + if (type == Conversation.TYPE_UNKNOWN) type = WeChatMessage.guessConversationType(content, n.tickerText.toString().trim(), title); + final boolean is_group_chat = Conversation.isGroupChat(type); + if (SDK_INT >= O) { + if (extras.containsKey(KEY_SILENT_REVIVAL)) { + setGroup(n, "nevo.group.auto"); // Special group name to let Nevolution auto-group it as if not yet grouped. (To be standardized in SDK) + setGroupAlertBehavior(n, Notification.GROUP_ALERT_SUMMARY); // This trick makes notification silent } + if (is_group_chat && ! CHANNEL_DND.equals(channel_id)) setChannelId(n, CHANNEL_GROUP_CONVERSATION); + else if (channel_id == null) setChannelId(n, CHANNEL_MESSAGE); // WeChat versions targeting O+ have its own channel for message } - - } - public static class WeChatSystemUIDecorator extends NevoDecoratorService.SystemUIDecorator implements HookSupport { - - private final WeChatDecorator decorator; + // WeChat previously uses dynamic counter starting from 4097 as notification ID, which is reused after cancelled by WeChat itself, + // causing conversation duplicate or overwritten notifications. + final Conversation conversation = mConversationManager.getConversation(id); - public WeChatSystemUIDecorator(String prefKey, WeChatDecorator decorator) { - super(prefKey); - this.decorator = decorator; - } + conversation.setTitle(title); + conversation.summary = content; + conversation.ticker = n.tickerText; + conversation.timestamp = n.when; + if (is_recall) + conversation.setType((recaller == null) ? Conversation.TYPE_DM_RECALL : Conversation.TYPE_GC_RECALL); + else if (conversation.getType() == Conversation.TYPE_UNKNOWN) + conversation.setType(WeChatMessage.guessConversationType(conversation)); - @Override public void hook(XC_LoadPackage.LoadPackageParam loadPackageParam) { - final Class clazz = XposedHelpers.findClass("android.app.Notification$Builder", loadPackageParam.classLoader); - XposedBridge.log("Builder clazz: " + clazz); - // 强制自定义视图 - XposedBridge.hookAllMethods(clazz, "createContentView", new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - if (BuildConfig.DEBUG) Log.d(TAG, "createContentView"); - Notification.Builder builder = (Notification.Builder)param.thisObject; - Notification n = (Notification)XposedHelpers.getObjectField(builder, "mN"); - RemoteViews remoteViews = NevoDecoratorService.overridedContentView(n); - if (remoteViews != null) { - Log.d(TAG, "cheating createContentView"); - param.setResult(remoteViews); - } - } - }); - XposedBridge.hookAllMethods(clazz, "createBigContentView", new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - if (BuildConfig.DEBUG) Log.d(TAG, "createContentView"); - Notification.Builder builder = (Notification.Builder)param.thisObject; - Notification n = (Notification)XposedHelpers.getObjectField(builder, "mN"); - RemoteViews remoteViews = NevoDecoratorService.overridedBigContentView(n); - if (remoteViews != null) { - Log.d(TAG, "cheating createBigContentView"); - param.setResult(remoteViews); - } - } - }); - } + extras.putBoolean(Notification.EXTRA_SHOW_WHEN, true); + // if (mPreferences.getBoolean(mPrefKeyWear, false)) n.flags &= ~ Notification.FLAG_LOCAL_ONLY; // TODO + setSortKey(n, String.valueOf(Long.MAX_VALUE - n.when + (is_group_chat ? GROUP_CHAT_SORT_KEY_SHIFT : 0))); // Place group chat below other messages - @Override public Decorating onNotificationPosted(final StatusBarNotification evolving) { - // cache(evolving); + MessagingStyle messaging = mMessagingBuilder.buildFromExtender(conversation, id, n, title, getArchivedNotifications(id)); // build message from android auto + if (messaging == null) // EXTRA_TEXT will be written in buildFromArchive() + messaging = mMessagingBuilder.buildFromArchive(conversation, n, title, getArchivedNotifications(id)); + if (messaging == null) return Decorating.Unprocessed; + final List messages = messaging.getMessages(); + if (messages.isEmpty()) return Decorating.Unprocessed; - // final String key = evolving.getKey(); - // setOriginalId(evolving, evolving.getId()); - // setOriginalKey(evolving, key); - // setOriginalTag(evolving, evolving.getTag()); - - // final Notification n = evolving.getNotification(); - // final Bundle extras = n.extras; - // final Context context = getPackageContext(); - - // // 图片 - // if (extras.containsKey(EXTRA_PICTURE_PATH)) { - // final String path = extras.getString(EXTRA_PICTURE_PATH); - // File file = new File(path); - // if (!file.exists() && path.startsWith(STORAGE_PREFIX)) { // StorageRedirect - // // path = "/storage/emulated/0/Android/data/com.tencent.mm/sdcard/" + path.substring(STORAGE_PREFIX.length()); - // if (BuildConfig.DEBUG) Log.d(TAG, "path " + path); - // file = new File(path.replace(STORAGE_PREFIX, "/storage/emulated/0/Android/data/com.tencent.mm/sdcard/")); - // } - // if (file.exists()) extras.putString(EXTRA_PICTURE_PATH, file.getPath()); // 更新文件路径 - // } - - // CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE); - // if (title == null || title.length() == 0) { - // Log.e(TAG, "Title is missing: " + evolving); - return Decorating.Unprocessed; - // } - // if (title != (title = EmojiTranslator.translate(title))) extras.putCharSequence(Notification.EXTRA_TITLE, title); - // n.color = PRIMARY_COLOR; // Tint the small icon - - // final String channel_id = SDK_INT >= O ? n.getChannelId() : null; - // final CharSequence content_text = extras.getCharSequence(Notification.EXTRA_TEXT); - // if (BuildConfig.DEBUG) Log.d(TAG, "content_text " + content_text); - // boolean is_recall = extras.getBoolean(EXTRA_RECALL); - // String recaller = extras.getString(EXTRA_RECALLER); - // if (BuildConfig.DEBUG) Log.d(TAG, "recaller " + recaller); // TODO 将撤回消息本身显示在消息记录中 - // if (CHANNEL_MISC.equals(channel_id)) { // Misc. notifications on Android 8+. - // return Decorating.Unprocessed; - // } else if (n.tickerText == null) { // Legacy misc. notifications. - // if (!is_recall) { - // Log.d(TAG, "Skip further process for non-conversation notification: " + title); // E.g. web login confirmation notification. - // return Decorating.Unprocessed; - // } - // } - // if (content_text == null) return Decorating.Unprocessed; - - // // WeChat previously uses dynamic counter starting from 4097 as notification ID, which is reused after cancelled by WeChat itself, - // // causing conversation duplicate or overwritten notifications. - // final Conversation conversation; - // if (! isDistinctId(n, evolving.getPackageName())) { - // final int title_hash = title.hashCode(); // Not using the hash code of original title, which might have already evolved. - // setId(evolving, title_hash); - // conversation = mConversationManager.getConversation(title_hash); - // } else conversation = mConversationManager.getConversation(getOriginalId(evolving)); - - // conversation.setTitle(title); - // conversation.summary = content_text; - // conversation.ticker = n.tickerText; - // conversation.timestamp = n.when; - // if (is_recall) - // conversation.setType((recaller == null) ? Conversation.TYPE_DM_RECALL : Conversation.TYPE_GC_RECALL); - // else if (conversation.getType() == Conversation.TYPE_UNKNOWN) - // conversation.setType(WeChatMessage.guessConversationType(conversation)); - // final boolean is_group_chat = conversation.isGroupChat(); - - // extras.putBoolean(Notification.EXTRA_SHOW_WHEN, true); - // // if (mPreferences.getBoolean(mPrefKeyWear, false)) n.flags &= ~ Notification.FLAG_LOCAL_ONLY; // TODO - // setSortKey(n, String.valueOf(Long.MAX_VALUE - n.when + (is_group_chat ? GROUP_CHAT_SORT_KEY_SHIFT : 0))); // Place group chat below other messages - - // MessagingStyle messaging = mMessagingBuilder.buildFromExtender(conversation, evolving, title, getArchivedNotifications(getOriginalKey(evolving))); // build message from android auto - // if (messaging == null) // EXTRA_TEXT will be written in buildFromArchive() - // messaging = mMessagingBuilder.buildFromArchive(conversation, n, title, getArchivedNotifications(getOriginalKey(evolving))); - // if (messaging == null) return Decorating.Unprocessed; - // final List messages = messaging.getMessages(); - // if (messages.isEmpty()) return Decorating.Unprocessed; - - // if (is_group_chat) messaging.setGroupConversation(true).setConversationTitle(title); - // MessagingBuilder.flatIntoExtras(messaging, extras); - // extras.putString(Notification.EXTRA_TEMPLATE, TEMPLATE_MESSAGING); - - // if (SDK_INT >= N && extras.getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY) != null) - // n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; // No more alert for direct-replied notification. - - // return Decorating.Processed; - } - - @Override public void onNotificationRemoved(final StatusBarNotification notification, final int reason) { - Log.d(TAG, "onNotificationRemoved(" + notification + ", " + reason + ")"); - super.onNotificationRemoved(notification, reason); - } - - public void onNotificationRemoved(final String id, final int reason) { - Log.d(TAG, "onNotificationRemoved(" + id + ", " + reason + ")"); - // if (reason == REASON_APP_CANCEL) { // Only if "Removal-Aware" of Nevolution is activated - // Log.d(TAG, "Cancel notification: " + id); - // // cancelNotification(id); // Will cancel all notifications evolved from this original key, thus trigger the "else" branch below - // } else if (reason == REASON_CHANNEL_BANNED) { // In case WeChat deleted our notification channel for group conversation in Insider delivery mode - // // mHandler.post(() -> reviveNotificationAfterChannelDeletion(id)); - // } else if (SDK_INT < O || reason == REASON_CANCEL) { // Exclude the removal request by us in above case. (Removal-Aware is only supported on Android 8+) - // mMessagingBuilder.markRead(id); - // } - } + if (is_group_chat) messaging.setGroupConversation(true).setConversationTitle(title); + MessagingBuilder.flatIntoExtras(messaging, extras); + extras.putString(Notification.EXTRA_TEMPLATE, TEMPLATE_MESSAGING); - private MessagingBuilder mMessagingBuilder; - private final ConversationManager mConversationManager = new ConversationManager(); + if (SDK_INT >= N && extras.getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY) != null) + n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; // No more alert for direct-replied notification. - @Override public void onCreate(SharedPreferences pref) { - super.onCreate(pref); - - // mMessagingBuilder = new MessagingBuilder(getAppContext(), getPackageContext(), decorator::recastNotification); // Must be called after loadPreferences(). - } - - @Override public void onDestroy() { - mMessagingBuilder.close(); - super.onDestroy(); - } - - private boolean isDistinctId(final Notification n, final String pkg) { - if (mDistinctIdSupported != null) return mDistinctIdSupported; - int version = 0; - final ApplicationInfo app_info = n.extras.getParcelable("android.appInfo"); - if (app_info != null) try { - if (pkg.equals(app_info.packageName)) // This will be Nevolution for active evolved notifications. - //noinspection JavaReflectionMemberAccess - version = (int) ApplicationInfo.class.getField("versionCode").get(app_info); - } catch (final IllegalAccessException | NoSuchFieldException | ClassCastException ignored) {} // Fall-through - if (version == 0) try { - version = getPackageManager().getPackageInfo(pkg, 0).versionCode; - } catch (final PackageManager.NameNotFoundException ignored) {} - return version != 0 && (mDistinctIdSupported = version >= 1340); // Distinct ID is supported since WeChat 6.7.3. + // 维护NotificationChannel + channel_id = n.getChannelId(); + if (channel_id != null) { // 确保NotificationChannel存在 + NotificationChannel channel = nm.getNotificationChannel(channel_id); + if (BuildConfig.DEBUG) Log.d(TAG, channel_id + " " + channel); + if (channel != null) return Decorating.Processed; + switch (channel_id) { + case CHANNEL_GROUP_CONVERSATION: + channel = makeChannel(CHANNEL_GROUP_CONVERSATION, channelGroupMessage, false); + break; + case CHANNEL_MESSAGE: + channel = migrate(nm, OLD_CHANNEL_MESSAGE, CHANNEL_MESSAGE, channelMessage, false); + break; + case CHANNEL_MISC: + channel = migrate(nm, OLD_CHANNEL_MISC, CHANNEL_MISC, channelMisc, true); + break; + } + if (BuildConfig.DEBUG) Log.d(TAG, channel_id + " " + channel); + nm.createNotificationChannel(channel); + channel = nm.getNotificationChannel(channel_id); + if (BuildConfig.DEBUG) Log.d(TAG, channel_id + " " + channel); } - private Boolean mDistinctIdSupported; - + + return Decorating.Processed; } - public static final String WECHAT_PACKAGE = "com.tencent.mm"; - private static final long GROUP_CHAT_SORT_KEY_SHIFT = 24 * 60 * 60 * 1000L; // Sort group chat like one day older message. - private static final String CHANNEL_MESSAGE = "message_channel_new_id"; // Channel ID used by WeChat for all message notifications - private static final String OLD_CHANNEL_MESSAGE = "message"; // old name for migration - private static final String CHANNEL_MISC = "reminder_channel_id"; // Channel ID used by WeChat for misc. notifications - private static final String OLD_CHANNEL_MISC = "misc"; // old name for migration - private static final String CHANNEL_DND = "message_dnd_mode_channel_id"; // Channel ID used by WeChat for its own DND mode - private static final String CHANNEL_GROUP_CONVERSATION = "group"; // WeChat has no separate group for group conversation - private static final String RECALL_PATTERN = "(\"(?[^\"]+)\" )?撤回了?一条消息"; - private static final Pattern pattern = Pattern.compile(RECALL_PATTERN); // [2条]"🦉 " 撤回了一条消息 / [2条]撤回一条消息 + private void reviveNotificationAfterChannelDeletion(final int id) { + Log.d(TAG, ("Revive silently: ") + id); + modifyNotification(id, n -> { + n.extras.putBoolean(KEY_SILENT_REVIVAL, true); + }); + } - private static final @ColorInt int PRIMARY_COLOR = 0xFF33B332; - private static final @ColorInt int LIGHT_COLOR = 0xFF00FF00; - static final String ACTION_SETTINGS_CHANGED = "SETTINGS_CHANGED"; - static final String ACTION_DEBUG_NOTIFICATION = "DEBUG"; - private static final String KEY_SILENT_REVIVAL = "nevo.wechat.revival"; - private static final String EXTRA_RECALL = "nevo.wechat.recall"; - private static final String EXTRA_RECALLER = "nevo.wechat.recaller"; - public static final String EXTRA_PICTURE_PATH = "nevo.wechat.picturePath"; - private static final String EXTRA_PICTURE = "nevo.wechat.picture"; - private static final String STORAGE_PREFIX = "/storage/emulated/0/"; + @RequiresApi(O) private NotificationChannel migrate(NotificationManager nm, final String old_id, final String new_id, final String new_name, final boolean silent) { + final NotificationChannel channel_message = nm.getNotificationChannel(old_id); + nm.deleteNotificationChannel(old_id); + if (channel_message != null) return cloneChannel(channel_message, new_id, new_name); + else return makeChannel(new_id, new_name, silent); + } - @Override public LocalDecorator createLocalDecorator(String packageName) { - return new WeChatLocalDecorator(prefKey, this); + @RequiresApi(O) private NotificationChannel makeChannel(final String channel_id, final String name, final boolean silent) { + final NotificationChannel channel = new NotificationChannel(channel_id, name, NotificationManager.IMPORTANCE_HIGH/* Allow heads-up (by default) */); + if (silent) channel.setSound(null, null); + else channel.setSound(getDefaultSound(), new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT).build()); + channel.enableLights(true); + channel.setLightColor(LIGHT_COLOR); + return channel; } - @Override public SystemUIDecorator createSystemUIDecorator() { - return new WeChatSystemUIDecorator(prefKey, this); + + @RequiresApi(O) private NotificationChannel cloneChannel(final NotificationChannel channel, final String id, final String new_name) { + final NotificationChannel clone = new NotificationChannel(id, new_name, channel.getImportance()); + clone.setGroup(channel.getGroup()); + clone.setDescription(channel.getDescription()); + clone.setLockscreenVisibility(channel.getLockscreenVisibility()); + clone.setSound(Optional.ofNullable(channel.getSound()).orElse(getDefaultSound()), channel.getAudioAttributes()); + clone.setBypassDnd(channel.canBypassDnd()); + clone.setLightColor(channel.getLightColor()); + clone.setShowBadge(channel.canShowBadge()); + clone.setVibrationPattern(channel.getVibrationPattern()); + return clone; } - static final String TAG = "WeChatDecorator"; + @Nullable private Uri getDefaultSound() { // Before targeting O, WeChat actually plays sound by itself (not via Notification). + return mWeChatTargetingO ? Settings.System.DEFAULT_NOTIFICATION_URI : null; + } - public static interface ModifyNotification { void modify(Notification n); } + private boolean isWeChatTargeting26OrAbove() { + try { + return getPackageManager().getApplicationInfo(WECHAT_PACKAGE, PackageManager.GET_UNINSTALLED_PACKAGES).targetSdkVersion >= O; + } catch (final PackageManager.NameNotFoundException e) { + return false; + } + } private void modifyNotification(final int id, final ModifyNotification... modifies) { - if (hasArchivedNotifications0(id)) { - Notification n = getArchivedNotification0(id); + if (hasArchivedNotifications(id)) { + Notification n = getArchivedNotification(id); for (ModifyNotification modify : modifies) modify.modify(n); Log.d(TAG, "recast " + id + " " + n.extras.getCharSequence(Notification.EXTRA_TITLE)); - recastNotification0(id, n); + recastNotification(id, n); } else { Log.d(TAG, "can not recast " + id + ", so cancel it"); cancelNotification(id); diff --git a/src/main/java/com/oasisfeng/nevo/sdk/NevoDecoratorService.java b/src/main/java/com/oasisfeng/nevo/sdk/NevoDecoratorService.java index 8ef20d4..4757bf3 100644 --- a/src/main/java/com/oasisfeng/nevo/sdk/NevoDecoratorService.java +++ b/src/main/java/com/oasisfeng/nevo/sdk/NevoDecoratorService.java @@ -45,10 +45,6 @@ public abstract class NevoDecoratorService { private static final String TAG = "NevoDecoratorService"; - protected interface BindRemoteViews { - void bind(RemoteViews remoteViews); - } - private static volatile Context appContext, packageContext; private static volatile NotificationListenerService mNLS; private static volatile NotificationManager mNM; @@ -91,52 +87,6 @@ protected static String getString(int key) { return getPackageContext().getString(key); } - public static void setId(StatusBarNotification sbn, int id) { - XposedHelpers.setIntField(sbn, "id", id); - } - - public static void setNotification(StatusBarNotification sbn, Notification n) { - XposedHelpers.setObjectField(sbn, "notification", n); - } - - public static int getOriginalId(StatusBarNotification sbn) { - return (Integer)XposedHelpers.getAdditionalInstanceField(sbn, "originalId"); - } - - public static void setOriginalId(StatusBarNotification sbn, int id) { - XposedHelpers.setAdditionalInstanceField(sbn, "originalId", (Integer)id); - } - - public static String getOriginalKey(StatusBarNotification sbn) { - return (String)XposedHelpers.getAdditionalInstanceField(sbn, "originalKey"); - } - - public static void setOriginalKey(StatusBarNotification sbn, String key) { - XposedHelpers.setAdditionalInstanceField(sbn, "originalKey", key); - } - - public static void setOriginalTag(StatusBarNotification sbn, String tag) { - XposedHelpers.setAdditionalInstanceField(sbn, "originalTag", tag); - } - - public static RemoteViews overrideBigContentView(Notification n, RemoteViews remoteViews) { - n.extras.putParcelable(EXTRAS_BIG_CONTENT_VIEW_OVERRIDE, remoteViews); - return remoteViews; - } - - public static RemoteViews overridedBigContentView(Notification n) { - return n.extras.getParcelable(EXTRAS_BIG_CONTENT_VIEW_OVERRIDE); - } - - public static RemoteViews overrideContentView(Notification n, RemoteViews remoteViews) { - n.extras.putParcelable(EXTRAS_CONTENT_VIEW_OVERRIDE, remoteViews); - return remoteViews; - } - - public static RemoteViews overridedContentView(Notification n) { - return (RemoteViews)n.extras.getParcelable(EXTRAS_CONTENT_VIEW_OVERRIDE); - } - public static void setChannelId(Notification n, String channelId) { XposedHelpers.setObjectField(n, "mChannelId", channelId); } @@ -157,179 +107,86 @@ public static void setActions(Notification n, Action... actions) { XposedHelpers.setObjectField(n, "actions", actions); } - private static final Map> map = new WeakHashMap<>(); - private static final Map> map0 = new WeakHashMap<>(); - - protected static void cache(StatusBarNotification sbn) { - final String key = sbn.getKey(); - LinkedList queue = map.get(key); - if (queue == null) { - queue = new LinkedList<>(); - map.put(key, queue); - } - queue.add(sbn); - if (queue.size() > MAX_NUM_ARCHIVED) queue.remove(); - } - - protected static List getArchivedNotifications(String key) { - LinkedList queue = map.get(key); - return queue != null ? new ArrayList<>(queue) : new ArrayList<>(); - } - - protected static StatusBarNotification getArchivedNotification(String key) { - LinkedList queue = map.get(key); - return queue.getLast(); - } - - protected static boolean hasArchivedNotifications(String key) { - return map.containsKey(key); - } + private static final Map> map = new WeakHashMap<>(); - protected static void cache0(final int id, final Notification n) { - Log.d(TAG, "cache0 id " + id); - LinkedList queue = map0.get(id); + protected static void cache(final int id, final Notification n) { + Log.d(TAG, "cache id " + id); + LinkedList queue = map.get(id); if (queue == null) { queue = new LinkedList<>(); - map0.put(id, queue); + map.put(id, queue); } queue.add(n); - Log.d(TAG, "cache0 queue " + queue); + Log.d(TAG, "cache queue " + queue); if (queue.size() > MAX_NUM_ARCHIVED) queue.remove(); } - protected static List getArchivedNotifications0(int key) { - LinkedList queue = map0.get(key); + protected static List getArchivedNotifications(int key) { + LinkedList queue = map.get(key); return queue != null ? new ArrayList<>(queue) : new ArrayList<>(); } - protected static Notification getArchivedNotification0(int key) { - LinkedList queue = map0.get(key); + protected static Notification getArchivedNotification(int key) { + LinkedList queue = map.get(key); return queue.getLast(); } - protected static boolean hasArchivedNotifications0(int key) { - return map0.containsKey(key); + protected static boolean hasArchivedNotifications(int key) { + return map.containsKey(key); } /** * 在应用进程中执行的通知预处理,某些功能(NotificationChannel等)在此实现。 */ - public static class LocalDecorator { - protected final String prefKey; - private boolean disabled; - - protected LocalDecorator(String prefKey) { this.prefKey = prefKey; } - - public boolean isDisabled() { return disabled; } - public void setDisabled(boolean disabled) { this.disabled = disabled; } - - @Keep public void onCreate(SharedPreferences pref) { - this.disabled = !pref.getBoolean(prefKey + ".enabled", true); - if (BuildConfig.DEBUG) Log.d(TAG, prefKey + ".disabled " + this.disabled); - } - - @Keep public void onDestroy() {} - - @Keep public Decorating apply(NotificationManager nm, String tag, int id, Notification n) { - return Decorating.Unprocessed; - } - } - - /** - * 在系统UI(SystemUI)中执行的通知处理。 - */ - @Deprecated - public static class SystemUIDecorator { - protected final String prefKey; - private boolean disabled; + // public static class LocalDecorator { + // protected final String prefKey; + // private boolean disabled; - protected SystemUIDecorator(String prefKey) { this.prefKey = prefKey; } + // protected LocalDecorator(String prefKey) { this.prefKey = prefKey; } - public boolean isDisabled() { return disabled; } - public void setDisabled(boolean disabled) { this.disabled = disabled; } + // public boolean isDisabled() { return disabled; } + // public void setDisabled(boolean disabled) { this.disabled = disabled; } - @Keep public void onCreate(SharedPreferences pref) { - this.disabled = !pref.getBoolean(prefKey + ".enabled", true); - if (BuildConfig.DEBUG) Log.d(TAG, prefKey + ".disabled " + this.disabled); - } + // @Keep public void onCreate(SharedPreferences pref) { + // this.disabled = !pref.getBoolean(prefKey + ".enabled", true); + // if (BuildConfig.DEBUG) Log.d(TAG, prefKey + ".disabled " + this.disabled); + // } - @Keep public void onDestroy() {} + // @Keep public void onDestroy() {} - @Keep public Decorating onNotificationPosted(final StatusBarNotification sbn) { - Log.d(TAG, "onNotificationPosted(" + sbn + ")"); - return Decorating.Unprocessed; - } - @Keep public void onNotificationRemoved(final StatusBarNotification evolving, final int reason) { - Log.d(TAG, "onNotificationRemoved(" + evolving + ", " + reason + ")"); - } - - protected final void cancelNotification(String key) { - Log.d(TAG, "cancelNotification " + key); - if (mNLS != null) mNLS.cancelNotification(key); - } - } + // @Keep public Decorating apply(NotificationManager nm, String tag, int id, Notification n) { + // return Decorating.Unprocessed; + // } + // } protected final String prefKey; - // private boolean disabled; + private boolean disabled; public NevoDecoratorService() { this.prefKey = getClass().getSimpleName(); } - private LocalDecorator localDecorator; - @Keep public LocalDecorator createLocalDecorator(String packageName) { return null; } - @Keep public final LocalDecorator getLocalDecorator(String packageName) { - if (localDecorator == null) localDecorator = createLocalDecorator(packageName); // 不同package在不同进程,无需映射packageName - return localDecorator; - } - private SystemUIDecorator systemUIDecorator; - @Keep public SystemUIDecorator createSystemUIDecorator() { return null; } - @Keep public final SystemUIDecorator getSystemUIDecorator() { - if (systemUIDecorator == null) systemUIDecorator = createSystemUIDecorator(); - return systemUIDecorator; - } + public boolean isDisabled() { return disabled; } + public void setDisabled(boolean disabled) { this.disabled = disabled; } - // public boolean isDisabled() { return disabled; } - // public void setDisabled(boolean disabled) { this.disabled = disabled; } - - // @Keep public void onCreate(SharedPreferences pref) { - // this.disabled = !pref.getBoolean(prefKey + ".enabled", true); - // if (BuildConfig.DEBUG) Log.d(TAG, prefKey + ".disabled " + this.disabled); - // } + @Keep public void onCreate(SharedPreferences pref) { + this.disabled = !pref.getBoolean(prefKey + ".enabled", true); + if (BuildConfig.DEBUG) Log.d(TAG, prefKey + ".disabled " + this.disabled); + } - // @Keep public void onDestroy() {} + @Keep public void onDestroy() {} - // /** - // * 在应用进程中执行的通知预处理,某些功能(NotificationChannel等)在此实现。 - // */ - // @Keep public void preApply(NotificationManager nm, String tag, int id, Notification n) {} - // @Keep public Decorating onNotificationPosted(final StatusBarNotification sbn) { - // apply(sbn); - // return Decorating.Processed; - // } - // /** - // * 在系统UI(SystemUI)中执行的通知处理。 - // */ - // @Deprecated - // @Keep public void apply(final StatusBarNotification evolving) {} - // @Keep public void onNotificationRemoved(final StatusBarNotification evolving, final int reason) { - // Log.d(TAG, "onNotificationRemoved(" + evolving + ", " + reason + ")"); - // onNotificationRemoved(evolving.getKey(), reason); - // } - // @Keep public void onNotificationRemoved(final String key, final int reason) {} + @Keep public Decorating apply(NotificationManager nm, String tag, int id, Notification n) { + return Decorating.Unprocessed; + } protected final void cancelNotification(int id) { - Log.d(TAG, "cancelNotification " + id); + Log.d(TAG, "cancelNotification " + mNM + " " + id); if (mNM != null) mNM.cancel(null, id); } - protected final void recastNotification(final StatusBarNotification sbn) { - Log.d(TAG, "recastNotification " + sbn + " " + mNLS); - if (mNLS != null) mNLS.onNotificationPosted(sbn, null); - } - - protected final void recastNotification0(final int id, final Notification n) { - Log.d(TAG, "recastNotification0 " + n.extras.getCharSequence(Notification.EXTRA_TITLE)); + protected final void recastNotification(final int id, final Notification n) { + Log.d(TAG, "recastNotification " + mNM + " " + n.extras.getCharSequence(Notification.EXTRA_TITLE)); if (mNM != null) mNM.notify(null, id, n); } } \ No newline at end of file diff --git a/src/main/java/com/oasisfeng/nevo/xposed/MainHook.java b/src/main/java/com/oasisfeng/nevo/xposed/MainHook.java index a06e2df..d2d7d46 100644 --- a/src/main/java/com/oasisfeng/nevo/xposed/MainHook.java +++ b/src/main/java/com/oasisfeng/nevo/xposed/MainHook.java @@ -25,8 +25,6 @@ import com.oasisfeng.nevo.sdk.HookSupport; import com.oasisfeng.nevo.sdk.NevoDecoratorService; -import com.oasisfeng.nevo.sdk.NevoDecoratorService.LocalDecorator; -import com.oasisfeng.nevo.sdk.NevoDecoratorService.SystemUIDecorator; import com.oasisfeng.nevo.xposed.BuildConfig; import de.robv.android.xposed.IXposedHookLoadPackage; @@ -82,119 +80,7 @@ private static void inspectThen(XC_LoadPackage.LoadPackageParam loadPackageParam @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) { - switch (loadPackageParam.packageName) { - case "com.android.systemui": - hookSystemUI(loadPackageParam); - break; - case "com.tencent.mm": // TODO - hookWeChat(loadPackageParam); - } - /* inspect(loadPackageParam, - "com.android.server.notification.NotificationManagerService", - "getNotificationChannel", - "deleteNotificationChannel", - "deleteNotificationChannelGroup", - "createNotificationChannels"); */ - } - - private void hookSystemUI(XC_LoadPackage.LoadPackageParam loadPackageParam) { - AtomicReference nlsRef = new AtomicReference<>(); - final XC_MethodHook onNotificationPosted = new XC_MethodHook() { // 捕获通知到达 - @Override - protected void beforeHookedMethod(MethodHookParam param) { - StatusBarNotification sbn = (StatusBarNotification)param.args[0]; - // RankingMap rankingMap = (RankingMap)param.args[1]; - Log.d(TAG, "onNotificationPosted"); - onNotificationPosted(sbn); - } - }, onNotificationRemoved = new XC_MethodHook() { // 捕获通知移除 - @Override - protected void beforeHookedMethod(MethodHookParam param) { - StatusBarNotification sbn = (StatusBarNotification)param.args[0]; - // RankingMap rankingMap = (RankingMap)param.args[1]; - // NotificationStats stats = (NotificationStats)param.args[2]; - int reason = (int)param.args[3]; - Log.d(TAG, "onNotificationRemoved"); - onNotificationRemoved(sbn, reason); - } - }, nls = new XC_MethodHook() { // 捕获NotificationListenerService的具体实现 - @Override - protected void afterHookedMethod(MethodHookParam param) { - // XposedBridge.log("nls constructor " + param.thisObject); - NotificationListenerService nls = (NotificationListenerService)param.thisObject; - if (nlsRef.compareAndSet(null, nls)) { - NevoDecoratorService.setNLS(nls); - } - try { - final Class clazz = nls.getClass(); - XposedBridge.log("NL clazz: " + clazz + " " + loadPackageParam.packageName); - Method method = XposedHelpers.findMethodExact(clazz, "onNotificationPosted", - StatusBarNotification.class, RankingMap.class); - Log.d(TAG, "method " + method); - XposedBridge.hookMethod(method, onNotificationPosted); - method = XposedHelpers.findMethodBestMatch(clazz, "onNotificationRemoved", StatusBarNotification.class, RankingMap.class, - XposedHelpers.findClass("android.service.notification.NotificationStats", loadPackageParam.classLoader), int.class); - Log.d(TAG, "method " + method); - XposedBridge.hookMethod(method, onNotificationRemoved); - } catch (XposedHelpers.ClassNotFoundError e) { XposedBridge.log("NL hook failed "); } - } - }; - try { - XposedBridge.hookAllConstructors(NotificationListenerService.class, nls); - } catch (XposedHelpers.ClassNotFoundError e) { XposedBridge.log("NotificationListenerService hook failed "); } - try { - final Class clazz = XposedHelpers.findClass("android.app.ContextImpl", loadPackageParam.classLoader); - XposedBridge.log("CI clazz: " + clazz); - AtomicReference ref = new AtomicReference<>(); - XposedBridge.hookAllMethods(clazz, "createAppContext", new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) { - Context context = (Context)param.getResult(); - if (ref.compareAndSet(null, context)) { - XposedBridge.log("onCreate " + context); - onCreate(context); - } - } - }); - } catch (XposedHelpers.ClassNotFoundError e) { XposedBridge.log("ContextImpl hook failed"); } - try { - SystemUIDecorator wechat = this.wechat.getSystemUIDecorator(); - if ((wechat instanceof HookSupport)) { ((HookSupport)wechat).hook(loadPackageParam); } - } catch (XposedHelpers.ClassNotFoundError e) { XposedBridge.log(this.wechat + " hook failed"); } - } - - private void onCreate(Context context) { - NevoDecoratorService.setAppContext(context); - - SystemUIDecorator wechat = this.wechat.getSystemUIDecorator(); - wechat.onCreate(pref); - } - - private void onNotificationPosted(StatusBarNotification sbn) { - if (XposedHelpers.getAdditionalInstanceField(sbn, "applied") != null) { - Log.d(TAG, "skip " + sbn); - return; - } - XposedHelpers.setAdditionalInstanceField(sbn, "applied", true); - - SystemUIDecorator wechat = this.wechat.getSystemUIDecorator(); - switch (sbn.getPackageName()) { - case "com.tencent.mm": - if (!wechat.isDisabled()) wechat.onNotificationPosted(sbn); - break; - } - } - - private void onNotificationRemoved(StatusBarNotification sbn, int reason) { - SystemUIDecorator wechat = this.wechat.getSystemUIDecorator(); - switch (sbn.getPackageName()) { - case "com.tencent.mm": - if (!wechat.isDisabled()) wechat.onNotificationRemoved(sbn, reason); - break; - } - } - - private void hookWeChat(XC_LoadPackage.LoadPackageParam loadPackageParam) { + if (!"com.tencent.mm".equals(loadPackageParam.packageName)) return; if (!"com.tencent.mm".equals(loadPackageParam.processName)) return; try { final Class clazz = XposedHelpers.findClass("android.app.NotificationManager", loadPackageParam.classLoader); @@ -208,12 +94,12 @@ protected void beforeHookedMethod(MethodHookParam param) { String tag = (String)param.args[0]; int id = (int)param.args[1]; Notification n = (Notification)param.args[2]; + Log.d(TAG, "before apply " + nm + " " + tag + " " + id); applyLocally(nm, tag, id, n); } }); } catch (XposedHelpers.ClassNotFoundError e) { XposedBridge.log("NotificationManager hook failed"); } try { - LocalDecorator wechat = this.wechat.getLocalDecorator("com.tencent.mm"); // TODO AtomicReference ref = new AtomicReference<>(); XposedHelpers.findAndHookMethod(ContextWrapper.class, "attachBaseContext", Context.class, new XC_MethodHook() { @Override @@ -227,8 +113,14 @@ protected void afterHookedMethod(MethodHookParam param) { } }); } catch (XposedHelpers.ClassNotFoundError e) { XposedBridge.log("ContextWrapper hook failed"); } + /* inspect(loadPackageParam, + "com.android.server.notification.NotificationManagerService", + "getNotificationChannel", + "deleteNotificationChannel", + "deleteNotificationChannelGroup", + "createNotificationChannels"); */ } - + // TODO private void applyLocally(NotificationManager nm, String tag, int id, Notification n) { if (XposedHelpers.getAdditionalInstanceField(n, "pre-applied") != null) { @@ -236,7 +128,7 @@ private void applyLocally(NotificationManager nm, String tag, int id, Notificati return; } XposedHelpers.setAdditionalInstanceField(n, "pre-applied", true); - LocalDecorator wechat = this.wechat.getLocalDecorator("com.tencent.mm"); // TODO + NevoDecoratorService.setNM(nm); if (!wechat.isDisabled()) wechat.apply(nm, tag, id, n); } }