Skip to content

Commit 7a11fe8

Browse files
committed
UPDATE: Rework the conversation participants management, with new ConversationManager introduced to replace Persons.
1 parent 20e1d6f commit 7a11fe8

File tree

4 files changed

+155
-105
lines changed

4 files changed

+155
-105
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.oasisfeng.nevo.decorators.wechat;
2+
3+
import android.text.TextUtils;
4+
import android.util.ArrayMap;
5+
import android.util.SparseArray;
6+
7+
import java.lang.annotation.Retention;
8+
import java.lang.annotation.RetentionPolicy;
9+
import java.util.Map;
10+
11+
import androidx.annotation.IntDef;
12+
import androidx.annotation.Nullable;
13+
import androidx.core.app.Person;
14+
15+
import static java.util.Objects.requireNonNull;
16+
17+
/**
18+
* Manage all conversations.
19+
*
20+
* Created by Oasis on 2019-4-11.
21+
*/
22+
class ConversationManager {
23+
24+
private static final Person SENDER_PLACEHOLDER = new Person.Builder().setName(" ").build(); // Cannot be empty string, or it will be treated as null.
25+
26+
static class Conversation {
27+
28+
static final int TYPE_UNKNOWN = 0;
29+
static final int TYPE_DIRECT_MESSAGE = 1;
30+
static final int TYPE_GROUP_CHAT = 2;
31+
static final int TYPE_BOT_MESSAGE = 3;
32+
@IntDef({ TYPE_UNKNOWN, TYPE_DIRECT_MESSAGE, TYPE_GROUP_CHAT, TYPE_BOT_MESSAGE }) @Retention(RetentionPolicy.SOURCE) @interface ConversationType {}
33+
34+
final int id;
35+
@Nullable String key;
36+
Person sender = SENDER_PLACEHOLDER;
37+
38+
int getType() { return mType; }
39+
40+
void setType(final int type) {
41+
if (type == this.mType) return;
42+
this.mType = type;
43+
sender = type == TYPE_UNKNOWN || type == TYPE_GROUP_CHAT ? SENDER_PLACEHOLDER
44+
: sender.toBuilder().setKey(key).setBot(type == TYPE_BOT_MESSAGE).build(); // Always set key as it may change
45+
if (type != TYPE_GROUP_CHAT) mParticipants.clear();
46+
}
47+
48+
CharSequence getTitle() { return mTitle; }
49+
50+
void setTitle(final CharSequence title) {
51+
if (TextUtils.equals(title, mTitle)) return;
52+
mTitle = title;
53+
sender = sender.toBuilder().setName(title).build(); // Rename the sender
54+
}
55+
56+
Person getGroupParticipant(final String key, final String name) {
57+
if (mType != TYPE_GROUP_CHAT) throw new IllegalStateException("Not group chat");
58+
Person.Builder builder = null;
59+
Person participant = mParticipants.get(key);
60+
if (participant == null) builder = new Person.Builder().setKey(key);
61+
else if (! TextUtils.equals(name, requireNonNull(participant.getUri()).substring(SCHEME_ORIGINAL_NAME.length()))) // Original name is changed
62+
builder = participant.toBuilder();
63+
if (builder != null) mParticipants.put(key, participant = builder.setUri(SCHEME_ORIGINAL_NAME + name).setName(EmojiTranslator.translate(name)).build());
64+
return participant;
65+
}
66+
67+
private static final String SCHEME_ORIGINAL_NAME = "ON:";
68+
69+
Conversation(final int id) { this.id = id; }
70+
71+
@ConversationType private int mType;
72+
private CharSequence mTitle;
73+
private final Map<String, Person> mParticipants = new ArrayMap<>();
74+
}
75+
76+
Conversation getConversation(final int id) {
77+
Conversation conversation = mConversations.get(id);
78+
if (conversation == null) mConversations.put(id, conversation = new Conversation(id));
79+
return conversation;
80+
}
81+
82+
private final SparseArray<Conversation> mConversations = new SparseArray<>();
83+
}

src/main/java/com/oasisfeng/nevo/decorators/wechat/MessagingBuilder.java

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
import android.net.Uri;
1212
import android.os.Bundle;
1313
import android.os.Parcelable;
14+
import android.provider.ContactsContract;
1415
import android.service.notification.StatusBarNotification;
1516
import android.text.TextUtils;
1617
import android.util.ArrayMap;
1718
import android.util.Log;
1819
import android.util.LongSparseArray;
1920

21+
import com.oasisfeng.nevo.decorators.wechat.ConversationManager.Conversation;
2022
import com.oasisfeng.nevo.sdk.MutableNotification;
2123
import com.oasisfeng.nevo.sdk.MutableStatusBarNotification;
2224

@@ -37,17 +39,17 @@
3739
import static android.os.Build.VERSION.SDK_INT;
3840
import static android.os.Build.VERSION_CODES.N;
3941
import static android.os.Build.VERSION_CODES.P;
42+
import static com.oasisfeng.nevo.decorators.wechat.ConversationManager.Conversation.TYPE_GROUP_CHAT;
4043
import static com.oasisfeng.nevo.decorators.wechat.WeChatDecorator.SENDER_MESSAGE_SEPARATOR;
4144

4245
/**
4346
* Build the modernized {@link MessagingStyle} for WeChat conversation.
4447
*
4548
* Refactored by Oasis on 2018-8-9.
4649
*/
47-
public class MessagingBuilder {
50+
class MessagingBuilder {
4851

4952
private static final int MAX_NUM_HISTORICAL_LINES = 10;
50-
private static final Person SENDER_PLACEHOLDER = new Person.Builder().setName(" ").build(); // Cannot be empty string, or it will be treated as null.
5153

5254
private static final String ACTION_REPLY = "REPLY";
5355
private static final String SCHEME_KEY = "key";
@@ -67,8 +69,9 @@ public class MessagingBuilder {
6769
private static final String KEY_ON_READ = "on_read";
6870
private static final String KEY_PARTICIPANTS = "participants";
6971
private static final String KEY_TIMESTAMP = "timestamp";
72+
private static final String KEY_USERNAME = "key_username";
7073

71-
@Nullable MessagingStyle buildFromArchive(final Notification n, final CharSequence title, final boolean group_chat, final List<StatusBarNotification> archive) {
74+
@Nullable MessagingStyle buildFromArchive(final Conversation conversation, final Notification n, final CharSequence title, final List<StatusBarNotification> archive) {
7275
// Chat history in big content view
7376
if (archive.isEmpty()) {
7477
Log.d(TAG, "No history");
@@ -114,30 +117,31 @@ public class MessagingBuilder {
114117

115118
n.extras.putCharSequence(EXTRA_TEXT, text); // Latest message text for collapsed layout.
116119

117-
final MessagingStyle messaging = new MessagingStyle(mPersons.getSelf().toPerson());
120+
final MessagingStyle messaging = new MessagingStyle(mUserSelf);
118121
final boolean sender_inline = num_lines_with_colon == lines.size();
119122
for (int i = 0, size = lines.size(); i < size; i++) // All lines have colon in text
120-
messaging.addMessage(buildMessage(lines.keyAt(i), n.tickerText, title, lines.valueAt(i), sender_inline ? null : title.toString(), group_chat));
123+
messaging.addMessage(buildMessage(conversation, lines.keyAt(i), n.tickerText, lines.valueAt(i), sender_inline ? null : title.toString(), null));
121124
return messaging;
122125
}
123126

124-
@Nullable MessagingStyle buildFromExtender(final MutableStatusBarNotification sbn, final CharSequence title, final boolean is_group) {
127+
@Nullable MessagingStyle buildFromExtender(final Conversation conversation, final MutableStatusBarNotification sbn) {
125128
final MutableNotification n = sbn.getNotification();
126129
final Bundle car_extender = n.extras.getBundle(EXTRA_CAR_EXTENDER);
127130
if (car_extender == null) return null;
128-
final Bundle conversation = car_extender.getBundle(EXTRA_CONVERSATION);
129-
if (conversation == null) {
131+
final Bundle convs = car_extender.getBundle(EXTRA_CONVERSATION);
132+
if (convs == null) {
130133
Log.w(TAG, EXTRA_CONVERSATION + " is missing");
131134
return null;
132135
}
133-
final Parcelable[] parcelable_messages = conversation.getParcelableArray(KEY_MESSAGES);
136+
final Parcelable[] parcelable_messages = convs.getParcelableArray(KEY_MESSAGES);
134137
if (parcelable_messages == null) {
135138
Log.w(TAG, KEY_MESSAGES + " is missing");
136139
return null;
137140
}
138-
final MessagingStyle messaging = new MessagingStyle(mPersons.getSelf().toPerson());
141+
final PendingIntent on_reply = convs.getParcelable(KEY_ON_REPLY);
142+
final MessagingStyle messaging = new MessagingStyle(mUserSelf);
139143
if (parcelable_messages.length == 0) { // When only one message in this conversation
140-
final Message message = buildMessage(n.when, n.tickerText, title, n.extras.getCharSequence(EXTRA_TEXT), null, is_group);
144+
final Message message = buildMessage(conversation, n.when, n.tickerText, n.extras.getCharSequence(EXTRA_TEXT), null, on_reply);
141145
messaging.addMessage(message);
142146
} else for (int i = 0, num_messages = parcelable_messages.length; i < num_messages; i ++) {
143147
final Parcelable parcelable = parcelable_messages[i];
@@ -147,20 +151,20 @@ public class MessagingBuilder {
147151
if (text == null) continue;
148152
final long timestamp = car_message.getLong(KEY_TIMESTAMP);
149153
final @Nullable String author = car_message.getString(KEY_AUTHOR); // Apparently always null (not yet implemented by WeChat)
150-
final Message message = buildMessage(timestamp, i == num_messages - 1 ? n.tickerText : null, title, text, author, is_group);
154+
final Message message = buildMessage(conversation, timestamp, i == num_messages - 1 ? n.tickerText : null, text, author, on_reply);
151155
messaging.addMessage(message);
152156
}
153157

154-
final PendingIntent on_read = conversation.getParcelable(KEY_ON_READ);
158+
final PendingIntent on_read = convs.getParcelable(KEY_ON_READ);
155159
if (on_read != null) mMarkReadPendingIntents.put(sbn.getKey(), on_read); // Mapped by evolved key,
156160

157-
final PendingIntent on_reply; final RemoteInput remote_input;
158-
if (SDK_INT >= N && (on_reply = conversation.getParcelable(KEY_ON_REPLY)) != null && (remote_input = conversation.getParcelable(KEY_REMOTE_INPUT)) != null) {
161+
final RemoteInput remote_input;
162+
if (SDK_INT >= N && on_reply != null && (remote_input = convs.getParcelable(KEY_REMOTE_INPUT)) != null) {
159163
final CharSequence[] input_history = n.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY);
160164
final PendingIntent proxy = proxyDirectReply(sbn, on_reply, remote_input, input_history);
161165
final RemoteInput.Builder tweaked = new RemoteInput.Builder(remote_input.getResultKey()).addExtras(remote_input.getExtras())
162166
.setAllowFreeFormInput(true).setChoices(SmartReply.generateChoices(messaging));
163-
final String[] participants = conversation.getStringArray(KEY_PARTICIPANTS);
167+
final String[] participants = convs.getStringArray(KEY_PARTICIPANTS);
164168
if (participants != null && participants.length > 0) {
165169
final StringBuilder label = new StringBuilder();
166170
for (final String participant : participants) label.append(',').append(participant);
@@ -175,22 +179,33 @@ public class MessagingBuilder {
175179
return messaging;
176180
}
177181

178-
private Message buildMessage(final long when, final @Nullable CharSequence ticker, final CharSequence title,
179-
final CharSequence text, @Nullable String sender, final boolean group_chat) {
182+
private Message buildMessage(final Conversation conversation, final long when, final @Nullable CharSequence ticker,
183+
final CharSequence text, @Nullable String sender, final @Nullable PendingIntent on_reply) {
180184
CharSequence actual_text = text;
181185
if (sender == null) {
182186
sender = extractSenderFromText(text);
183187
if (sender != null) {
184188
actual_text = text.subSequence(sender.length() + SENDER_MESSAGE_SEPARATOR.length(), text.length());
185-
if (TextUtils.equals(title, sender)) sender = null; // In this case, the actual sender is user itself.
189+
if (TextUtils.equals(conversation.getTitle(), sender)) sender = null; // In this case, the actual sender is user itself.
186190
}
187191
}
188192
actual_text = EmojiTranslator.translate(actual_text);
189-
if (group_chat) {
193+
194+
if (conversation.key == null) try {
195+
if (on_reply != null) on_reply.send(mContext, 0, null, (p, intent, r, d, b) -> {
196+
conversation.key = intent.getStringExtra(KEY_USERNAME); // setType() below will trigger rebuilding of conversation sender.
197+
conversation.setType(conversation.key.endsWith("@chatroom") ? TYPE_GROUP_CHAT
198+
: conversation.key.startsWith("gh_") ? Conversation.TYPE_BOT_MESSAGE : Conversation.TYPE_DIRECT_MESSAGE);
199+
}, null);
200+
} catch (final PendingIntent.CanceledException e) {
201+
Log.e(TAG, "Error parsing reply intent.", e);
202+
}
203+
204+
if (conversation.getType() == TYPE_GROUP_CHAT) {
190205
final String ticker_sender = ticker != null ? extractSenderFromText(ticker) : null; // Group nick is used in ticker while original nick in sender.
191-
final Person person = sender != null ? mPersons.get(title.toString(), sender, ticker_sender != null ? ticker_sender : sender).toPerson() : null;
206+
final Person person = sender == null ? null : conversation.getGroupParticipant(sender, ticker_sender != null ? ticker_sender : sender);
192207
return new Message(actual_text, when, person);
193-
} else return new Message(actual_text, when, SENDER_PLACEHOLDER);
208+
} else return new Message(actual_text, when, conversation.sender);
194209
}
195210

196211
private static @Nullable String extractSenderFromText(final CharSequence text) {
@@ -283,7 +298,8 @@ interface Controller { void recastNotification(String key, Bundle addition); }
283298
MessagingBuilder(final Context context, final Controller controller) {
284299
mContext = context;
285300
mController = controller;
286-
mPersons = new Persons(context.getString(R.string.self_display_name));
301+
final Uri profile_lookup = ContactsContract.Contacts.getLookupUri(context.getContentResolver(), ContactsContract.Profile.CONTENT_URI);
302+
mUserSelf = new Person.Builder().setUri(profile_lookup != null ? profile_lookup.toString() : null).setName(context.getString(R.string.self_display_name)).build();
287303

288304
final IntentFilter filter = new IntentFilter(ACTION_REPLY);
289305
filter.addDataScheme(SCHEME_KEY);
@@ -296,7 +312,7 @@ void close() {
296312

297313
private final Context mContext;
298314
private final Controller mController;
299-
private final Persons mPersons;
315+
private final Person mUserSelf;
300316
private final Map<String/* evolved key */, PendingIntent> mMarkReadPendingIntents = new ArrayMap<>();
301317
private static final String TAG = WeChatDecorator.TAG;
302318
}

src/main/java/com/oasisfeng/nevo/decorators/wechat/Persons.java

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)