diff --git a/src/main/java/com/oasisfeng/nevo/decorators/wechat/ConversationManager.java b/src/main/java/com/oasisfeng/nevo/decorators/wechat/ConversationManager.java index 677569d..9509762 100644 --- a/src/main/java/com/oasisfeng/nevo/decorators/wechat/ConversationManager.java +++ b/src/main/java/com/oasisfeng/nevo/decorators/wechat/ConversationManager.java @@ -2,11 +2,13 @@ import android.text.TextUtils; import android.util.ArrayMap; +import android.util.Log; import android.util.SparseArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; +import java.util.regex.Pattern; import androidx.annotation.IntDef; import androidx.annotation.Nullable; @@ -32,6 +34,7 @@ static class Conversation { @IntDef({ TYPE_UNKNOWN, TYPE_DIRECT_MESSAGE, TYPE_GROUP_CHAT, TYPE_BOT_MESSAGE }) @Retention(RetentionPolicy.SOURCE) @interface ConversationType {} private static final String SCHEME_ORIGINAL_NAME = "ON:"; + private static final Pattern pattern = Pattern.compile("^[a-zA-Z0-9\\u4e00-\\u9fa5]"); final int id; @Nullable String key; @@ -71,7 +74,16 @@ Person getGroupParticipant(final String key, final String name) { if (participant == null) builder = new Person.Builder().setKey(key); else if (! TextUtils.equals(name, requireNonNull(participant.getUri()).substring(SCHEME_ORIGINAL_NAME.length()))) // Original name is changed builder = participant.toBuilder(); - if (builder != null) mParticipants.put(key, participant = builder.setUri(SCHEME_ORIGINAL_NAME + name).setName(EmojiTranslator.translate(name)).build()); + if (builder != null) { + final CharSequence n = EmojiTranslator.translate(name); + builder.setUri(SCHEME_ORIGINAL_NAME + name); + if (pattern.matcher(n).find()) { + builder.setName(n); + } else { + builder.setName("\u200b" + n); + } + mParticipants.put(key, participant = builder.build()); + } return participant; } @@ -94,4 +106,5 @@ Conversation getConversation(final int id) { } private final SparseArray mConversations = new SparseArray<>(); + private static final String TAG = WeChatDecorator.TAG; } diff --git a/src/main/java/com/oasisfeng/nevo/decorators/wechat/EmojiMap.java b/src/main/java/com/oasisfeng/nevo/decorators/wechat/EmojiMap.java index 9f6b045..50c025a 100644 --- a/src/main/java/com/oasisfeng/nevo/decorators/wechat/EmojiMap.java +++ b/src/main/java/com/oasisfeng/nevo/decorators/wechat/EmojiMap.java @@ -13,32 +13,51 @@ public class EmojiMap { // Pull Request is welcome. (columns are split by "tab" for visual alignment) // Proper emoji is needed for commented lines. static final String[][] MAP = new String[][] { + { "表情", null, "㊙️" }, + { "链接", null, "🔗" }, + { "花", null, "🌺" }, + { "钱", null, "💰" }, + { "闪烁", null, "✨" }, + { "X", "X", "❌" }, { "OK", "OK", "👌" }, { "耶", "Yeah!", "✌" }, { "嘘", "Silent", "🤫" }, { "晕", "Dizzy", "😲" }, { "衰", "BadLuck", "😳" }, + { "脸红", null, "😳" }, { "色", "Drool", "😍" }, { "囧", "Tension", "☺" }, - { "鸡", "Chick", "🐥" }, + { "热情", null, "☺" }, + { "鸡", "Chick", "🐥" }, // missing { "强", "Thumbs Up", "👍" }, { "弱", "Weak", "👎" }, - { "睡", "Sleep", "😴" }, + { "睡", "Sleep", "😴" }, // missing { "吐", "Puke", "🤢" }, { "困", "Drowsy", "😪" }, + { "睡觉", null, "😪" }, { "發", "Rich", "🀅" }, { "微笑", "Smile", "😃" }, + { "大笑", null, "😃" }, // iOS 7.0.4 + { "开心", null, "😃" }, // Android 7.0.4 { "撇嘴", "Grimace", "😖" }, + { "呸", null, "😖" }, { "发呆", "Scowl", "😳" }, - { "得意", "CoolGuy", "😎" }, + { "脸红", null, "😳" }, + { "得意", "CoolGuy", "😎" }, // missing { "流泪", "Sob", "😭" }, + { "哭", null, "😭" }, { "害羞", "Shy", "☺" }, - { "闭嘴", "Shutup", "🤐" }, + { "闭嘴", "Shutup", "🤐" }, // missing { "大哭", "Cry", "😣" }, + { "悔恨", null, "😣" }, { "尴尬", "Awkward", "😰" }, + { "担心", null, "😰" }, { "发怒", "Angry", "😡" }, + { "生气", null, "😡" }, { "调皮", "Tongue", "😜" }, + { "戏弄", null, "😜" }, { "呲牙", "Grin", "😁" }, + { "露齿笑", null, "😁" }, { "惊讶", "Surprise", "😱" }, { "难过", "Frown", "🙁" }, { "抓狂", "Scream", "😫" }, @@ -99,6 +118,7 @@ public class EmojiMap { // { "机智", "Smart", "" }, // { "抠鼻", "DigNose", "" }, // { "可怜", "Whimper", "" }, + { "皇冠", null, "👑" }, { "快哭了", "Puling", "😔" }, // { "左哼哼", "Bah!L", "" }, // { "右哼哼", "Bah!R", "" }, diff --git a/src/main/java/com/oasisfeng/nevo/decorators/wechat/EmojiTranslator.java b/src/main/java/com/oasisfeng/nevo/decorators/wechat/EmojiTranslator.java index c71a6aa..ab7ee57 100644 --- a/src/main/java/com/oasisfeng/nevo/decorators/wechat/EmojiTranslator.java +++ b/src/main/java/com/oasisfeng/nevo/decorators/wechat/EmojiTranslator.java @@ -17,8 +17,10 @@ public class EmojiTranslator { private static final Map ENGLISH_MAP = new HashMap<>(EmojiMap.MAP.length); static { for (final String[] entry : EmojiMap.MAP) { - CHINESE_MAP.put(entry[0], entry[2]); - ENGLISH_MAP.put(entry[1], entry[2]); + if (entry[0] != null) + CHINESE_MAP.put(entry[0], entry[2]); + if (entry[1] != null) + ENGLISH_MAP.put(entry[1], entry[2]); } } diff --git a/src/main/java/com/oasisfeng/nevo/decorators/wechat/MessagingBuilder.java b/src/main/java/com/oasisfeng/nevo/decorators/wechat/MessagingBuilder.java index ca8fb27..c13a420 100644 --- a/src/main/java/com/oasisfeng/nevo/decorators/wechat/MessagingBuilder.java +++ b/src/main/java/com/oasisfeng/nevo/decorators/wechat/MessagingBuilder.java @@ -37,7 +37,6 @@ import androidx.core.app.NotificationCompat.MessagingStyle; import androidx.core.app.NotificationCompat.MessagingStyle.Message; import androidx.core.app.Person; -import androidx.core.graphics.drawable.IconCompat; import static android.app.Notification.EXTRA_REMOTE_INPUT_HISTORY; import static android.app.Notification.EXTRA_TEXT; @@ -87,14 +86,16 @@ class MessagingBuilder { return null; } - final LongSparseArray lines = new LongSparseArray<>(MAX_NUM_HISTORICAL_LINES); + final LongSparseArray tickerArray = new LongSparseArray<>(MAX_NUM_HISTORICAL_LINES); + final LongSparseArray textArray = new LongSparseArray<>(MAX_NUM_HISTORICAL_LINES); CharSequence text; int count = 0, num_lines_with_colon = 0; final String redundant_prefix = title.toString() + SENDER_MESSAGE_SEPARATOR; for (final StatusBarNotification each : archive) { final Notification notification = each.getNotification(); + tickerArray.put(notification.when, notification.tickerText); final Bundle its_extras = notification.extras; - final CharSequence its_title = its_extras.getCharSequence(Notification.EXTRA_TITLE); + final CharSequence its_title = EmojiTranslator.translate(its_extras.getCharSequence(Notification.EXTRA_TITLE)); if (! title.equals(its_title)) { Log.d(TAG, "Skip other conversation with the same key in archive: " + its_title); // ID reset by WeChat due to notification removal in previous evolving continue; @@ -111,27 +112,28 @@ class MessagingBuilder { if (trimmed_text.toString().startsWith(redundant_prefix)) // Remove redundant prefix trimmed_text = trimmed_text.subSequence(redundant_prefix.length(), trimmed_text.length()); else if (trimmed_text.toString().indexOf(SENDER_MESSAGE_SEPARATOR) > 0) num_lines_with_colon ++; - lines.put(notification.when, trimmed_text); + textArray.put(notification.when, trimmed_text); } else { count = 1; - lines.put(notification.when, text = its_text); + textArray.put(notification.when, text = its_text); if (text.toString().indexOf(SENDER_MESSAGE_SEPARATOR) > 0) num_lines_with_colon ++; } } n.number = count; - if (lines.size() == 0) { + if (textArray.size() == 0) { Log.w(TAG, "No lines extracted, expected " + count); return null; } final MessagingStyle messaging = new MessagingStyle(mUserSelf); - final boolean sender_inline = num_lines_with_colon == lines.size(); - for (int i = 0, size = lines.size(); i < size; i++) // All lines have colon in text - messaging.addMessage(buildMessage(conversation, lines.keyAt(i), n.tickerText, lines.valueAt(i), sender_inline ? null : title.toString())); + final boolean sender_inline = num_lines_with_colon == textArray.size(); + for (int i = 0, size = textArray.size(); i < size; i++) { // All lines have colon in text + messaging.addMessage(buildMessage(conversation, textArray.keyAt(i), tickerArray.valueAt(i), textArray.valueAt(i), sender_inline ? null : title.toString())); + } return messaging; } - @Nullable MessagingStyle buildFromExtender(final Conversation conversation, final MutableStatusBarNotification sbn) { + @Nullable MessagingStyle buildFromExtender(final Conversation conversation, final MutableStatusBarNotification sbn, final CharSequence title, final List archive) { final MutableNotification n = sbn.getNotification(); final Notification.CarExtender extender = new Notification.CarExtender(n); final CarExtender.UnreadConversation convs = extender.getUnreadConversation(); @@ -157,7 +159,7 @@ class MessagingBuilder { } final MessagingStyle messaging = new MessagingStyle(mUserSelf); - final Message[] messages = WeChatMessage.buildFromCarConversation(conversation, convs); + final Message[] messages = WeChatMessage.buildFromCarConversation(conversation, convs, archive); for (final Message message : messages) messaging.addMessage(message); final PendingIntent on_read = convs.getReadPendingIntent(); @@ -356,8 +358,7 @@ interface Controller { void recastNotification(String key, Bundle addition); } } private static Person buildPersonFromProfile(final Context context) { - return new Person.Builder().setName(context.getString(R.string.self_display_name)) - .setIcon(IconCompat.createWithContentUri(Uri.withAppendedPath(Profile.CONTENT_URI, Contacts.Photo.DISPLAY_PHOTO))).build(); + return new Person.Builder().setName(context.getString(R.string.self_display_name)).build(); } void close() { 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 563bbad..2d2b2cf 100644 --- a/src/main/java/com/oasisfeng/nevo/decorators/wechat/WeChatDecorator.java +++ b/src/main/java/com/oasisfeng/nevo/decorators/wechat/WeChatDecorator.java @@ -141,7 +141,7 @@ public class WeChatDecorator extends NevoDecoratorService { else if (channel_id == null) n.setChannelId(CHANNEL_MESSAGE); // WeChat versions targeting O+ have its own channel for message } - MessagingStyle messaging = mMessagingBuilder.buildFromExtender(conversation, evolving); + MessagingStyle messaging = mMessagingBuilder.buildFromExtender(conversation, evolving, title, getArchivedNotifications(evolving.getOriginalKey(), MAX_NUM_ARCHIVED)); // build message from android auto if (messaging == null) // EXTRA_TEXT will be written in buildFromArchive() messaging = mMessagingBuilder.buildFromArchive(conversation, n, title, getArchivedNotifications(evolving.getOriginalKey(), MAX_NUM_ARCHIVED)); if (messaging == null) return; diff --git a/src/main/java/com/oasisfeng/nevo/decorators/wechat/WeChatMessage.java b/src/main/java/com/oasisfeng/nevo/decorators/wechat/WeChatMessage.java index 05b34bc..01d7357 100644 --- a/src/main/java/com/oasisfeng/nevo/decorators/wechat/WeChatMessage.java +++ b/src/main/java/com/oasisfeng/nevo/decorators/wechat/WeChatMessage.java @@ -1,11 +1,14 @@ package com.oasisfeng.nevo.decorators.wechat; import android.app.Notification; +import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; import com.oasisfeng.nevo.decorators.wechat.ConversationManager.Conversation; +import java.util.List; + import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat.MessagingStyle.Message; import androidx.core.app.Person; @@ -31,17 +34,22 @@ class WeChatMessage { static final String SENDER_MESSAGE_SEPARATOR = ": "; private static final String SELF = ""; - static Message[] buildFromCarConversation(final Conversation conversation, final Notification.CarExtender.UnreadConversation convs) { + static Message[] buildFromCarConversation(final Conversation conversation, final Notification.CarExtender.UnreadConversation convs, final List archive) { final String[] car_messages = convs.getMessages(); if (car_messages.length == 0) return new Message[] { buildFromBasicFields(conversation).toMessage() }; // No messages in car conversation final WeChatMessage basic_msg = buildFromBasicFields(conversation); final Message[] messages = new Message[car_messages.length]; + final CharSequence[] tickerArray = new CharSequence[car_messages.length]; + for (int i = archive.size() - 1, diff = archive.size() - car_messages.length; i >= 0 && i >= diff; i--) { + tickerArray[i - diff] = archive.get(i).getNotification().tickerText; + } int end_of_peers = -1; if (! conversation.isGroupChat()) for (end_of_peers = car_messages.length - 1; end_of_peers >= -1; end_of_peers --) if (end_of_peers >= 0 && TextUtils.equals(basic_msg.text, car_messages[end_of_peers])) break; // Find the actual end line which matches basic fields, in case extra lines are sent by self - for (int i = 0, count = car_messages.length; i < count; i ++) - messages[i] = buildFromCarMessage(conversation, car_messages[i], end_of_peers >= 0 && i > end_of_peers).toMessage(); + for (int i = 0, count = car_messages.length; i < count; i ++) { + messages[i] = buildFromCarMessage(conversation, car_messages[i], tickerArray[i], end_of_peers >= 0 && i > end_of_peers).toMessage(); + } return messages; } @@ -130,9 +138,11 @@ private static boolean startsWith(final CharSequence text, final CharSequence ne && TextUtils.regionMatches(text, needle1_length, needle2, 0, needle2_length); } - private static WeChatMessage buildFromCarMessage(final Conversation conversation, final String message, final boolean from_self) { + private static WeChatMessage buildFromCarMessage(final Conversation conversation, final String message, final @Nullable CharSequence ticker, final boolean from_self) { String text = message, sender = null; - final int pos = from_self ? 0 : TextUtils.indexOf(message, SENDER_MESSAGE_SEPARATOR); + int pos; + // parse text + pos = from_self ? 0 : TextUtils.indexOf(message, SENDER_MESSAGE_SEPARATOR); if (pos > 0) { sender = message.substring(0, pos); final boolean title_as_sender = TextUtils.equals(sender, conversation.getTitle()); @@ -141,6 +151,15 @@ private static WeChatMessage buildFromCarMessage(final Conversation conversation if (conversation.isGroupChat() && title_as_sender) sender = SELF; // WeChat incorrectly use group chat title as sender for self-sent messages. } else sender = null; // Not really the sender name, revert the parsing result. } + // parse sender (from ticker) + pos = from_self ? 0 : TextUtils.indexOf(ticker, SENDER_MESSAGE_SEPARATOR); + if (pos > 0) { + sender = ticker.toString().substring(0, pos); + final boolean title_as_sender = TextUtils.equals(sender, conversation.getTitle()); + if (conversation.isGroupChat() || title_as_sender) { // Verify the sender with title for non-group conversation + if (conversation.isGroupChat() && title_as_sender) sender = SELF; // WeChat incorrectly use group chat title as sender for self-sent messages. + } else sender = null; // Not really the sender name, revert the parsing result. + } return new WeChatMessage(conversation, from_self ? SELF : sender, EmojiTranslator.translate(text), 0); }