That fuck shit the fascists are using
1package org.tm.archive.conversation;
2
3import android.app.Activity;
4import android.content.Context;
5import android.content.Intent;
6import android.net.Uri;
7import android.os.Bundle;
8
9import androidx.annotation.MainThread;
10import androidx.annotation.NonNull;
11import androidx.annotation.Nullable;
12
13import org.signal.core.util.logging.Log;
14import org.tm.archive.badges.models.Badge;
15import org.tm.archive.conversation.colors.ChatColors;
16import org.tm.archive.conversation.v2.ConversationActivity;
17import org.tm.archive.database.SignalDatabase;
18import org.tm.archive.database.ThreadTable;
19import org.tm.archive.mediasend.Media;
20import org.tm.archive.mms.SlideFactory;
21import org.tm.archive.recipients.Recipient;
22import org.tm.archive.recipients.RecipientId;
23import org.tm.archive.stickers.StickerLocator;
24import org.tm.archive.wallpaper.ChatWallpaper;
25import org.whispersystems.signalservice.api.util.Preconditions;
26
27import java.util.ArrayList;
28import java.util.Collection;
29import java.util.List;
30import java.util.Objects;
31
32import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
33import io.reactivex.rxjava3.core.Single;
34import io.reactivex.rxjava3.schedulers.Schedulers;
35
36public class ConversationIntents {
37 private static final String TAG = Log.tag(ConversationIntents.class);
38
39 private static final String BUBBLE_AUTHORITY = "bubble";
40 private static final String NOTIFICATION_CUSTOM_SCHEME = "custom";
41 private static final String EXTRA_RECIPIENT = "recipient_id";
42 private static final String EXTRA_THREAD_ID = "thread_id";
43 private static final String EXTRA_TEXT = "draft_text";
44 private static final String EXTRA_MEDIA = "media_list";
45 private static final String EXTRA_STICKER = "sticker_extra";
46 private static final String EXTRA_BORDERLESS = "borderless_extra";
47 private static final String EXTRA_DISTRIBUTION_TYPE = "distribution_type";
48 private static final String EXTRA_STARTING_POSITION = "starting_position";
49 private static final String EXTRA_FIRST_TIME_IN_SELF_CREATED_GROUP = "first_time_in_group";
50 private static final String EXTRA_WITH_SEARCH_OPEN = "with_search_open";
51 private static final String EXTRA_GIFT_BADGE = "gift_badge";
52 private static final String EXTRA_SHARE_DATA_TIMESTAMP = "share_data_timestamp";
53 private static final String EXTRA_CONVERSATION_TYPE = "conversation_type";
54 private static final String INTENT_DATA = "intent_data";
55 private static final String INTENT_TYPE = "intent_type";
56
57 private ConversationIntents() {
58 }
59
60 /**
61 * Create a conversation builder for the given recipientId / threadId. Thread ids are required for CFV2,
62 * so we will resolve the Recipient into a ThreadId if the threadId is invalid (below 0)
63 *
64 * @param context Context for Intent creation
65 * @param recipientId The RecipientId to query the thread ID for if the passed one is invalid.
66 * @param threadId The threadId, or -1L
67 * @return A Single that will return a builder to create the conversation intent.
68 */
69 @MainThread
70 public static @NonNull Single<Builder> createBuilder(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) {
71 if (threadId > 0L) {
72 return Single.just(createBuilderSync(context, recipientId, threadId));
73 } else {
74 return Single.fromCallable(() -> {
75 long newThreadId = SignalDatabase.threads().getOrCreateThreadIdFor(Recipient.resolved(recipientId));
76 return createBuilderSync(context, recipientId, newThreadId);
77 }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
78 }
79 }
80
81 public static @NonNull Builder createPopUpBuilder(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) {
82 return new Builder(context, ConversationPopupActivity.class, recipientId, threadId, ConversationScreenType.POPUP);
83 }
84
85 public static @NonNull Intent createBubbleIntent(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) {
86 return new Builder(context, BubbleConversationActivity.class, recipientId, threadId, ConversationScreenType.BUBBLE).build();
87 }
88
89 /**
90 * Create a Builder for a Conversation Intent. Does not perform a lookup for the thread id if the thread id is < 1. For CFV2, this is
91 * considered an invalid state and will be met with an IllegalArgumentException.
92 *
93 * @param context Context for Intent creation
94 * @param recipientId The recipientId, only used if the threadId is not valid
95 * @param threadId The threadId, required for CFV2.
96 * @return A builder that can be used to create a conversation intent.
97 */
98 public static @NonNull Builder createBuilderSync(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) {
99 Preconditions.checkArgument(threadId > 0, "threadId is invalid");
100 return new Builder(context, ConversationActivity.class, recipientId, threadId, ConversationScreenType.NORMAL);
101 }
102
103 static @Nullable Uri getIntentData(@NonNull Bundle bundle) {
104 return bundle.getParcelable(INTENT_DATA);
105 }
106
107 static @Nullable String getIntentType(@NonNull Bundle bundle) {
108 return bundle.getString(INTENT_TYPE);
109 }
110
111 public static @NonNull Bundle createParentFragmentArguments(@NonNull Intent intent) {
112 Bundle bundle = new Bundle();
113
114 if (intent.getExtras() != null) {
115 bundle.putAll(intent.getExtras());
116 }
117
118 bundle.putParcelable(INTENT_DATA, intent.getData());
119 bundle.putString(INTENT_TYPE, intent.getType());
120
121 return bundle;
122 }
123
124 public static boolean isBubbleIntentUri(@Nullable Uri uri) {
125 return uri != null && Objects.equals(uri.getAuthority(), BUBBLE_AUTHORITY);
126 }
127
128 static boolean isNotificationIntentUri(@Nullable Uri uri) {
129 return uri != null && Objects.equals(uri.getScheme(), NOTIFICATION_CUSTOM_SCHEME);
130 }
131
132 public final static class Args {
133 private final RecipientId recipientId;
134 private final long threadId;
135 private final String draftText;
136 private final Uri draftMedia;
137 private final String draftContentType;
138 private final SlideFactory.MediaType draftMediaType;
139 private final ArrayList<Media> media;
140 private final StickerLocator stickerLocator;
141 private final boolean isBorderless;
142 private final int distributionType;
143 private final int startingPosition;
144 private final boolean firstTimeInSelfCreatedGroup;
145 private final boolean withSearchOpen;
146 private final Badge giftBadge;
147 private final long shareDataTimestamp;
148 private final ConversationScreenType conversationScreenType;
149
150 public static Args from(@NonNull Bundle arguments) {
151 Uri intentDataUri = getIntentData(arguments);
152 if (isBubbleIntentUri(intentDataUri)) {
153 return new Args(RecipientId.from(intentDataUri.getQueryParameter(EXTRA_RECIPIENT)),
154 Long.parseLong(intentDataUri.getQueryParameter(EXTRA_THREAD_ID)),
155 null,
156 null,
157 null,
158 null,
159 null,
160 false,
161 ThreadTable.DistributionTypes.DEFAULT,
162 -1,
163 false,
164 false,
165 null,
166 -1L,
167 ConversationScreenType.BUBBLE);
168 }
169
170 return new Args(RecipientId.from(Objects.requireNonNull(arguments.getString(EXTRA_RECIPIENT))),
171 arguments.getLong(EXTRA_THREAD_ID, -1),
172 arguments.getString(EXTRA_TEXT),
173 ConversationIntents.getIntentData(arguments),
174 ConversationIntents.getIntentType(arguments),
175 arguments.getParcelableArrayList(EXTRA_MEDIA),
176 arguments.getParcelable(EXTRA_STICKER),
177 arguments.getBoolean(EXTRA_BORDERLESS, false),
178 arguments.getInt(EXTRA_DISTRIBUTION_TYPE, ThreadTable.DistributionTypes.DEFAULT),
179 arguments.getInt(EXTRA_STARTING_POSITION, -1),
180 arguments.getBoolean(EXTRA_FIRST_TIME_IN_SELF_CREATED_GROUP, false),
181 arguments.getBoolean(EXTRA_WITH_SEARCH_OPEN, false),
182 arguments.getParcelable(EXTRA_GIFT_BADGE),
183 arguments.getLong(EXTRA_SHARE_DATA_TIMESTAMP, -1L),
184 ConversationScreenType.from(arguments.getInt(EXTRA_CONVERSATION_TYPE, 0)));
185 }
186
187 private Args(@NonNull RecipientId recipientId,
188 long threadId,
189 @Nullable String draftText,
190 @Nullable Uri draftMedia,
191 @Nullable String draftContentType,
192 @Nullable ArrayList<Media> media,
193 @Nullable StickerLocator stickerLocator,
194 boolean isBorderless,
195 int distributionType,
196 int startingPosition,
197 boolean firstTimeInSelfCreatedGroup,
198 boolean withSearchOpen,
199 @Nullable Badge giftBadge,
200 long shareDataTimestamp,
201 @NonNull ConversationScreenType conversationScreenType)
202 {
203 this.recipientId = recipientId;
204 this.threadId = threadId;
205 this.draftText = draftText;
206 this.draftMedia = draftMedia;
207 this.draftContentType = draftContentType;
208 this.media = media;
209 this.stickerLocator = stickerLocator;
210 this.isBorderless = isBorderless;
211 this.distributionType = distributionType;
212 this.startingPosition = startingPosition;
213 this.firstTimeInSelfCreatedGroup = firstTimeInSelfCreatedGroup;
214 this.withSearchOpen = withSearchOpen;
215 this.giftBadge = giftBadge;
216 this.shareDataTimestamp = shareDataTimestamp;
217 this.conversationScreenType = conversationScreenType;
218 this.draftMediaType = SlideFactory.MediaType.from(draftContentType);
219 }
220
221 public @NonNull RecipientId getRecipientId() {
222 return recipientId;
223 }
224
225 public long getThreadId() {
226 return threadId;
227 }
228
229 public @Nullable String getDraftText() {
230 return draftText;
231 }
232
233 public @Nullable Uri getDraftMedia() {
234 return draftMedia;
235 }
236
237 public @Nullable String getDraftContentType() {
238 return draftContentType;
239 }
240
241 public @Nullable SlideFactory.MediaType getDraftMediaType() {
242 return draftMediaType;
243 }
244
245 public @Nullable ArrayList<Media> getMedia() {
246 return media;
247 }
248
249 public @Nullable StickerLocator getStickerLocator() {
250 return stickerLocator;
251 }
252
253 public int getDistributionType() {
254 return distributionType;
255 }
256
257 public int getStartingPosition() {
258 return startingPosition;
259 }
260
261 public boolean isBorderless() {
262 return isBorderless;
263 }
264
265 public boolean isFirstTimeInSelfCreatedGroup() {
266 return firstTimeInSelfCreatedGroup;
267 }
268
269 public @Nullable ChatWallpaper getWallpaper() {
270 return Recipient.resolved(recipientId).getWallpaper();
271 }
272
273 public @NonNull ChatColors getChatColors() {
274 return Recipient.resolved(recipientId).getChatColors();
275 }
276
277 public boolean isWithSearchOpen() {
278 return withSearchOpen;
279 }
280
281 public @Nullable Badge getGiftBadge() {
282 return giftBadge;
283 }
284
285 public long getShareDataTimestamp() {
286 return shareDataTimestamp;
287 }
288
289 public @NonNull ConversationScreenType getConversationScreenType() {
290 return conversationScreenType;
291 }
292
293 public boolean canInitializeFromDatabase() {
294 return draftText == null && (draftMedia == null || ConversationIntents.isBubbleIntentUri(draftMedia) || ConversationIntents.isNotificationIntentUri(draftMedia)) && draftMediaType == null;
295 }
296 }
297
298 public final static class Builder {
299 private final Context context;
300 private final Class<? extends Activity> conversationActivityClass;
301 private final RecipientId recipientId;
302 private final long threadId;
303 private final ConversationScreenType conversationScreenType;
304
305 private String draftText;
306 private List<Media> media;
307 private StickerLocator stickerLocator;
308 private boolean isBorderless;
309 private int distributionType = ThreadTable.DistributionTypes.DEFAULT;
310 private int startingPosition = -1;
311 private Uri dataUri;
312 private String dataType;
313 private boolean firstTimeInSelfCreatedGroup;
314 private boolean withSearchOpen;
315 private Badge giftBadge;
316 private long shareDataTimestamp = -1L;
317
318 private Builder(@NonNull Context context,
319 @NonNull Class<? extends Activity> conversationActivityClass,
320 @NonNull RecipientId recipientId,
321 long threadId,
322 @NonNull ConversationScreenType conversationScreenType)
323 {
324 this.context = context;
325 this.conversationActivityClass = conversationActivityClass;
326 this.recipientId = recipientId;
327 this.threadId = checkThreadId(threadId);
328 this.conversationScreenType = conversationScreenType;
329 }
330
331 public @NonNull Builder withDraftText(@Nullable String draftText) {
332 this.draftText = draftText;
333 return this;
334 }
335
336 public @NonNull Builder withMedia(@Nullable Collection<Media> media) {
337 this.media = media != null ? new ArrayList<>(media) : null;
338 return this;
339 }
340
341 public @NonNull Builder withStickerLocator(@Nullable StickerLocator stickerLocator) {
342 this.stickerLocator = stickerLocator;
343 return this;
344 }
345
346 public @NonNull Builder asBorderless(boolean isBorderless) {
347 this.isBorderless = isBorderless;
348 return this;
349 }
350
351 public @NonNull Builder withDistributionType(int distributionType) {
352 this.distributionType = distributionType;
353 return this;
354 }
355
356 public @NonNull Builder withStartingPosition(int startingPosition) {
357 this.startingPosition = startingPosition;
358 return this;
359 }
360
361 public @NonNull Builder withDataUri(@Nullable Uri dataUri) {
362 this.dataUri = dataUri;
363 return this;
364 }
365
366 public @NonNull Builder withDataType(@Nullable String dataType) {
367 this.dataType = dataType;
368 return this;
369 }
370
371 public @NonNull Builder withSearchOpen(boolean withSearchOpen) {
372 this.withSearchOpen = withSearchOpen;
373 return this;
374 }
375
376 public Builder firstTimeInSelfCreatedGroup() {
377 this.firstTimeInSelfCreatedGroup = true;
378 return this;
379 }
380
381 public Builder withGiftBadge(@NonNull Badge badge) {
382 this.giftBadge = badge;
383 return this;
384 }
385
386 public Builder withShareDataTimestamp(long timestamp) {
387 this.shareDataTimestamp = timestamp;
388 return this;
389 }
390
391 public @NonNull Intent build() {
392 if (stickerLocator != null && media != null) {
393 throw new IllegalStateException("Cannot have both sticker and media array");
394 }
395
396 Intent intent = new Intent(context, conversationActivityClass);
397
398 intent.setAction(Intent.ACTION_DEFAULT);
399
400 if (conversationScreenType.isInBubble()) {
401 intent.setData(new Uri.Builder().authority(BUBBLE_AUTHORITY)
402 .appendQueryParameter(EXTRA_RECIPIENT, recipientId.serialize())
403 .appendQueryParameter(EXTRA_THREAD_ID, String.valueOf(threadId))
404 .build());
405
406 return intent;
407 }
408
409 intent.putExtra(EXTRA_RECIPIENT, recipientId.serialize());
410 intent.putExtra(EXTRA_THREAD_ID, threadId);
411 intent.putExtra(EXTRA_DISTRIBUTION_TYPE, distributionType);
412 intent.putExtra(EXTRA_STARTING_POSITION, startingPosition);
413 intent.putExtra(EXTRA_BORDERLESS, isBorderless);
414 intent.putExtra(EXTRA_FIRST_TIME_IN_SELF_CREATED_GROUP, firstTimeInSelfCreatedGroup);
415 intent.putExtra(EXTRA_WITH_SEARCH_OPEN, withSearchOpen);
416 intent.putExtra(EXTRA_GIFT_BADGE, giftBadge);
417 intent.putExtra(EXTRA_SHARE_DATA_TIMESTAMP, shareDataTimestamp);
418 intent.putExtra(EXTRA_CONVERSATION_TYPE, conversationScreenType.code);
419
420 if (draftText != null) {
421 intent.putExtra(EXTRA_TEXT, draftText);
422 }
423
424 if (media != null) {
425 intent.putParcelableArrayListExtra(EXTRA_MEDIA, new ArrayList<>(media));
426 }
427
428 if (stickerLocator != null) {
429 intent.putExtra(EXTRA_STICKER, stickerLocator);
430 }
431
432 if (dataUri != null && dataType != null) {
433 intent.setDataAndType(dataUri, dataType);
434 } else if (dataUri != null) {
435 intent.setData(dataUri);
436 } else if (dataType != null) {
437 intent.setType(dataType);
438 }
439
440 Bundle args = ConversationIntents.createParentFragmentArguments(intent);
441
442 return intent.putExtras(args);
443 }
444 }
445
446 public enum ConversationScreenType {
447 NORMAL(0),
448 BUBBLE(1),
449 POPUP(2);
450
451 private final int code;
452
453 ConversationScreenType(int code) {
454 this.code = code;
455 }
456
457 public boolean isInBubble() {
458 return Objects.equals(this, BUBBLE);
459 }
460
461 public boolean isInPopup() {
462 return Objects.equals(this, POPUP);
463 }
464
465 public boolean isNormal() {
466 return Objects.equals(this, NORMAL);
467 }
468
469 private static @NonNull ConversationScreenType from(int code) {
470 for (ConversationScreenType type : values()) {
471 if (type.code == code) {
472 return type;
473 }
474 }
475
476 return NORMAL;
477 }
478 }
479
480 private static long checkThreadId(long threadId) {
481 if (threadId < 0) {
482 throw new IllegalArgumentException("ThreadId is a required field in CFV2");
483 } else {
484 return threadId;
485 }
486 }
487}