From 4b3e7c885889fec754c44c419f142520820d22ec Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Sun, 28 Feb 2021 10:37:27 -0500 Subject: [PATCH 01/24] Remove SELF_LOCK from LiveRecipientCache. Had the potential to deadlock if accessed inside of a database transaction. --- .../securesms/recipients/LiveRecipientCache.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java index 39f71cbfb9a..de3c95253ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java @@ -9,6 +9,7 @@ import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.MissingRecipientException; @@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.util.LRUCache; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.signalservice.api.SignalSessionLock; import java.util.ArrayList; import java.util.Collection; @@ -30,14 +32,11 @@ public final class LiveRecipientCache { private static final int CACHE_MAX = 1000; private static final int CACHE_WARM_MAX = 500; - private static final Object SELF_LOCK = new Object(); - private final Context context; private final RecipientDatabase recipientDatabase; private final Map recipients; private final LiveRecipient unknown; - @GuardedBy("SELF_LOCK") private RecipientId localRecipientId; private boolean warmedUp; @@ -111,7 +110,7 @@ public synchronized void addToCache(@NonNull Collection newRecipients } @NonNull Recipient getSelf() { - synchronized (SELF_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { if (localRecipientId == null) { UUID localUuid = TextSecurePreferences.getLocalUuid(context); String localE164 = TextSecurePreferences.getLocalNumber(context); From 38caf1e2b7a2f46af71b72ab7754f71cfebbd7d0 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 1 Mar 2021 09:50:31 -0500 Subject: [PATCH 02/24] Update ShortcutBadger to 1.1.22 --- app/build.gradle | 8 +------- app/witness-verifications.gradle | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 09f1b772dcc..1a3a3502d83 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,12 +16,6 @@ repositories { includeGroupByRegex "com\\.github\\.chrisbanes.*" } } - maven { - url "https://raw.github.com/signalapp/maven/master/shortcutbadger/releases/" - content { - includeGroupByRegex "me\\.leolin.*" - } - } maven { url "https://raw.github.com/signalapp/maven/master/circular-progress-button/releases/" content { @@ -342,7 +336,7 @@ dependencies { implementation 'org.signal:ringrtc-android:2.9.2' - implementation "me.leolin:ShortcutBadger:1.1.16" + implementation "me.leolin:ShortcutBadger:1.1.22" implementation 'se.emilsjolander:stickylistheaders:2.7.0' implementation 'com.jpardogo.materialtabstrip:library:1.0.9' implementation 'org.apache.httpcomponents:httpclient-android:4.3.5' diff --git a/app/witness-verifications.gradle b/app/witness-verifications.gradle index 4c6ef0ee142..327a500dbb9 100644 --- a/app/witness-verifications.gradle +++ b/app/witness-verifications.gradle @@ -378,8 +378,8 @@ dependencyVerification { ['javax.inject:javax.inject:1', '91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff'], - ['me.leolin:ShortcutBadger:1.1.16', - 'e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774'], + ['me.leolin:ShortcutBadger:1.1.22', + 'cd1610dc48c5699229502187fdc3b5c5433870d5aab159332121b1f8c1dad8c3'], ['mobi.upod:time-duration-picker:1.1.3', 'db469ce0f48dd96b892eac424ed76870e54bf00fe0a28cdcddfbe5f2a226a0e1'], From dc9b8169c097621a71c1b552902cf9d7c7bdd622 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Mon, 1 Mar 2021 15:44:33 -0500 Subject: [PATCH 03/24] Make thread related utility methods available for use in all modules. --- .../securesms/DeviceActivity.java | 4 +- .../securesms/VerifyIdentityActivity.java | 3 +- .../securesms/audio/AudioRecorder.java | 6 +- .../securesms/audio/AudioWaveForm.java | 18 ++-- .../components/ExpirationTimerView.java | 6 +- .../securesms/components/InputPanel.java | 7 +- .../components/TypingStatusRepository.java | 7 +- .../components/TypingStatusSender.java | 17 ++-- .../components/camera/CameraView.java | 7 +- .../components/emoji/EmojiProvider.java | 6 +- .../emoji/parsing/EmojiPageBitmap.java | 3 +- .../webrtc/CallParticipantView.java | 6 +- .../conversation/ConversationActivity.java | 5 +- .../conversation/ConversationAdapter.java | 4 +- .../ConversationSearchViewModel.java | 4 +- .../ConversationListViewModel.java | 4 +- .../database/model/UpdateDescription.java | 4 +- .../groups/GroupsV2ProcessingLock.java | 4 +- .../ui/managegroup/ManageGroupViewModel.java | 4 +- .../securesms/jobmanager/JobManager.java | 4 +- .../linkpreview/LinkPreviewViewModel.java | 6 +- .../CameraContactSelectionViewModel.java | 8 +- .../mediasend/MediaSendViewModel.java | 7 +- .../megaphone/ClientDeprecatedActivity.java | 4 +- .../MessageHeaderViewHolder.java | 6 +- .../securesms/mms/AttachmentManager.java | 4 +- .../manage/UsernameEditViewModel.java | 6 +- .../any/ReactWithAnyEmojiRepository.java | 4 +- .../securesms/recipients/LiveRecipient.java | 14 +-- .../bottomsheet/RecipientDialogViewModel.java | 4 +- .../ManageRecipientViewModel.java | 6 +- .../revealable/ViewOnceMessageViewModel.java | 4 +- .../ActiveCallActionProcessorDelegate.java | 4 +- .../service/webrtc/WebRtcVideoUtil.java | 6 +- .../securesms/shakereport/ShakeToReport.java | 5 +- .../securesms/util/AppForegroundObserver.java | 4 +- .../securesms/util/AsynchronousCallback.java | 6 +- .../securesms/util/BitmapUtil.java | 3 +- .../securesms/util/CachedInflater.java | 3 +- .../securesms/util/SignalProxyUtil.java | 9 +- .../org/thoughtcrime/securesms/util/Util.java | 91 ------------------- .../securesms/util/concurrent/SimpleTask.java | 6 +- .../util/views/SimpleProgressDialog.java | 10 +- .../GroupsV2UpdateMessageProducerTest.java | 3 +- .../database/model/UpdateDescriptionTest.java | 4 +- .../securesms/testutil/MainThreadUtil.java | 12 +-- .../java/org/signal/core/util/ThreadUtil.java | 90 ++++++++++++++++++ 47 files changed, 228 insertions(+), 224 deletions(-) create mode 100644 core-util/src/main/java/org/signal/core/util/ThreadUtil.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java b/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java index 28ef89f9eba..b7b6a481114 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java @@ -18,6 +18,7 @@ import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; @@ -28,7 +29,6 @@ import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.InvalidKeyException; @@ -123,7 +123,7 @@ public void onClick(View v) { @Override public void onQrDataFound(final String data) { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { ((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50); Uri uri = Uri.parse(data); deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this); diff --git a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java index e864f636d1d..435e474f014 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -60,6 +60,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.color.MaterialColor; @@ -185,7 +186,7 @@ public boolean onOptionsItemSelected(MenuItem item) { @Override public void onQrDataFound(final String data) { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { ((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50); getSupportFragmentManager().popBackStack(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java index f2c59959534..caed6f337d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioRecorder.java @@ -8,11 +8,11 @@ import androidx.annotation.NonNull; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.util.MediaUtil; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.whispersystems.libsignal.util.Pair; @@ -90,10 +90,10 @@ public void startRecording() { } private void sendToFuture(final SettableFuture future, final Exception exception) { - Util.runOnMain(() -> future.setException(exception)); + ThreadUtil.runOnMain(() -> future.setException(exception)); } private void sendToFuture(final SettableFuture future, final T result) { - Util.runOnMain(() -> future.set(result)); + ThreadUtil.runOnMain(() -> future.set(result)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java index 2a3814e523c..3e32b7e6e3c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java +++ b/app/src/main/java/org/thoughtcrime/securesms/audio/AudioWaveForm.java @@ -16,6 +16,7 @@ import com.google.protobuf.ByteString; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.attachments.Attachment; @@ -26,7 +27,6 @@ import org.thoughtcrime.securesms.media.DecryptableUriMediaInput; import org.thoughtcrime.securesms.media.MediaInput; import org.thoughtcrime.securesms.mms.AudioSlide; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.SerialExecutor; import java.io.IOException; @@ -61,13 +61,13 @@ public void getWaveForm(@NonNull Consumer onSuccess, @NonNull Run if (uri == null) { Log.w(TAG, "No uri"); - Util.runOnMain(onFailure); + ThreadUtil.runOnMain(onFailure); return; } if (!(attachment instanceof DatabaseAttachment)) { Log.i(TAG, "Not yet in database"); - Util.runOnMain(onFailure); + ThreadUtil.runOnMain(onFailure); return; } @@ -75,7 +75,7 @@ public void getWaveForm(@NonNull Consumer onSuccess, @NonNull Run AudioFileInfo cached = WAVE_FORM_CACHE.get(cacheKey); if (cached != null) { Log.i(TAG, "Loaded wave form from cache " + cacheKey); - Util.runOnMain(() -> onSuccess.accept(cached)); + ThreadUtil.runOnMain(() -> onSuccess.accept(cached)); return; } @@ -83,7 +83,7 @@ public void getWaveForm(@NonNull Consumer onSuccess, @NonNull Run AudioFileInfo cachedInExecutor = WAVE_FORM_CACHE.get(cacheKey); if (cachedInExecutor != null) { Log.i(TAG, "Loaded wave form from cache inside executor" + cacheKey); - Util.runOnMain(() -> onSuccess.accept(cachedInExecutor)); + ThreadUtil.runOnMain(() -> onSuccess.accept(cachedInExecutor)); return; } @@ -92,14 +92,14 @@ public void getWaveForm(@NonNull Consumer onSuccess, @NonNull Run AudioFileInfo audioFileInfo = AudioFileInfo.fromDatabaseProtobuf(audioHash.getAudioWaveForm()); if (audioFileInfo.waveForm.length == 0) { Log.w(TAG, "Recovering from a wave form generation error " + cacheKey); - Util.runOnMain(onFailure); + ThreadUtil.runOnMain(onFailure); return; } else if (audioFileInfo.waveForm.length != BAR_COUNT) { Log.w(TAG, "Wave form from database does not match bar count, regenerating " + cacheKey); } else { WAVE_FORM_CACHE.put(cacheKey, audioFileInfo); Log.i(TAG, "Loaded wave form from DB " + cacheKey); - Util.runOnMain(() -> onSuccess.accept(audioFileInfo)); + ThreadUtil.runOnMain(() -> onSuccess.accept(audioFileInfo)); return; } } @@ -120,10 +120,10 @@ public void getWaveForm(@NonNull Consumer onSuccess, @NonNull Run attachmentDatabase.writeAudioHash(dbAttachment.getAttachmentId(), fileInfo.toDatabaseProtobuf()); WAVE_FORM_CACHE.put(cacheKey, fileInfo); - Util.runOnMain(() -> onSuccess.accept(fileInfo)); + ThreadUtil.runOnMain(() -> onSuccess.accept(fileInfo)); } catch (Throwable e) { Log.w(TAG, "Failed to create audio wave form for " + cacheKey, e); - Util.runOnMain(onFailure); + ThreadUtil.runOnMain(onFailure); } }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ExpirationTimerView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ExpirationTimerView.java index d52b8916236..1439fefc7d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ExpirationTimerView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ExpirationTimerView.java @@ -6,8 +6,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.util.Util; import java.lang.ref.WeakReference; import java.util.concurrent.TimeUnit; @@ -67,7 +67,7 @@ public void startAnimation() { else stopped = false; } - Util.runOnMainDelayed(new AnimationUpdateRunnable(this), calculateAnimationDelay(this.startedAt, this.expiresIn)); + ThreadUtil.runOnMainDelayed(new AnimationUpdateRunnable(this), calculateAnimationDelay(this.startedAt, this.expiresIn)); } public void stopAnimation() { @@ -116,7 +116,7 @@ public void run() { } } - Util.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn)); + ThreadUtil.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java index b937a98beb0..0c1d0d4687c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java @@ -22,10 +22,10 @@ import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.animation.AnimationCompleteListener; @@ -42,7 +42,6 @@ import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; @@ -501,7 +500,7 @@ public void display() { this.startTime = System.currentTimeMillis(); this.recordTimeView.setText(DateUtils.formatElapsedTime(0)); ViewUtil.fadeIn(this.recordTimeView, FADE_TIME); - Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1)); + ThreadUtil.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1)); microphone.setVisibility(View.VISIBLE); microphone.startAnimation(pulseAnimation()); } @@ -527,7 +526,7 @@ public void run() { onLimitHit.run(); } else { recordTimeView.setText(DateUtils.formatElapsedTime(elapsedSeconds)); - Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1)); + ThreadUtil.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1)); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusRepository.java b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusRepository.java index 433456eb7fe..4fa1b5e17ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusRepository.java @@ -10,6 +10,7 @@ import com.annimon.stream.Collectors; import com.annimon.stream.Stream; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.Util; @@ -58,11 +59,11 @@ public synchronized void onTypingStarted(@NonNull Context context, long threadId Runnable timer = timers.get(typist); if (timer != null) { - Util.cancelRunnableOnMain(timer); + ThreadUtil.cancelRunnableOnMain(timer); } timer = () -> onTypingStopped(context, threadId, author, device, false); - Util.runOnMainDelayed(timer, RECIPIENT_TYPING_TIMEOUT); + ThreadUtil.runOnMainDelayed(timer, RECIPIENT_TYPING_TIMEOUT); timers.put(typist, timer); } @@ -85,7 +86,7 @@ public synchronized void onTypingStopped(@NonNull Context context, long threadId Runnable timer = timers.get(typist); if (timer != null) { - Util.cancelRunnableOnMain(timer); + ThreadUtil.cancelRunnableOnMain(timer); timers.remove(typist); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java index 5dab5b8ec35..25abdca93ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -1,11 +1,8 @@ package org.thoughtcrime.securesms.components; import android.annotation.SuppressLint; -import android.app.Application; -import android.content.Context; - -import androidx.annotation.NonNull; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.TypingSendJob; import org.thoughtcrime.securesms.util.Util; @@ -36,16 +33,16 @@ public synchronized void onTypingStarted(long threadId) { sendTyping(threadId, true); Runnable start = new StartRunnable(threadId); - Util.runOnMainDelayed(start, REFRESH_TYPING_TIMEOUT); + ThreadUtil.runOnMainDelayed(start, REFRESH_TYPING_TIMEOUT); pair.setStart(start); } if (pair.getStop() != null) { - Util.cancelRunnableOnMain(pair.getStop()); + ThreadUtil.cancelRunnableOnMain(pair.getStop()); } Runnable stop = () -> onTypingStopped(threadId, true); - Util.runOnMainDelayed(stop, PAUSE_TYPING_TIMEOUT); + ThreadUtil.runOnMainDelayed(stop, PAUSE_TYPING_TIMEOUT); pair.setStop(stop); } @@ -62,7 +59,7 @@ private synchronized void onTypingStopped(long threadId, boolean notify) { selfTypingTimers.put(threadId, pair); if (pair.getStart() != null) { - Util.cancelRunnableOnMain(pair.getStart()); + ThreadUtil.cancelRunnableOnMain(pair.getStart()); if (notify) { sendTyping(threadId, false); @@ -70,7 +67,7 @@ private synchronized void onTypingStopped(long threadId, boolean notify) { } if (pair.getStop() != null) { - Util.cancelRunnableOnMain(pair.getStop()); + ThreadUtil.cancelRunnableOnMain(pair.getStop()); } pair.setStart(null); @@ -92,7 +89,7 @@ private StartRunnable(long threadId) { @Override public void run() { sendTyping(threadId, true); - Util.runOnMainDelayed(this, REFRESH_TYPING_TIMEOUT); + ThreadUtil.runOnMainDelayed(this, REFRESH_TYPING_TIMEOUT); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java b/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java index 3c5b1729045..2c196037399 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/camera/CameraView.java @@ -36,6 +36,7 @@ Portions Copyright (C) 2007 The Android Open Source Project import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.BitmapUtil; @@ -320,7 +321,7 @@ private void startPreview(final @NonNull Parameters parameters) { camera.startPreview(); Log.i(TAG, "camera.startPreview() -> " + (System.currentTimeMillis() - previewStartMillis) + "ms"); state = State.ACTIVE; - Util.runOnMain(new Runnable() { + ThreadUtil.runOnMain(new Runnable() { @Override public void run() { requestLayout(); @@ -495,9 +496,9 @@ public final void run() { return; } - Util.runOnMainSync(this::onPreMain); + ThreadUtil.runOnMainSync(this::onPreMain); final Result result = onRunBackground(); - Util.runOnMainSync(() -> onPostMain(result)); + ThreadUtil.runOnMainSync(() -> onPostMain(result)); } protected boolean onWait() { return true; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java index 72939dffdbc..7befdcaa5ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiProvider.java @@ -18,6 +18,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.emoji.parsing.EmojiDrawInfo; @@ -25,7 +26,6 @@ import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser; import org.thoughtcrime.securesms.components.emoji.parsing.EmojiTree; import org.thoughtcrime.securesms.util.FutureTaskListener; -import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.Pair; import java.util.List; @@ -118,7 +118,7 @@ private EmojiProvider(Context context) { final EmojiDrawable drawable = new EmojiDrawable(drawInfo, decodeScale); drawInfo.getPage().get().addListener(new FutureTaskListener() { @Override public void onSuccess(final Bitmap result) { - Util.runOnMain(() -> drawable.setBitmap(result)); + ThreadUtil.runOnMain(() -> drawable.setBitmap(result)); } @Override public void onFailure(ExecutionException error) { @@ -170,7 +170,7 @@ public void draw(@NonNull Canvas canvas) { @TargetApi(VERSION_CODES.HONEYCOMB_MR1) public void setBitmap(Bitmap bitmap) { - Util.assertMainThread(); + ThreadUtil.assertMainThread(); if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB_MR1 || bmp == null || !bmp.sameAs(bitmap)) { bmp = bitmap; invalidateSelf(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiPageBitmap.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiPageBitmap.java index 6f52cfdd4f9..5039914b693 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiPageBitmap.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/parsing/EmojiPageBitmap.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.components.emoji.EmojiPageModel; import org.thoughtcrime.securesms.util.ListenableFutureTask; @@ -39,7 +40,7 @@ public EmojiPageBitmap(@NonNull Context context, @NonNull EmojiPageModel model, @SuppressLint("StaticFieldLeak") public ListenableFutureTask get() { - Util.assertMainThread(); + ThreadUtil.assertMainThread(); if (bitmapReference != null && bitmapReference.get() != null) { return new ListenableFutureTask<>(bitmapReference.get()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java index 45285f29e92..993ff207dfb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java @@ -17,6 +17,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; @@ -29,7 +30,6 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.AvatarUtil; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import org.webrtc.RendererCommon; @@ -154,7 +154,7 @@ void setCallParticipant(@NonNull CallParticipant participant) { private boolean isMissingMediaKeys(@NonNull CallParticipant participant) { if (missingMediaKeysUpdater != null) { - Util.cancelRunnableOnMain(missingMediaKeysUpdater); + ThreadUtil.cancelRunnableOnMain(missingMediaKeysUpdater); missingMediaKeysUpdater = null; } @@ -168,7 +168,7 @@ private boolean isMissingMediaKeys(@NonNull CallParticipant participant) { setCallParticipant(participant); } }; - Util.runOnMainDelayed(missingMediaKeysUpdater, DELAY_SHOWING_MISSING_MEDIA_KEYS - time); + ThreadUtil.runOnMainDelayed(missingMediaKeysUpdater, DELAY_SHOWING_MISSING_MEDIA_KEYS - time); } } return false; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 2121943f681..4ee7052f6f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -88,6 +88,7 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.BlockUnblockDialog; @@ -200,7 +201,6 @@ import org.thoughtcrime.securesms.messagerequests.MessageRequestViewModel; import org.thoughtcrime.securesms.messagerequests.MessageRequestsBottomView; import org.thoughtcrime.securesms.mms.AttachmentManager; -import org.thoughtcrime.securesms.mms.SlideFactory.MediaType; import org.thoughtcrime.securesms.mms.AudioSlide; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.GifSlide; @@ -217,6 +217,7 @@ import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideFactory; +import org.thoughtcrime.securesms.mms.SlideFactory.MediaType; import org.thoughtcrime.securesms.mms.StickerSlide; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.notifications.NotificationChannels; @@ -455,7 +456,7 @@ public void onSuccess(Boolean result) { public void onSuccess(Boolean loadedDraft) { if (loadedDraft != null && loadedDraft) { Log.i(TAG, "Finished loading draft"); - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { if (fragment != null && fragment.isResumed()) { fragment.moveToLastSeen(); } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java index 9b51a52e44b..19b6a6c4491 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java @@ -36,6 +36,7 @@ import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.signal.paging.PagingController; import org.thoughtcrime.securesms.BindableConversationItem; @@ -43,7 +44,6 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.CachedInflater; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.StickyHeaderDecoration; @@ -545,7 +545,7 @@ static void initializePool(@NonNull RecyclerView.RecycledViewPool pool) { @MainThread private void cleanFastRecords() { - Util.assertMainThread(); + ThreadUtil.assertMainThread(); synchronized (releasedFastRecords) { Iterator messageIterator = fastRecords.iterator(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java index a72550c07a3..7769ddc6942 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java @@ -7,11 +7,11 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.conversationlist.model.MessageResult; import org.thoughtcrime.securesms.database.CursorList; import org.thoughtcrime.securesms.search.SearchRepository; import org.thoughtcrime.securesms.util.Debouncer; -import org.thoughtcrime.securesms.util.Util; import java.util.List; @@ -101,7 +101,7 @@ private void updateQuery(@NonNull String query, long threadId) { firstSearch = false; searchRepository.query(query, threadId, messages -> { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { if (searchOpen && query.equals(activeQuery)) { result.setValue(new SearchResult(messages, 0)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListViewModel.java index 71f7628ab54..ffd7489f50b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListViewModel.java @@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.signal.paging.PagedData; import org.signal.paging.PagingConfig; @@ -25,7 +26,6 @@ import org.thoughtcrime.securesms.search.SearchRepository; import org.thoughtcrime.securesms.util.Debouncer; import org.thoughtcrime.securesms.util.ThrottledDebouncer; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.util.paging.Invalidator; @@ -141,7 +141,7 @@ void onMegaphoneVisible(@NonNull Megaphone visible) { void updateQuery(String query) { lastQuery = query; searchDebouncer.publish(() -> searchRepository.query(query, result -> { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { if (query.equals(lastQuery)) { searchResult.setValue(result); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/UpdateDescription.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/UpdateDescription.java index eabd82fd93b..8d3f54793a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/UpdateDescription.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/UpdateDescription.java @@ -7,7 +7,7 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; -import org.thoughtcrime.securesms.util.Util; +import org.signal.core.util.ThreadUtil; import org.whispersystems.signalservice.api.util.UuidUtil; import java.util.Collection; @@ -110,7 +110,7 @@ public boolean isStringStatic() { return staticString; } - Util.assertNotMainThread(); + ThreadUtil.assertNotMainThread(); //noinspection ConstantConditions return stringFactory.create(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2ProcessingLock.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2ProcessingLock.java index af0dc24dafb..a8a4d702146 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2ProcessingLock.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupsV2ProcessingLock.java @@ -2,8 +2,8 @@ import androidx.annotation.WorkerThread; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.util.Util; import java.io.Closeable; import java.util.concurrent.TimeUnit; @@ -26,7 +26,7 @@ static Closeable acquireGroupProcessingLock() throws GroupChangeBusyException { @WorkerThread static Closeable acquireGroupProcessingLock(long timeoutMs) throws GroupChangeBusyException { - Util.assertNotMainThread(); + ThreadUtil.assertNotMainThread(); try { if (!lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupViewModel.java index 665ea852629..1d8bebbd0ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupViewModel.java @@ -16,6 +16,7 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.BlockUnblockDialog; import org.thoughtcrime.securesms.ContactSelectionListFragment; import org.thoughtcrime.securesms.ExpirationDialog; @@ -44,7 +45,6 @@ import org.thoughtcrime.securesms.util.DefaultValueLiveData; import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.FeatureFlags; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; @@ -320,7 +320,7 @@ private void onBlockAndLeaveConfirmed() { @WorkerThread private void showErrorToast(@NonNull GroupChangeFailureReason e) { - Util.runOnMain(() -> Toast.makeText(context, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show()); + ThreadUtil.runOnMain(() -> Toast.makeText(context, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show()); } public void onAddMembersClick(@NonNull Fragment fragment, int resultCode) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java index 000d85f1348..7e0ca63736e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java @@ -9,8 +9,8 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.jobmanager.impl.DefaultExecutorFactory; import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage; @@ -58,7 +58,7 @@ public class JobManager implements ConstraintObserver.Notifier { public JobManager(@NonNull Application application, @NonNull Configuration configuration) { this.application = application; this.configuration = configuration; - this.executor = new FilteredExecutor(configuration.getExecutorFactory().newSingleThreadExecutor("signal-JobManager"), Util::isMainThread); + this.executor = new FilteredExecutor(configuration.getExecutorFactory().newSingleThreadExecutor("signal-JobManager"), ThreadUtil::isMainThread); this.jobTracker = configuration.getJobTracker(); this.jobController = new JobController(application, configuration.getJobStorage(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModel.java index d81fea67ae9..8fe9560e945 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewViewModel.java @@ -11,10 +11,10 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.net.RequestController; import org.thoughtcrime.securesms.util.Debouncer; -import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import java.util.Collections; @@ -99,7 +99,7 @@ public void onTextChanged(@NonNull Context context, @NonNull String text, int cu activeRequest = repository.getLinkPreview(context, link.get().getUrl(), new LinkPreviewRepository.Callback() { @Override public void onSuccess(@NonNull LinkPreview linkPreview) { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { if (!userCanceled) { if (activeUrl != null && activeUrl.equals(linkPreview.getUrl())) { linkPreviewState.setValue(LinkPreviewState.forPreview(linkPreview)); @@ -113,7 +113,7 @@ public void onSuccess(@NonNull LinkPreview linkPreview) { @Override public void onError(@NonNull LinkPreviewRepository.Error error) { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { if (!userCanceled) { linkPreviewState.setValue(LinkPreviewState.forLinksWithNoPreview(error)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraContactSelectionViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraContactSelectionViewModel.java index d181618cbc6..ff4a6ca286f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraContactSelectionViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraContactSelectionViewModel.java @@ -7,9 +7,9 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.SingleLiveEvent; -import org.thoughtcrime.securesms.util.Util; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -34,7 +34,7 @@ private CameraContactSelectionViewModel(@NonNull CameraContactsRepository reposi this.selected = new LinkedHashSet<>(); repository.getCameraContacts(cameraContacts -> { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { contacts.postValue(new ContactState(cameraContacts, new ArrayList<>(selected), currentQuery)); }); }); @@ -56,7 +56,7 @@ void onQueryUpdated(String query) { this.currentQuery = query; repository.getCameraContacts(query, cameraContacts -> { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { contacts.postValue(new ContactState(cameraContacts, new ArrayList<>(selected), query)); }); }); @@ -64,7 +64,7 @@ void onQueryUpdated(String query) { void onRefresh() { repository.getCameraContacts(cameraContacts -> { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { contacts.postValue(new ContactState(cameraContacts, new ArrayList<>(selected), currentQuery)); }); }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java index 776f92b3a77..6abc0ee15ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java @@ -15,6 +15,7 @@ import com.annimon.stream.Stream; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.TransportOption; import org.thoughtcrime.securesms.database.ThreadDatabase; @@ -144,7 +145,7 @@ void onSelectedMediaChanged(@NonNull Context context, @NonNull List newMe } repository.getPopulatedMedia(context, newMedia, populatedMedia -> { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { List filteredMedia = getFilteredMedia(context, populatedMedia, mediaConstraints); if (filteredMedia.size() != newMedia.size()) { @@ -196,7 +197,7 @@ void onSingleMediaSelected(@NonNull Context context, @NonNull Media media) { selectedMedia.setValue(Collections.singletonList(media)); repository.getPopulatedMedia(context, Collections.singletonList(media), populatedMedia -> { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { List filteredMedia = getFilteredMedia(context, populatedMedia, mediaConstraints); if (filteredMedia.isEmpty()) { @@ -654,7 +655,7 @@ private void sendMessages(@NonNull List recipients, @NonNull String b // XXX We must do this to avoid sending out messages to the same recipient with the same // sentTimestamp. If we do this, they'll be considered dupes by the receiver. - Util.sleep(5); + ThreadUtil.sleep(5); } MessageSender.sendMediaBroadcast(application, messages, preUploadResults); diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/ClientDeprecatedActivity.java b/app/src/main/java/org/thoughtcrime/securesms/megaphone/ClientDeprecatedActivity.java index 7ab4bd66d56..c6b57088ed9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/ClientDeprecatedActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/ClientDeprecatedActivity.java @@ -4,13 +4,13 @@ import androidx.appcompat.app.AlertDialog; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.PlayStoreUtil; -import org.thoughtcrime.securesms.util.Util; /** * Shown when a users build fully expires. Controlled by {@link Megaphones.Event#CLIENT_DEPRECATED}. @@ -53,7 +53,7 @@ private void onDontUpdateClicked() { .setMessage(R.string.ClientDeprecatedActivity_your_version_of_signal_has_expired_you_can_view_your_message_history) .setPositiveButton(R.string.ClientDeprecatedActivity_dont_update, (dialog, which) -> { ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.CLIENT_DEPRECATED, () -> { - Util.runOnMain(this::finish); + ThreadUtil.runOnMain(this::finish); }); }) .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageHeaderViewHolder.java b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageHeaderViewHolder.java index 4f63908f64e..2ddcd363b4e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageHeaderViewHolder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageHeaderViewHolder.java @@ -12,6 +12,7 @@ import androidx.lifecycle.LifecycleOwner; import androidx.recyclerview.widget.RecyclerView; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.conversation.ConversationItem; @@ -21,7 +22,6 @@ import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.ExpirationUtil; -import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import java.sql.Date; @@ -156,7 +156,7 @@ private void bindExpirationTime(final MessageRecord messageRecord, boolean runni expiresGroup.setVisibility(View.VISIBLE); if (running) { expiresUpdater = new ExpiresUpdater(messageRecord); - Util.runOnMain(expiresUpdater); + ThreadUtil.runOnMain(expiresUpdater); } } @@ -203,7 +203,7 @@ public void run() { expiresIn.setText(duration); if (running && expirationTime > 1) { - Util.runOnMainDelayed(this, 500); + ThreadUtil.runOnMainDelayed(this, 500); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index c8385b06edb..8c2278a2680 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -38,6 +38,7 @@ import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.MediaPreviewActivity; import org.thoughtcrime.securesms.R; @@ -58,7 +59,6 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.MediaUtil; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; @@ -207,7 +207,7 @@ public void onSuccess(@NonNull Bitmap result) { .createForSingleSessionInMemory(); LocationSlide locationSlide = new LocationSlide(context, uri, blob.length, place); - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { setSlide(locationSlide); attachmentListener.onAttachmentChanged(); returnResult.set(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java index c6873cec3f5..13ff7099d8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.java @@ -9,13 +9,13 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.SingleLiveEvent; import org.thoughtcrime.securesms.util.UsernameUtil; import org.thoughtcrime.securesms.util.UsernameUtil.InvalidReason; -import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; class UsernameEditViewModel extends ViewModel { @@ -73,7 +73,7 @@ void onUsernameSubmitted(@NonNull String username) { uiState.setValue(new State(ButtonState.SUBMIT_LOADING, UsernameStatus.NONE)); repo.setUsername(username, (result) -> { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { switch (result) { case SUCCESS: uiState.setValue(new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE)); @@ -100,7 +100,7 @@ void onUsernameDeleted() { uiState.setValue(new State(ButtonState.DELETE_LOADING, UsernameStatus.NONE)); repo.deleteUsername((result) -> { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { switch (result) { case SUCCESS: uiState.postValue(new State(ButtonState.DELETE_DISABLED, UsernameStatus.NONE)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java index 5d9b0e4db88..e32aaffd716 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/reactions/any/ReactWithAnyEmojiRepository.java @@ -8,6 +8,7 @@ import com.annimon.stream.Stream; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; @@ -21,7 +22,6 @@ import org.thoughtcrime.securesms.reactions.ReactionDetails; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.MessageSender; -import org.thoughtcrime.securesms.util.Util; import java.util.Arrays; import java.util.Collections; @@ -80,7 +80,7 @@ void addEmojiToMessage(@NonNull String emoji, long messageId, boolean isMms) { MessageSender.sendReactionRemoval(context, messageRecord.getId(), messageRecord.isMms(), oldRecord); } else { MessageSender.sendNewReaction(context, messageRecord.getId(), messageRecord.isMms(), emoji); - Util.runOnMain(() -> recentEmojiPageModel.onCodePointSelected(emoji)); + ThreadUtil.runOnMain(() -> recentEmojiPageModel.onCodePointSelected(emoji)); } } catch (NoSuchMessageException e) { Log.w(TAG, "Message not found! Ignoring."); diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java index e015377d1d5..0dd6a2b0943 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java @@ -11,13 +11,13 @@ import com.annimon.stream.Stream; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.whispersystems.libsignal.util.guava.Optional; @@ -79,14 +79,14 @@ public final class LiveRecipient { * use {@link #removeObservers(LifecycleOwner)}. */ public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) { - Util.postToMain(() -> observableLiveData.observe(owner, observer)); + ThreadUtil.postToMain(() -> observableLiveData.observe(owner, observer)); } /** * Removes all observers of this data registered for the given LifecycleOwner. */ public void removeObservers(@NonNull LifecycleOwner owner) { - Util.runOnMain(() -> observableLiveData.removeObservers(owner)); + ThreadUtil.runOnMain(() -> observableLiveData.removeObservers(owner)); } /** @@ -95,7 +95,7 @@ public void removeObservers(@NonNull LifecycleOwner owner) { * {@link #observe(LifecycleOwner, Observer)} if possible, as it is lifecycle-safe. */ public void observeForever(@NonNull RecipientForeverObserver observer) { - Util.postToMain(() -> { + ThreadUtil.postToMain(() -> { if (observers.isEmpty()) { observableLiveData.observeForever(foreverObserver); } @@ -107,7 +107,7 @@ public void observeForever(@NonNull RecipientForeverObserver observer) { * Unsubscribes the provided {@link RecipientForeverObserver} from future changes. */ public void removeForeverObserver(@NonNull RecipientForeverObserver observer) { - Util.postToMain(() -> { + ThreadUtil.postToMain(() -> { observers.remove(observer); if (observers.isEmpty()) { @@ -127,7 +127,7 @@ public void removeForeverObserver(@NonNull RecipientForeverObserver observer) { return current; } - if (Util.isMainThread()) { + if (ThreadUtil.isMainThread()) { Log.w(TAG, "[Resolve][MAIN] " + getId(), new Throwable()); } @@ -163,7 +163,7 @@ public void refresh(@NonNull RecipientId id) { if (getId().isUnknown()) return; - if (Util.isMainThread()) { + if (ThreadUtil.isMainThread()) { Log.w(TAG, "[Refresh][MAIN] " + id, new Throwable()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java index ba3c44ae1cb..ccc30154bc8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java @@ -15,6 +15,7 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.BlockUnblockDialog; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.VerifyIdentityActivity; @@ -30,7 +31,6 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.recipients.ui.managerecipient.ManageRecipientActivity; import org.thoughtcrime.securesms.util.CommunicationActions; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import java.util.Objects; @@ -205,7 +205,7 @@ void onAddToGroupButton(@NonNull Activity activity) { @WorkerThread private void showErrorToast(@NonNull GroupChangeFailureReason e) { - Util.runOnMain(() -> Toast.makeText(context, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show()); + ThreadUtil.runOnMain(() -> Toast.makeText(context, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show()); } static class AdminActionStatus { diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/managerecipient/ManageRecipientViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/managerecipient/ManageRecipientViewModel.java index dcea1518ac3..73d105fee62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/managerecipient/ManageRecipientViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/managerecipient/ManageRecipientViewModel.java @@ -16,6 +16,7 @@ import com.annimon.stream.Stream; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.BlockUnblockDialog; import org.thoughtcrime.securesms.ExpirationDialog; import org.thoughtcrime.securesms.R; @@ -38,7 +39,6 @@ import org.thoughtcrime.securesms.util.DefaultValueLiveData; import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.Hex; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import java.util.List; @@ -207,11 +207,11 @@ void revealCollapsedMembers() { } void onAddToGroupButton(@NonNull Activity activity) { - manageRecipientRepository.getGroupMembership(existingGroups -> Util.runOnMain(() -> activity.startActivity(AddToGroupsActivity.newIntent(activity, manageRecipientRepository.getRecipientId(), existingGroups)))); + manageRecipientRepository.getGroupMembership(existingGroups -> ThreadUtil.runOnMain(() -> activity.startActivity(AddToGroupsActivity.newIntent(activity, manageRecipientRepository.getRecipientId(), existingGroups)))); } private void withRecipient(@NonNull Consumer mainThreadRecipientCallback) { - manageRecipientRepository.getRecipient(recipient -> Util.runOnMain(() -> mainThreadRecipientCallback.accept(recipient))); + manageRecipientRepository.getRecipient(recipient -> ThreadUtil.runOnMain(() -> mainThreadRecipientCallback.accept(recipient))); } private static @NonNull List filterSharedGroupList(@NonNull List groups, diff --git a/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageViewModel.java index 4c1c93ccbf4..8247fe9498b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/revealable/ViewOnceMessageViewModel.java @@ -10,10 +10,10 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.database.DatabaseContentProviders; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; class ViewOnceMessageViewModel extends ViewModel { @@ -59,7 +59,7 @@ protected void onCleared() { } private void onMessageRetrieved(@NonNull Optional optionalMessage) { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { MmsMessageRecord current = message.getValue() != null ? message.getValue().orNull() : null; MmsMessageRecord proposed = optionalMessage.orNull(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java index a7625b47680..132a3e6d264 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/ActiveCallActionProcessorDelegate.java @@ -5,6 +5,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallId; @@ -15,7 +16,6 @@ import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger; import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; @@ -196,7 +196,7 @@ public ActiveCallActionProcessorDelegate(@NonNull WebRtcInteractor webRtcInterac OutgoingRinger ringer = new OutgoingRinger(context); ringer.start(OutgoingRinger.Type.BUSY); - Util.runOnMainDelayed(ringer::stop, BUSY_TONE_LENGTH); + ThreadUtil.runOnMainDelayed(ringer::stop, BUSY_TONE_LENGTH); } else if (action.equals(ACTION_ENDED_REMOTE_GLARE) && incomingBeforeAccept) { webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcVideoUtil.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcVideoUtil.java index 2b113e0bb4e..28b5f50f677 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcVideoUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcVideoUtil.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; +import org.signal.core.util.ThreadUtil; import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink; import org.thoughtcrime.securesms.components.webrtc.OrientationAwareVideoSink; import org.thoughtcrime.securesms.ringrtc.Camera; @@ -11,7 +12,6 @@ import org.thoughtcrime.securesms.ringrtc.CameraState; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder; -import org.thoughtcrime.securesms.util.Util; import org.webrtc.CapturerObserver; import org.webrtc.EglBase; import org.webrtc.VideoFrame; @@ -31,7 +31,7 @@ private WebRtcVideoUtil() {} { final WebRtcServiceStateBuilder builder = currentState.builder(); - Util.runOnMainSync(() -> { + ThreadUtil.runOnMainSync(() -> { EglBase eglBase = EglBase.create(); BroadcastVideoSink localSink = new BroadcastVideoSink(eglBase); Camera camera = new Camera(context, cameraEventListener, eglBase, CameraState.Direction.FRONT); @@ -57,7 +57,7 @@ private WebRtcVideoUtil() {} { final WebRtcServiceStateBuilder builder = currentState.builder(); - Util.runOnMainSync(() -> { + ThreadUtil.runOnMainSync(() -> { Camera camera = currentState.getVideoState().requireCamera(); camera.setEnabled(false); camera.dispose(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/shakereport/ShakeToReport.java b/app/src/main/java/org/thoughtcrime/securesms/shakereport/ShakeToReport.java index 2ef5c986a9c..e4b18be7a86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/shakereport/ShakeToReport.java +++ b/app/src/main/java/org/thoughtcrime/securesms/shakereport/ShakeToReport.java @@ -9,16 +9,15 @@ import androidx.appcompat.app.AlertDialog; import org.signal.core.util.ShakeDetector; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.signal.core.util.tracing.Tracer; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogRepository; import org.thoughtcrime.securesms.sharing.ShareIntents; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.ServiceUtil; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import java.lang.ref.WeakReference; @@ -96,7 +95,7 @@ private void submitLog(@NonNull Activity activity) { repo.submitLog(lines, Tracer.getInstance().serialize(), url -> { Log.i(TAG, "Logs uploaded!"); - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { spinner.dismiss(); if (url.isPresent()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AppForegroundObserver.java b/app/src/main/java/org/thoughtcrime/securesms/util/AppForegroundObserver.java index 9a9587c42e8..f077f8afb8b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AppForegroundObserver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AppForegroundObserver.java @@ -7,6 +7,8 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ProcessLifecycleOwner; +import org.signal.core.util.ThreadUtil; + import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -22,7 +24,7 @@ public final class AppForegroundObserver { @MainThread public void begin() { - Util.assertMainThread(); + ThreadUtil.assertMainThread(); ProcessLifecycleOwner.get().getLifecycle().addObserver(new DefaultLifecycleObserver() { @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AsynchronousCallback.java b/app/src/main/java/org/thoughtcrime/securesms/util/AsynchronousCallback.java index f0fdcbe4e84..1a5370acda2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AsynchronousCallback.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AsynchronousCallback.java @@ -3,6 +3,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.signal.core.util.ThreadUtil; + public final class AsynchronousCallback { /** @@ -51,12 +53,12 @@ public interface MainThread { return new WorkerThread() { @Override public void onComplete(@Nullable R result) { - Util.runOnMain(() -> MainThread.this.onComplete(result)); + ThreadUtil.runOnMain(() -> MainThread.this.onComplete(result)); } @Override public void onError(@Nullable E error) { - Util.runOnMain(() -> MainThread.this.onError(error)); + ThreadUtil.runOnMain(() -> MainThread.this.onError(error)); } }; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java index 196b9f553a7..6ff81f689bf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/BitmapUtil.java @@ -20,6 +20,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.MediaConstraints; @@ -410,7 +411,7 @@ public void run() { } }; - Util.runOnMain(runnable); + ThreadUtil.runOnMain(runnable); synchronized (result) { while (!created.get()) Util.wait(result, 0); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CachedInflater.java b/app/src/main/java/org/thoughtcrime/securesms/util/CachedInflater.java index 73326755863..0bc52bab563 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CachedInflater.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CachedInflater.java @@ -12,6 +12,7 @@ import androidx.annotation.Nullable; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.util.concurrent.SerialExecutor; @@ -124,7 +125,7 @@ void cacheUntilLimit(@NonNull Context context, @LayoutRes int layoutRes, @Nullab } AsyncLayoutInflater.OnInflateFinishedListener onInflateFinishedListener = (view, resId, p) -> { - Util.assertMainThread(); + ThreadUtil.assertMainThread(); if (enqueueTime < lastClearTime) { Log.d(TAG, "Prefetch is no longer valid. Ignoring."); return; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SignalProxyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/SignalProxyUtil.java index 8cdb6affd2e..4ab21330320 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SignalProxyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SignalProxyUtil.java @@ -6,6 +6,7 @@ import androidx.lifecycle.Observer; import org.conscrypt.Conscrypt; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -16,10 +17,6 @@ import org.whispersystems.signalservice.internal.configuration.SignalProxy; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -97,14 +94,14 @@ public static boolean testWebsocketConnection(long timeout) { } }; - Util.runOnMainSync(() -> ApplicationDependencies.getPipeListener().getState().observeForever(observer)); + ThreadUtil.runOnMainSync(() -> ApplicationDependencies.getPipeListener().getState().observeForever(observer)); try { latch.await(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Log.w(TAG, "Interrupted!", e); } finally { - Util.runOnMainSync(() -> ApplicationDependencies.getPipeListener().getState().removeObserver(observer)); + ThreadUtil.runOnMainSync(() -> ApplicationDependencies.getPipeListener().getState().removeObserver(observer)); } return success.get(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java index eb3a82bab16..c1eb02af671 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java @@ -27,8 +27,6 @@ import android.net.Uri; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.os.Handler; -import android.os.Looper; import android.provider.Telephony; import android.telephony.TelephonyManager; import android.text.Spannable; @@ -47,7 +45,6 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber; -import org.signal.core.util.LinkedBlockingLifoQueue; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.components.ComposeText; @@ -66,9 +63,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Util { @@ -76,8 +70,6 @@ public class Util { private static final long BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90); - private static volatile Handler handler; - public static List asList(T... elements) { List result = new LinkedList<>(); Collections.addAll(result, elements); @@ -148,17 +140,6 @@ public static String rightPad(String value, int length) { return out.toString(); } - public static ExecutorService newSingleThreadedLifoExecutor() { - ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingLifoQueue()); - - executor.execute(() -> { -// Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - Thread.currentThread().setPriority(Thread.MIN_PRIORITY); - }); - - return executor; - } - public static boolean isEmpty(EncodedStringValue[] value) { return value == null || value.length == 0; } @@ -422,59 +403,6 @@ public static boolean isMmsCapable(Context context) { return (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) || OutgoingLegacyMmsConnection.isConnectionPossible(context); } - public static boolean isMainThread() { - return Looper.myLooper() == Looper.getMainLooper(); - } - - public static void assertMainThread() { - if (!isMainThread()) { - throw new AssertionError("Must run on main thread."); - } - } - - public static void assertNotMainThread() { - if (isMainThread()) { - throw new AssertionError("Cannot run on main thread."); - } - } - - public static void postToMain(final @NonNull Runnable runnable) { - getHandler().post(runnable); - } - - public static void runOnMain(final @NonNull Runnable runnable) { - if (isMainThread()) runnable.run(); - else getHandler().post(runnable); - } - - public static void runOnMainDelayed(final @NonNull Runnable runnable, long delayMillis) { - getHandler().postDelayed(runnable, delayMillis); - } - - public static void cancelRunnableOnMain(@NonNull Runnable runnable) { - getHandler().removeCallbacks(runnable); - } - - public static void runOnMainSync(final @NonNull Runnable runnable) { - if (isMainThread()) { - runnable.run(); - } else { - final CountDownLatch sync = new CountDownLatch(1); - runOnMain(() -> { - try { - runnable.run(); - } finally { - sync.countDown(); - } - }); - try { - sync.await(); - } catch (InterruptedException ie) { - throw new AssertionError(ie); - } - } - } - public static T getRandomElement(T[] elements) { return elements[new SecureRandom().nextInt(elements.length)]; } @@ -551,29 +479,10 @@ public static String getPrettyFileSize(long sizeBytes) { return MemoryUnitFormat.formatBytes(sizeBytes); } - public static void sleep(long millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException e) { - throw new AssertionError(e); - } - } - public static void copyToClipboard(@NonNull Context context, @NonNull String text) { ServiceUtil.getClipboardManager(context).setPrimaryClip(ClipData.newPlainText("text", text)); } - private static Handler getHandler() { - if (handler == null) { - synchronized (Util.class) { - if (handler == null) { - handler = new Handler(Looper.getMainLooper()); - } - } - } - return handler; - } - @SafeVarargs public static List concatenatedList(Collection ... items) { final List concat = new ArrayList<>(Stream.of(items).reduce(0, (sum, list) -> sum + list.size())); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/concurrent/SimpleTask.java b/app/src/main/java/org/thoughtcrime/securesms/util/concurrent/SimpleTask.java index 43d12ecd4ec..0ddb5540c44 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/concurrent/SimpleTask.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/concurrent/SimpleTask.java @@ -5,8 +5,8 @@ import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; -import org.thoughtcrime.securesms.util.Util; import java.util.concurrent.Executor; @@ -28,7 +28,7 @@ public static void run(@NonNull Lifecycle lifecycle, @NonNull BackgroundTask final E result = backgroundTask.run(); if (isValid(lifecycle)) { - Util.runOnMain(() -> { + ThreadUtil.runOnMain(() -> { if (isValid(lifecycle)) { foregroundTask.run(result); } @@ -52,7 +52,7 @@ public static void run(@NonNull BackgroundTask backgroundTask, @NonNull F public static void run(@NonNull Executor executor, @NonNull BackgroundTask backgroundTask, @NonNull ForegroundTask foregroundTask) { executor.execute(() -> { final E result = backgroundTask.run(); - Util.runOnMain(() -> foregroundTask.run(result)); + ThreadUtil.runOnMain(() -> foregroundTask.run(result)); }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/views/SimpleProgressDialog.java b/app/src/main/java/org/thoughtcrime/securesms/util/views/SimpleProgressDialog.java index 5a99bc3d19f..5ff2a3e7cfb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/views/SimpleProgressDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/views/SimpleProgressDialog.java @@ -7,9 +7,9 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.util.Util; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -66,18 +66,18 @@ private SimpleProgressDialog() {} dialogAtomicReference.set(show(context)); }; - Util.runOnMainDelayed(showRunnable, delayMs); + ThreadUtil.runOnMainDelayed(showRunnable, delayMs); return () -> { - Util.cancelRunnableOnMain(showRunnable); - Util.runOnMain(() -> { + ThreadUtil.cancelRunnableOnMain(showRunnable); + ThreadUtil.runOnMain(() -> { AlertDialog alertDialog = dialogAtomicReference.getAndSet(null); if (alertDialog != null) { long beenShowingForMs = System.currentTimeMillis() - shownAt.get(); long remainingTimeMs = minimumShowTimeMs - beenShowingForMs; if (remainingTimeMs > 0) { - Util.runOnMainDelayed(alertDialog::dismiss, remainingTimeMs); + ThreadUtil.runOnMainDelayed(alertDialog::dismiss, remainingTimeMs); } else { alertDialog.dismiss(); } diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java index 1dd53f438a9..71df99ca1ff 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/database/model/GroupsV2UpdateMessageProducerTest.java @@ -18,6 +18,7 @@ import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.signal.core.util.ThreadUtil; import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; @@ -47,7 +48,7 @@ @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE, application = Application.class) @PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*", "androidx.*" }) -@PrepareForTest(Util.class) +@PrepareForTest(ThreadUtil.class) public final class GroupsV2UpdateMessageProducerTest { private UUID you; diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/model/UpdateDescriptionTest.java b/app/src/test/java/org/thoughtcrime/securesms/database/model/UpdateDescriptionTest.java index 0595f7dc16e..ce0e6ce9a53 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/database/model/UpdateDescriptionTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/database/model/UpdateDescriptionTest.java @@ -5,7 +5,7 @@ import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import org.thoughtcrime.securesms.util.Util; +import org.signal.core.util.ThreadUtil; import java.util.Arrays; import java.util.Collections; @@ -19,7 +19,7 @@ import static org.thoughtcrime.securesms.testutil.MainThreadUtil.setMainThread; @RunWith(PowerMockRunner.class) -@PrepareForTest(Util.class) +@PrepareForTest(ThreadUtil.class) public final class UpdateDescriptionTest { @Before diff --git a/app/src/test/java/org/thoughtcrime/securesms/testutil/MainThreadUtil.java b/app/src/test/java/org/thoughtcrime/securesms/testutil/MainThreadUtil.java index a83dd0682a8..d2addeddf63 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/testutil/MainThreadUtil.java +++ b/app/src/test/java/org/thoughtcrime/securesms/testutil/MainThreadUtil.java @@ -1,6 +1,6 @@ package org.thoughtcrime.securesms.testutil; -import org.thoughtcrime.securesms.util.Util; +import org.signal.core.util.ThreadUtil; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.doCallRealMethod; @@ -12,17 +12,17 @@ private MainThreadUtil() { } /** - * Makes {@link Util}'s Main thread assertions pass or fail during tests. + * Makes {@link ThreadUtil}'s Main thread assertions pass or fail during tests. *

* Use with {@link org.powermock.modules.junit4.PowerMockRunner} or robolectric with powermock * rule and {@code @PrepareForTest(Util.class)} */ public static void setMainThread(boolean isMainThread) { - mockStatic(Util.class); - when(Util.isMainThread()).thenReturn(isMainThread); + mockStatic(ThreadUtil.class); + when(ThreadUtil.isMainThread()).thenReturn(isMainThread); try { - doCallRealMethod().when(Util.class, "assertMainThread"); - doCallRealMethod().when(Util.class, "assertNotMainThread"); + doCallRealMethod().when(ThreadUtil.class, "assertMainThread"); + doCallRealMethod().when(ThreadUtil.class, "assertNotMainThread"); } catch (Exception e) { throw new AssertionError(); } diff --git a/core-util/src/main/java/org/signal/core/util/ThreadUtil.java b/core-util/src/main/java/org/signal/core/util/ThreadUtil.java new file mode 100644 index 00000000000..54d68ba432a --- /dev/null +++ b/core-util/src/main/java/org/signal/core/util/ThreadUtil.java @@ -0,0 +1,90 @@ +package org.signal.core.util; + +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; + +import java.util.concurrent.CountDownLatch; + +/** + * Thread related utility functions. + */ +public final class ThreadUtil { + + private static volatile Handler handler; + + private ThreadUtil() {} + + private static Handler getHandler() { + if (handler == null) { + synchronized (ThreadUtil.class) { + if (handler == null) { + handler = new Handler(Looper.getMainLooper()); + } + } + } + return handler; + } + + public static boolean isMainThread() { + return Looper.myLooper() == Looper.getMainLooper(); + } + + public static void assertMainThread() { + if (!isMainThread()) { + throw new AssertionError("Must run on main thread."); + } + } + + public static void assertNotMainThread() { + if (isMainThread()) { + throw new AssertionError("Cannot run on main thread."); + } + } + + public static void postToMain(final @NonNull Runnable runnable) { + getHandler().post(runnable); + } + + public static void runOnMain(final @NonNull Runnable runnable) { + if (isMainThread()) runnable.run(); + else getHandler().post(runnable); + } + + public static void runOnMainDelayed(final @NonNull Runnable runnable, long delayMillis) { + getHandler().postDelayed(runnable, delayMillis); + } + + public static void cancelRunnableOnMain(@NonNull Runnable runnable) { + getHandler().removeCallbacks(runnable); + } + + public static void runOnMainSync(final @NonNull Runnable runnable) { + if (isMainThread()) { + runnable.run(); + } else { + final CountDownLatch sync = new CountDownLatch(1); + runOnMain(() -> { + try { + runnable.run(); + } finally { + sync.countDown(); + } + }); + try { + sync.await(); + } catch (InterruptedException ie) { + throw new AssertionError(ie); + } + } + } + + public static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + } +} From dc9fceb8cfeb41556f149b1b2c5e3079fcbe2626 Mon Sep 17 00:00:00 2001 From: Jim Gustafson Date: Mon, 1 Mar 2021 20:41:52 -0800 Subject: [PATCH 04/24] Update to RingRTC v2.9.4 --- app/build.gradle | 2 +- app/witness-verifications.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1a3a3502d83..c34f7ea3382 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -334,7 +334,7 @@ dependencies { implementation 'com.google.protobuf:protobuf-javalite:3.10.0' implementation 'org.signal:argon2:13.1@aar' - implementation 'org.signal:ringrtc-android:2.9.2' + implementation 'org.signal:ringrtc-android:2.9.4' implementation "me.leolin:ShortcutBadger:1.1.22" implementation 'se.emilsjolander:stickylistheaders:2.7.0' diff --git a/app/witness-verifications.gradle b/app/witness-verifications.gradle index 327a500dbb9..fca345318e4 100644 --- a/app/witness-verifications.gradle +++ b/app/witness-verifications.gradle @@ -414,8 +414,8 @@ dependencyVerification { ['org.signal:argon2:13.1', '0f686ccff0d4842bfcc74d92e8dc780a5f159b9376e37a1189fabbcdac458bef'], - ['org.signal:ringrtc-android:2.9.2', - 'baf77e1f314dce89278af205637d607dbf4607651dee02384f111f43fea29cf6'], + ['org.signal:ringrtc-android:2.9.4', + 'd4d26b26b096c553966f77229b3f7a0d3704d88caf9caca937388cff054f5257'], ['org.signal:zkgroup-android:0.7.0', '52b172565bd01526e93ebf1796b834bdc449d4fe3422c1b827e49cb8d4f13fbd'], From fd9c420dc8dcf920c1993e256a1139a7c2e2c368 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Wed, 3 Mar 2021 09:37:30 -0400 Subject: [PATCH 05/24] Split system names into first / last. --- .../securesms/contacts/ContactAccessor.java | 108 +++++++++--------- .../securesms/contacts/ContactRepository.java | 2 +- .../contacts/sync/ContactHolder.java | 53 +++++++++ .../contacts/sync/DirectoryHelper.java | 89 ++++++++++++--- .../contacts/sync/PhoneNumberRecord.java | 99 ++++++++++++++++ .../contacts/sync/StructuredNameRecord.java | 46 ++++++++ .../securesms/database/RecipientDatabase.java | 95 ++++++++------- .../database/helpers/SQLCipherOpenHelper.java | 9 +- .../insights/InsightsRepository.java | 2 +- .../jobs/MultiDeviceContactUpdateJob.java | 4 +- .../migrations/ApplicationMigrations.java | 5 + .../securesms/profiles/ProfileName.java | 7 ++ .../edit/EditGroupProfileRepository.java | 2 +- .../securesms/recipients/Recipient.java | 80 ++++++++----- .../recipients/RecipientDetails.java | 16 +-- 15 files changed, 459 insertions(+), 158 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactHolder.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/contacts/sync/PhoneNumberRecord.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/contacts/sync/StructuredNameRecord.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java index aa104dc4d2d..81eca1d7b3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java @@ -23,28 +23,37 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.PhoneLookup; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; +import androidx.annotation.NonNull; + import com.annimon.stream.Stream; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; +import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.util.CursorUtil; +import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; @@ -65,6 +74,9 @@ public class ContactAccessor { public static final String PUSH_COLUMN = "push"; + private static final String GIVEN_NAME = ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME; + private static final String FAMILY_NAME = ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME; + private static final ContactAccessor instance = new ContactAccessor(); public static synchronized ContactAccessor getInstance() { @@ -85,55 +97,42 @@ public Set getAllContactsWithNumbers(Context context) { return results; } + /** + * Gets and returns a cursor of data for all contacts, containing both phone number data and + * structured name data. + * + * Cursor rows are ordered as follows: + * + *

    + *
  1. Contact Lookup Key
  2. + *
  3. Mimetype
  4. + *
  5. id
  6. + *
+ * + * The lookup key is a fixed value that allows you to verify two rows in the database actually + * belong to the same contact, since the contact uri can be unstable (if a sync fails, say.) + * + * We order by id explicitly here for the same contact sync failure error, which could result in + * multiple structured name rows for the same user. By ordering by id DESC, we ensure we get + * whatever the latest input data was. + * + * What this results in is a cursor that looks like: + * + * Alice phone 1 + * Alice phone 2 + * Alice structured name 2 + * Alice structured name 1 + * Bob phone 1 + * ... etc. + */ public Cursor getAllSystemContacts(Context context) { - return context.getContentResolver().query(Phone.CONTENT_URI, new String[] {Phone.NUMBER, Phone.DISPLAY_NAME, Phone.LABEL, Phone.PHOTO_URI, Phone._ID, Phone.LOOKUP_KEY, Phone.TYPE}, null, null, null); - } - - public boolean isSystemContact(Context context, String number) { - Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); - String[] projection = new String[]{PhoneLookup.DISPLAY_NAME, PhoneLookup.LOOKUP_KEY, - PhoneLookup._ID, PhoneLookup.NUMBER}; - Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); - - try { - if (cursor != null && cursor.moveToFirst()) { - return true; - } - } finally { - if (cursor != null) cursor.close(); - } + Uri uri = ContactsContract.Data.CONTENT_URI; + String[] projection = SqlUtil.buildArgs(ContactsContract.Data.MIMETYPE, Phone.NUMBER, Phone.DISPLAY_NAME, Phone.LABEL, Phone.PHOTO_URI, Phone._ID, Phone.LOOKUP_KEY, Phone.TYPE, GIVEN_NAME, FAMILY_NAME); + String where = ContactsContract.Data.MIMETYPE + " IN (?, ?)"; + String[] args = SqlUtil.buildArgs(Phone.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); + String orderBy = Phone.LOOKUP_KEY + " ASC, " + ContactsContract.Data.MIMETYPE + " DESC, " + ContactsContract.CommonDataKinds.Phone._ID + " DESC"; - return false; - } - - public Collection getContactsWithPush(Context context) { - final ContentResolver resolver = context.getContentResolver(); - final String[] inProjection = new String[]{PhoneLookup._ID, PhoneLookup.DISPLAY_NAME}; - - final List registeredAddresses = Stream.of(DatabaseFactory.getRecipientDatabase(context).getRegistered()) - .map(Recipient::resolved) - .filter(r -> r.getE164().isPresent()) - .map(Recipient::requireE164) - .toList(); - final Collection lookupData = new ArrayList<>(registeredAddresses.size()); - - for (String registeredAddress : registeredAddresses) { - Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(registeredAddress)); - Cursor lookupCursor = resolver.query(uri, inProjection, null, null, null); - - try { - if (lookupCursor != null && lookupCursor.moveToFirst()) { - final ContactData contactData = new ContactData(lookupCursor.getLong(0), lookupCursor.getString(1)); - contactData.numbers.add(new NumberData("TextSecure", registeredAddress)); - lookupData.add(contactData); - } - } finally { - if (lookupCursor != null) - lookupCursor.close(); - } - } - - return lookupData; + return context.getContentResolver().query(uri, projection, where, args, orderBy); } public String getNameFromContact(Context context, Uri uri) { @@ -160,13 +159,13 @@ public ContactData getContactData(Context context, Uri uri) { private ContactData getContactData(Context context, String displayName, long id) { ContactData contactData = new ContactData(id, displayName); - Cursor numberCursor = null; - - try { - numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null, - Phone.CONTACT_ID + " = ?", - new String[] {contactData.id + ""}, null); + try (Cursor numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, + null, + Phone.CONTACT_ID + " = ?", + new String[] {contactData.id + ""}, + null)) + { while (numberCursor != null && numberCursor.moveToNext()) { int type = numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE)); String label = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL)); @@ -175,9 +174,6 @@ private ContactData getContactData(Context context, String displayName, long id) contactData.numbers.add(new NumberData(typeLabel, number)); } - } finally { - if (numberCursor != null) - numberCursor.close(); } return contactData; diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java index 3696efdc171..7c614b13886 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactRepository.java @@ -57,7 +57,7 @@ public class ContactRepository { add(new Pair<>(ID_COLUMN, cursor -> CursorUtil.requireLong(cursor, RecipientDatabase.ID))); add(new Pair<>(NAME_COLUMN, cursor -> { - String system = CursorUtil.requireString(cursor, RecipientDatabase.SYSTEM_DISPLAY_NAME); + String system = CursorUtil.requireString(cursor, RecipientDatabase.SYSTEM_JOINED_NAME); String profile = CursorUtil.requireString(cursor, RecipientDatabase.SEARCH_PROFILE_NAME); return Util.getFirstNonEmpty(system, profile); diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactHolder.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactHolder.java new file mode 100644 index 00000000000..4a84052523a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactHolder.java @@ -0,0 +1,53 @@ +package org.thoughtcrime.securesms.contacts.sync; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.profiles.ProfileName; + +import java.util.LinkedList; +import java.util.List; + +final class ContactHolder { + + private final String lookupKey; + private final List phoneNumberRecords = new LinkedList<>(); + + private StructuredNameRecord structuredNameRecord; + + ContactHolder(@NonNull String lookupKey) { + this.lookupKey = lookupKey; + } + + @NonNull String getLookupKey() { + return lookupKey; + } + + public void addPhoneNumberRecord(@NonNull PhoneNumberRecord phoneNumberRecord) { + phoneNumberRecords.add(phoneNumberRecord); + } + + public void setStructuredNameRecord(@NonNull StructuredNameRecord structuredNameRecord) { + this.structuredNameRecord = structuredNameRecord; + } + + void commit(@NonNull RecipientDatabase.BulkOperationsHandle handle) { + for (PhoneNumberRecord phoneNumberRecord : phoneNumberRecords) { + handle.setSystemContactInfo(phoneNumberRecord.getRecipientId(), + getProfileName(phoneNumberRecord.getDisplayName()), + phoneNumberRecord.getContactPhotoUri(), + phoneNumberRecord.getContactLabel(), + phoneNumberRecord.getPhoneType(), + phoneNumberRecord.getContactUri().toString()); + } + } + + private @NonNull ProfileName getProfileName(@NonNull String displayName) { + if (structuredNameRecord.hasGivenName()) { + return structuredNameRecord.asProfileName(); + } else { + return ProfileName.asGiven(displayName); + } + } + +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java index dbf70159545..128775eee10 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java @@ -7,7 +7,6 @@ import android.content.Context; import android.content.OperationApplicationException; import android.database.Cursor; -import android.net.Uri; import android.os.RemoteException; import android.provider.ContactsContract; import android.text.TextUtils; @@ -43,6 +42,7 @@ import org.thoughtcrime.securesms.registration.RegistrationUtil; import org.thoughtcrime.securesms.sms.IncomingJoinedMessage; import org.thoughtcrime.securesms.storage.StorageSyncHelper; +import org.thoughtcrime.securesms.util.CursorUtil; import org.thoughtcrime.securesms.util.ProfileUtil; import org.thoughtcrime.securesms.util.SetUtil; import org.thoughtcrime.securesms.util.Stopwatch; @@ -63,6 +63,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutionException; @@ -320,27 +321,63 @@ private static void updateContactsDatabase(@NonNull Context context, contactsDatabase.removeDeletedRawContacts(account.getAccount()); contactsDatabase.setRegisteredUsers(account.getAccount(), activeAddresses, removeMissing); - Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context); - BulkOperationsHandle handle = recipientDatabase.beginBulkSystemContactUpdate(); + BulkOperationsHandle handle = recipientDatabase.beginBulkSystemContactUpdate(); - try { + ContactHolder old = null; + try (Cursor cursor = ContactAccessor.getInstance().getAllSystemContacts(context)) { while (cursor != null && cursor.moveToNext()) { - String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)); - - if (isValidContactNumber(number)) { - String formattedNumber = PhoneNumberFormatter.get(context).format(number); - String realNumber = Util.getFirstNonEmpty(rewrites.get(formattedNumber), formattedNumber); - RecipientId recipientId = Recipient.externalContact(context, realNumber).getId(); - String displayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); - String contactPhotoUri = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.PHOTO_URI)); - String contactLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL)); - int phoneType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE)); - Uri contactUri = ContactsContract.Contacts.getLookupUri(cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone._ID)), - cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY))); - - handle.setSystemContactInfo(recipientId, displayName, contactPhotoUri, contactLabel, phoneType, contactUri.toString()); + String lookupKey = getLookupKey(cursor); + String mimeType = getMimeType(cursor); + ContactHolder contactHolder = new ContactHolder(lookupKey); + + if (!isPhoneMimeType(mimeType)) { + Log.w(TAG, "Ignoring unexpected mime type: " + mimeType); + } + + while (getLookupKey(cursor).equals(lookupKey) && isPhoneMimeType(getMimeType(cursor))) { + String number = CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.NUMBER); + + if (isValidContactNumber(number)) { + String formattedNumber = PhoneNumberFormatter.get(context).format(number); + String realNumber = Util.getFirstNonEmpty(rewrites.get(formattedNumber), formattedNumber); + + PhoneNumberRecord.Builder builder = new PhoneNumberRecord.Builder(); + + builder.withRecipientId(Recipient.externalContact(context, realNumber).getId()); + builder.withDisplayName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + builder.withContactPhotoUri(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.PHOTO_URI)); + builder.withContactLabel(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.LABEL)); + builder.withPhoneType(CursorUtil.requireInt(cursor, ContactsContract.CommonDataKinds.Phone.TYPE)); + builder.withContactUri(ContactsContract.Contacts.getLookupUri(CursorUtil.requireLong(cursor, ContactsContract.CommonDataKinds.Phone._ID), + CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY))); + + contactHolder.addPhoneNumberRecord(builder.build()); + } else { + Log.w(TAG, "Skipping phone entry with invalid number"); + } + + cursor.moveToNext(); + } + + if (getLookupKey(cursor).equals(lookupKey)) { + if (isStructuredNameMimeType(getMimeType(cursor))) { + StructuredNameRecord.Builder builder = new StructuredNameRecord.Builder(); + + builder.withGivenName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)); + builder.withFamilyName(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)); + + contactHolder.setStructuredNameRecord(builder.build()); + } else { + Log.i(TAG, "Skipping invalid mimeType " + mimeType); + } + } else { + Log.i(TAG, "No structured name for user, rolling back cursor."); + cursor.moveToPrevious(); } + + contactHolder.commit(handle); } + } finally { handle.finish(); } @@ -358,10 +395,26 @@ private static void updateContactsDatabase(@NonNull Context context, } } + private static boolean isPhoneMimeType(@NonNull String mimeType) { + return ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType); + } + + private static boolean isStructuredNameMimeType(@NonNull String mimeType) { + return ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(mimeType); + } + private static boolean isValidContactNumber(@Nullable String number) { return !TextUtils.isEmpty(number) && !UuidUtil.isUuid(number); } + private static @NonNull String getLookupKey(@NonNull Cursor cursor) { + return Objects.requireNonNull(CursorUtil.requireString(cursor, ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY)); + } + + private static @NonNull String getMimeType(@NonNull Cursor cursor) { + return CursorUtil.requireString(cursor, ContactsContract.Data.MIMETYPE); + } + private static @Nullable AccountHolder getOrCreateSystemAccount(Context context) { AccountManager accountManager = AccountManager.get(context); Account[] accounts = accountManager.getAccountsByType(BuildConfig.APPLICATION_ID); diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/PhoneNumberRecord.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/PhoneNumberRecord.java new file mode 100644 index 00000000000..0393f894103 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/PhoneNumberRecord.java @@ -0,0 +1,99 @@ +package org.thoughtcrime.securesms.contacts.sync; + +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.thoughtcrime.securesms.recipients.RecipientId; + +import java.util.Objects; + +/** + * Represents all the data we pull from a Phone data cursor row from the contacts database. + */ +final class PhoneNumberRecord { + + private final RecipientId recipientId; + private final String displayName; + private final String contactPhotoUri; + private final String contactLabel; + private final int phoneType; + private final Uri contactUri; + + private PhoneNumberRecord(@NonNull PhoneNumberRecord.Builder builder) { + recipientId = Objects.requireNonNull(builder.recipientId); + displayName = builder.displayName; + contactPhotoUri = builder.contactPhotoUri; + contactLabel = builder.contactLabel; + phoneType = builder.phoneType; + contactUri = builder.contactUri; + } + + @NonNull RecipientId getRecipientId() { + return recipientId; + } + + @Nullable String getDisplayName() { + return displayName; + } + + @Nullable String getContactPhotoUri() { + return contactPhotoUri; + } + + @Nullable String getContactLabel() { + return contactLabel; + } + + int getPhoneType() { + return phoneType; + } + + @Nullable Uri getContactUri() { + return contactUri; + } + + final static class Builder { + private RecipientId recipientId; + private String displayName; + private String contactPhotoUri; + private String contactLabel; + private int phoneType; + private Uri contactUri; + + @NonNull Builder withRecipientId(@NonNull RecipientId recipientId) { + this.recipientId = recipientId; + return this; + } + + @NonNull Builder withDisplayName(@Nullable String displayName) { + this.displayName = displayName; + return this; + } + + @NonNull Builder withContactUri(@Nullable Uri contactUri) { + this.contactUri = contactUri; + return this; + } + + @NonNull Builder withContactLabel(@NonNull String contactLabel) { + this.contactLabel = contactLabel; + return this; + } + + @NonNull Builder withContactPhotoUri(@NonNull String contactPhotoUri) { + this.contactPhotoUri = contactPhotoUri; + return this; + } + + @NonNull Builder withPhoneType(int phoneType) { + this.phoneType = phoneType; + return this; + } + + @NonNull PhoneNumberRecord build() { + return new PhoneNumberRecord(this); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/StructuredNameRecord.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/StructuredNameRecord.java new file mode 100644 index 00000000000..bfcacd2a730 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/StructuredNameRecord.java @@ -0,0 +1,46 @@ +package org.thoughtcrime.securesms.contacts.sync; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.thoughtcrime.securesms.profiles.ProfileName; + +/** + * Represents the data pulled from a StructuredName row of a Contacts data cursor. + */ +final class StructuredNameRecord { + private final String givenName; + private final String familyName; + + StructuredNameRecord(@NonNull StructuredNameRecord.Builder builder) { + this.givenName = builder.givenName; + this.familyName = builder.familyName; + } + + public boolean hasGivenName() { + return givenName != null; + } + + public @NonNull ProfileName asProfileName() { + return ProfileName.fromParts(givenName, familyName); + } + + final static class Builder { + private String givenName; + private String familyName; + + @NonNull Builder withGivenName(@Nullable String givenName) { + this.givenName = givenName; + return this; + } + + @NonNull Builder withFamilyName(@Nullable String familyName) { + this.familyName = familyName; + return this; + } + + @NonNull StructuredNameRecord build() { + return new StructuredNameRecord(this); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 7a9a8061554..b63de248e28 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -39,7 +39,6 @@ import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; -import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.recipients.Recipient; @@ -113,7 +112,9 @@ public class RecipientDatabase extends Database { private static final String DEFAULT_SUBSCRIPTION_ID = "default_subscription_id"; private static final String MESSAGE_EXPIRATION_TIME = "message_expiration_time"; public static final String REGISTERED = "registered"; - public static final String SYSTEM_DISPLAY_NAME = "system_display_name"; + public static final String SYSTEM_JOINED_NAME = "system_display_name"; + public static final String SYSTEM_FAMILY_NAME = "system_family_name"; + public static final String SYSTEM_GIVEN_NAME = "system_given_name"; private static final String SYSTEM_PHOTO_URI = "system_photo_uri"; public static final String SYSTEM_PHONE_TYPE = "system_phone_type"; public static final String SYSTEM_PHONE_LABEL = "system_phone_label"; @@ -157,7 +158,7 @@ private static final class Capabilities { ID, UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE, BLOCKED, MESSAGE_RINGTONE, CALL_RINGTONE, MESSAGE_VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, MESSAGE_EXPIRATION_TIME, REGISTERED, PROFILE_KEY, PROFILE_KEY_CREDENTIAL, - SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, SYSTEM_CONTACT_URI, + SYSTEM_GIVEN_NAME, SYSTEM_FAMILY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, SYSTEM_CONTACT_URI, PROFILE_GIVEN_NAME, PROFILE_FAMILY_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, LAST_PROFILE_FETCH, NOTIFICATION_CHANNEL, UNIDENTIFIED_ACCESS_MODE, @@ -170,15 +171,15 @@ private static final class Capabilities { }; private static final String[] ID_PROJECTION = new String[]{ID}; - private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME}; - public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, SEARCH_PROFILE_NAME, SORT_NAME}; + private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(SYSTEM_GIVEN_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME}; + public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_JOINED_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, ABOUT, ABOUT_EMOJI, SEARCH_PROFILE_NAME, SORT_NAME}; private static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) .map(columnName -> TABLE_NAME + "." + columnName) .toList().toArray(new String[0]); static final String[] TYPED_RECIPIENT_PROJECTION_NO_ID = Arrays.copyOfRange(TYPED_RECIPIENT_PROJECTION, 1, TYPED_RECIPIENT_PROJECTION.length); - private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME}; + private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(SYSTEM_GIVEN_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME}; public static final String[] CREATE_INDEXS = new String[] { "CREATE INDEX IF NOT EXISTS recipient_dirty_index ON " + TABLE_NAME + " (" + DIRTY + ");", @@ -339,7 +340,9 @@ public static MentionSetting fromId(int id) { DEFAULT_SUBSCRIPTION_ID + " INTEGER DEFAULT -1, " + MESSAGE_EXPIRATION_TIME + " INTEGER DEFAULT 0, " + REGISTERED + " INTEGER DEFAULT " + RegisteredState.UNKNOWN.getId() + ", " + - SYSTEM_DISPLAY_NAME + " TEXT DEFAULT NULL, " + + SYSTEM_GIVEN_NAME + " TEXT DEFAULT NULL, " + + SYSTEM_FAMILY_NAME + " TEXT DEFAULT NULL, " + + SYSTEM_JOINED_NAME + " TEXT DEFAULT NULL, " + SYSTEM_PHOTO_URI + " TEXT DEFAULT NULL, " + SYSTEM_PHONE_LABEL + " TEXT DEFAULT NULL, " + SYSTEM_PHONE_TYPE + " INTEGER DEFAULT -1, " + @@ -1264,7 +1267,8 @@ public List getContactStorageSyncIds() { int registeredState = CursorUtil.requireInt(cursor, REGISTERED); String profileKeyString = CursorUtil.requireString(cursor, PROFILE_KEY); String profileKeyCredentialString = CursorUtil.requireString(cursor, PROFILE_KEY_CREDENTIAL); - String systemDisplayName = CursorUtil.requireString(cursor, SYSTEM_DISPLAY_NAME); + String systemGivenName = CursorUtil.requireString(cursor, SYSTEM_GIVEN_NAME); + String systemFamilyName = CursorUtil.requireString(cursor, SYSTEM_FAMILY_NAME); String systemContactPhoto = CursorUtil.requireString(cursor, SYSTEM_PHOTO_URI); String systemPhoneLabel = CursorUtil.requireString(cursor, SYSTEM_PHONE_LABEL); String systemContactUri = CursorUtil.requireString(cursor, SYSTEM_CONTACT_URI); @@ -1350,7 +1354,7 @@ public List getContactStorageSyncIds() { RegisteredState.fromId(registeredState), profileKey, profileKeyCredential, - systemDisplayName, + ProfileName.fromParts(systemGivenName, systemFamilyName), systemContactPhoto, systemPhoneLabel, systemContactUri, @@ -1743,7 +1747,7 @@ public Set persistProfileKeySet(@NonNull ProfileKeySet profileKeySe public @NonNull List getSimilarRecipientIds(@NonNull Recipient recipient) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); - String[] projection = SqlUtil.buildArgs(ID, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ") AS checked_name"); + String[] projection = SqlUtil.buildArgs(ID, "COALESCE(" + nullIfEmpty(SYSTEM_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ") AS checked_name"); String where = "checked_name = ?"; String[] arguments = SqlUtil.buildArgs(recipient.getProfileName().toString()); @@ -2246,7 +2250,7 @@ public List getSystemContacts() { SQLiteDatabase db = databaseHelper.getReadableDatabase(); List results = new LinkedList<>(); - try (Cursor cursor = db.query(TABLE_NAME, ID_PROJECTION, SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"", null, null, null, null)) { + try (Cursor cursor = db.query(TABLE_NAME, ID_PROJECTION, SYSTEM_JOINED_NAME + " IS NOT NULL AND " + SYSTEM_JOINED_NAME + " != \"\"", null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { results.add(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))); } @@ -2260,10 +2264,10 @@ public void updateSystemContactColors(@NonNull ColorUpdater updater) { Map updates = new HashMap<>(); db.beginTransaction(); - try (Cursor cursor = db.query(TABLE_NAME, new String[] {ID, COLOR, SYSTEM_DISPLAY_NAME}, SYSTEM_DISPLAY_NAME + " IS NOT NULL AND " + SYSTEM_DISPLAY_NAME + " != \"\"", null, null, null, null)) { + try (Cursor cursor = db.query(TABLE_NAME, new String[] {ID, COLOR, SYSTEM_JOINED_NAME}, SYSTEM_JOINED_NAME + " IS NOT NULL AND " + SYSTEM_JOINED_NAME + " != \"\"", null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); - MaterialColor newColor = updater.update(cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)), + MaterialColor newColor = updater.update(cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_JOINED_NAME)), cursor.getString(cursor.getColumnIndexOrThrow(COLOR))); ContentValues contentValues = new ContentValues(1); @@ -2284,7 +2288,7 @@ public void updateSystemContactColors(@NonNull ColorUpdater updater) { String selection = BLOCKED + " = ? AND " + REGISTERED + " = ? AND " + GROUP_ID + " IS NULL AND " + - "(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " + + "(" + SYSTEM_JOINED_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " + "(" + SORT_NAME + " NOT NULL OR " + USERNAME + " NOT NULL)"; String[] args; @@ -2295,7 +2299,7 @@ public void updateSystemContactColors(@NonNull ColorUpdater updater) { args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), "1", Recipient.self().getId().serialize() }; } - String orderBy = SORT_NAME + ", " + SYSTEM_DISPLAY_NAME + ", " + SEARCH_PROFILE_NAME + ", " + USERNAME + ", " + PHONE; + String orderBy = SORT_NAME + ", " + SYSTEM_JOINED_NAME + ", " + SEARCH_PROFILE_NAME + ", " + USERNAME + ", " + PHONE; return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy); } @@ -2306,7 +2310,7 @@ public void updateSystemContactColors(@NonNull ColorUpdater updater) { String selection = BLOCKED + " = ? AND " + REGISTERED + " = ? AND " + GROUP_ID + " IS NULL AND " + - "(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " + + "(" + SYSTEM_JOINED_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " + "(" + PHONE + " GLOB ? OR " + SORT_NAME + " GLOB ? OR " + @@ -2321,7 +2325,7 @@ public void updateSystemContactColors(@NonNull ColorUpdater updater) { args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), "1", query, query, query, String.valueOf(Recipient.self().getId().toLong()) }; } - String orderBy = SORT_NAME + ", " + SYSTEM_DISPLAY_NAME + ", " + SEARCH_PROFILE_NAME + ", " + PHONE; + String orderBy = SORT_NAME + ", " + SYSTEM_JOINED_NAME + ", " + SEARCH_PROFILE_NAME + ", " + PHONE; return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy); } @@ -2330,10 +2334,10 @@ public void updateSystemContactColors(@NonNull ColorUpdater updater) { String selection = BLOCKED + " = ? AND " + REGISTERED + " != ? AND " + GROUP_ID + " IS NULL AND " + - SYSTEM_DISPLAY_NAME + " NOT NULL AND " + + SYSTEM_CONTACT_URI + " NOT NULL AND " + "(" + PHONE + " NOT NULL OR " + EMAIL + " NOT NULL)"; String[] args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()) }; - String orderBy = SYSTEM_DISPLAY_NAME + ", " + PHONE; + String orderBy = SYSTEM_JOINED_NAME + ", " + PHONE; return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy); } @@ -2344,15 +2348,15 @@ public void updateSystemContactColors(@NonNull ColorUpdater updater) { String selection = BLOCKED + " = ? AND " + REGISTERED + " != ? AND " + GROUP_ID + " IS NULL AND " + - SYSTEM_DISPLAY_NAME + " NOT NULL AND " + + SYSTEM_CONTACT_URI + " NOT NULL AND " + "(" + PHONE + " NOT NULL OR " + EMAIL + " NOT NULL) AND " + "(" + - PHONE + " GLOB ? OR " + - EMAIL + " GLOB ? OR " + - SYSTEM_DISPLAY_NAME + " GLOB ?" + + PHONE + " GLOB ? OR " + + EMAIL + " GLOB ? OR " + + SYSTEM_JOINED_NAME + " GLOB ?" + ")"; String[] args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), query, query, query }; - String orderBy = SYSTEM_DISPLAY_NAME + ", " + PHONE; + String orderBy = SYSTEM_JOINED_NAME + ", " + PHONE; return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, orderBy); } @@ -2429,7 +2433,7 @@ private static String buildCaseInsensitiveGlobPattern(@NonNull String query) { String selection = REGISTERED + " = ? AND " + GROUP_ID + " IS NULL AND " + ID + " != ? AND " + - "(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + ID + " IN (" + subquery + "))"; + "(" + SYSTEM_CONTACT_URI + " NOT NULL OR " + ID + " IN (" + subquery + "))"; String[] args = new String[] { String.valueOf(RegisteredState.REGISTERED.getId()), Recipient.self().getId().serialize() }; List recipients = new ArrayList<>(); @@ -2729,7 +2733,9 @@ private boolean update(@NonNull SqlUtil.Query updateQuery, @NonNull ContentValue uuidValues.put(DEFAULT_SUBSCRIPTION_ID, e164Settings.getDefaultSubscriptionId().or(-1)); uuidValues.put(MESSAGE_EXPIRATION_TIME, uuidSettings.getExpireMessages() > 0 ? uuidSettings.getExpireMessages() : e164Settings.getExpireMessages()); uuidValues.put(REGISTERED, RegisteredState.REGISTERED.getId()); - uuidValues.put(SYSTEM_DISPLAY_NAME, e164Settings.getSystemDisplayName()); + uuidValues.put(SYSTEM_GIVEN_NAME, e164Settings.getSystemProfileName().getGivenName()); + uuidValues.put(SYSTEM_FAMILY_NAME, e164Settings.getSystemProfileName().getFamilyName()); + uuidValues.put(SYSTEM_JOINED_NAME, e164Settings.getSystemProfileName().toString()); uuidValues.put(SYSTEM_PHOTO_URI, e164Settings.getSystemContactPhotoUri()); uuidValues.put(SYSTEM_PHONE_LABEL, e164Settings.getSystemPhoneLabel()); uuidValues.put(SYSTEM_CONTACT_URI, e164Settings.getSystemContactUri()); @@ -2840,14 +2846,16 @@ public class BulkOperationsHandle { } public void setSystemContactInfo(@NonNull RecipientId id, - @Nullable String displayName, + @NonNull ProfileName systemProfileName, @Nullable String photoUri, @Nullable String systemPhoneLabel, int systemPhoneType, @Nullable String systemContactUri) { ContentValues dirtyQualifyingValues = new ContentValues(); - dirtyQualifyingValues.put(SYSTEM_DISPLAY_NAME, displayName); + dirtyQualifyingValues.put(SYSTEM_GIVEN_NAME, systemProfileName.getGivenName()); + dirtyQualifyingValues.put(SYSTEM_FAMILY_NAME, systemProfileName.getFamilyName()); + dirtyQualifyingValues.put(SYSTEM_JOINED_NAME, systemProfileName.toString()); if (update(id, dirtyQualifyingValues)) { markDirty(id, DirtyState.UPDATE); @@ -2859,11 +2867,12 @@ public void setSystemContactInfo(@NonNull RecipientId id, refreshQualifyingValues.put(SYSTEM_PHONE_TYPE, systemPhoneType); refreshQualifyingValues.put(SYSTEM_CONTACT_URI, systemContactUri); + String joinedName = systemProfileName.toString(); boolean updatedValues = update(id, refreshQualifyingValues); - boolean updatedColor = displayName != null && setColorIfNotSetInternal(id, ContactColors.generateFor(displayName)); + boolean updatedColor = !TextUtils.isEmpty(joinedName) && setColorIfNotSetInternal(id, ContactColors.generateFor(joinedName)); if (updatedValues || updatedColor) { - pendingContactInfoMap.put(id, new PendingContactInfo(displayName, photoUri, systemPhoneLabel, systemContactUri)); + pendingContactInfoMap.put(id, new PendingContactInfo(systemProfileName, photoUri, systemPhoneLabel, systemContactUri)); } ContentValues otherValues = new ContentValues(); @@ -2898,7 +2907,9 @@ private void clearSystemDataForPendingInfo() { ContentValues values = new ContentValues(5); values.put(SYSTEM_INFO_PENDING, 0); - values.put(SYSTEM_DISPLAY_NAME, (String) null); + values.put(SYSTEM_GIVEN_NAME, (String) null); + values.put(SYSTEM_FAMILY_NAME, (String) null); + values.put(SYSTEM_JOINED_NAME, (String) null); values.put(SYSTEM_PHOTO_URI, (String) null); values.put(SYSTEM_PHONE_LABEL, (String) null); values.put(SYSTEM_CONTACT_URI, (String) null); @@ -2939,7 +2950,7 @@ public static class RecipientSettings { private final RegisteredState registered; private final byte[] profileKey; private final ProfileKeyCredential profileKeyCredential; - private final String systemDisplayName; + private final ProfileName systemProfileName; private final String systemContactPhoto; private final String systemPhoneLabel; private final String systemContactUri; @@ -2981,7 +2992,7 @@ public static class RecipientSettings { @NonNull RegisteredState registered, @Nullable byte[] profileKey, @Nullable ProfileKeyCredential profileKeyCredential, - @Nullable String systemDisplayName, + @NonNull ProfileName systemProfileName, @Nullable String systemContactPhoto, @Nullable String systemPhoneLabel, @Nullable String systemContactUri, @@ -3021,7 +3032,7 @@ public static class RecipientSettings { this.registered = registered; this.profileKey = profileKey; this.profileKeyCredential = profileKeyCredential; - this.systemDisplayName = systemDisplayName; + this.systemProfileName = systemProfileName; this.systemContactPhoto = systemContactPhoto; this.systemPhoneLabel = systemPhoneLabel; this.systemContactUri = systemContactUri; @@ -3125,8 +3136,8 @@ public RegisteredState getRegistered() { return profileKeyCredential; } - public @Nullable String getSystemDisplayName() { - return systemDisplayName; + public @NonNull ProfileName getSystemProfileName() { + return systemProfileName; } public @Nullable String getSystemContactPhotoUri() { @@ -3313,13 +3324,13 @@ public boolean requiresDirectoryRefresh() { private static class PendingContactInfo { - private final String displayName; - private final String photoUri; - private final String phoneLabel; - private final String contactUri; + private final ProfileName profileName; + private final String photoUri; + private final String phoneLabel; + private final String contactUri; - private PendingContactInfo(String displayName, String photoUri, String phoneLabel, String contactUri) { - this.displayName = displayName; + private PendingContactInfo(@NonNull ProfileName systemProfileName, String photoUri, String phoneLabel, String contactUri) { + this.profileName = systemProfileName; this.photoUri = photoUri; this.phoneLabel = phoneLabel; this.contactUri = contactUri; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 6bc81894509..95bcbdfe4d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -171,8 +171,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab private static final int LAST_RESET_SESSION_TIME = 87; private static final int WALLPAPER = 88; private static final int ABOUT = 89; + private static final int SPLIT_SYSTEM_NAMES = 90; - private static final int DATABASE_VERSION = 89; + private static final int DATABASE_VERSION = 90; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -1258,6 +1259,12 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("ALTER TABLE recipient ADD COLUMN about_emoji TEXT DEFAULT NULL"); } + if (oldVersion < SPLIT_SYSTEM_NAMES) { + db.execSQL("ALTER TABLE recipient ADD COLUMN system_family_name TEXT DEFAULT NULL"); + db.execSQL("ALTER TABLE recipient ADD COLUMN system_given_name TEXT DEFAULT NULL"); + db.execSQL("UPDATE recipient SET system_given_name = system_display_name"); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java index cab776f0129..467226ee846 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/insights/InsightsRepository.java @@ -66,7 +66,7 @@ public void getInsecureRecipients(@NonNull Consumer> insecureRec public void getUserAvatar(@NonNull Consumer avatarConsumer) { SimpleTask.run(() -> { Recipient self = Recipient.self().resolve(); - String name = Optional.fromNullable(self.getName(context)).or(""); + String name = Optional.fromNullable(self.getDisplayName(context)).or(""); MaterialColor fallbackColor = self.getColor(); if (fallbackColor == ContactColors.UNKNOWN_COLOR && !TextUtils.isEmpty(name)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java index 9fe83a54850..aa90945a0a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceContactUpdateJob.java @@ -136,7 +136,7 @@ private void generateSingleContactUpdate(@NonNull RecipientId recipientId) Set archived = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients(); out.write(new DeviceContact(RecipientUtil.toSignalServiceAddress(context, recipient), - Optional.fromNullable(recipient.getName(context)), + Optional.fromNullable(recipient.isGroup() || recipient.isSystemContact() ? recipient.getDisplayName(context) : null), getAvatar(recipient.getId(), recipient.getContactUri()), Optional.fromNullable(recipient.getColor().serialize()), verifiedMessage, @@ -191,7 +191,7 @@ private void generateFullContactUpdate() for (Recipient recipient : recipients) { Optional identity = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()); Optional verified = getVerifiedMessage(recipient, identity); - Optional name = Optional.fromNullable(recipient.getName(context)); + Optional name = Optional.fromNullable(recipient.isSystemContact() ? recipient.getDisplayName(context) : recipient.getGroupName(context)); Optional color = Optional.of(recipient.getColor().serialize()); Optional profileKey = ProfileKeyUtil.profileKeyOptional(recipient.getProfileKey()); boolean blocked = recipient.isBlocked(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index 1412c4a806b..3b5b2eaa76b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -70,6 +70,7 @@ private static final class Version { static final int USER_NOTIFICATION = 25; static final int DAY_BY_DAY_STICKERS = 26; static final int BLOB_LOCATION = 27; + static final int SYSTEM_NAME_SPLIT = 28; } /** @@ -296,6 +297,10 @@ private static LinkedHashMap getMigrationJobs(@NonNull Co jobs.put(Version.BLOB_LOCATION, new BlobStorageLocationMigrationJob()); } + if (lastSeenVersion < Version.SYSTEM_NAME_SPLIT) { + jobs.put(Version.SYSTEM_NAME_SPLIT, new DirectoryRefreshMigrationJob()); + } + return jobs; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java index daca12f4597..dba8634f131 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java @@ -89,6 +89,13 @@ public boolean isGivenNameEmpty() { } } + /** + * Creates a profile name that only contains a given name. + */ + public static @NonNull ProfileName asGiven(@Nullable String givenName) { + return fromParts(givenName, null); + } + /** * Creates a profile name, trimming chars until it fits the limits. */ diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditGroupProfileRepository.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditGroupProfileRepository.java index 39a81600c6d..3951d399787 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditGroupProfileRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditGroupProfileRepository.java @@ -74,7 +74,7 @@ public void getCurrentName(@NonNull Consumer nameConsumer) { String title = groupRecord.getTitle(); return title == null ? "" : title; }) - .or(() -> recipient.getName(context)); + .or(() -> recipient.getGroupName(context)); }, nameConsumer::accept); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java index e9da73848d7..c2159827b32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -89,11 +89,11 @@ public class Recipient { private final RegisteredState registered; private final byte[] profileKey; private final ProfileKeyCredential profileKeyCredential; - private final String name; + private final String groupName; private final Uri systemContactPhoto; private final String customLabel; private final Uri contactUri; - private final ProfileName profileName; + private final ProfileName signalProfileName; private final String profileAvatar; private final boolean hasProfileImage; private final boolean profileSharing; @@ -109,6 +109,7 @@ public class Recipient { private final ChatWallpaper wallpaper; private final String about; private final String aboutEmoji; + private final ProfileName systemProfileName; /** @@ -326,11 +327,11 @@ public class Recipient { this.registered = RegisteredState.UNKNOWN; this.profileKey = null; this.profileKeyCredential = null; - this.name = null; + this.groupName = null; this.systemContactPhoto = null; this.customLabel = null; this.contactUri = null; - this.profileName = ProfileName.EMPTY; + this.signalProfileName = ProfileName.EMPTY; this.profileAvatar = null; this.hasProfileImage = false; this.profileSharing = false; @@ -345,6 +346,7 @@ public class Recipient { this.wallpaper = null; this.about = null; this.aboutEmoji = null; + this.systemProfileName = ProfileName.EMPTY; } public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) { @@ -371,11 +373,11 @@ public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boo this.registered = details.registered; this.profileKey = details.profileKey; this.profileKeyCredential = details.profileKeyCredential; - this.name = details.name; + this.groupName = details.groupName; this.systemContactPhoto = details.systemContactPhoto; this.customLabel = details.customLabel; this.contactUri = details.contactUri; - this.profileName = details.profileName; + this.signalProfileName = details.profileName; this.profileAvatar = details.profileAvatar; this.hasProfileImage = details.hasProfileImage; this.profileSharing = details.profileSharing; @@ -390,6 +392,7 @@ public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boo this.wallpaper = details.wallpaper; this.about = details.about; this.aboutEmoji = details.aboutEmoji; + this.systemProfileName = details.systemProfileName; } public @NonNull RecipientId getId() { @@ -404,8 +407,8 @@ public boolean isSelf() { return contactUri; } - public @Nullable String getName(@NonNull Context context) { - if (this.name == null && groupId != null && groupId.isMms()) { + public @Nullable String getGroupName(@NonNull Context context) { + if (this.groupName == null && groupId != null && groupId.isMms()) { List names = new LinkedList<>(); for (Recipient recipient : participants) { @@ -413,27 +416,32 @@ public boolean isSelf() { } return Util.join(names, ", "); - } else if (name == null && groupId != null && groupId.isPush()) { + } else if (groupName == null && groupId != null && groupId.isPush()) { return context.getString(R.string.RecipientProvider_unnamed_group); } else { - return this.name; + return this.groupName; } } public boolean hasName() { - return name != null; + return groupName != null; } /** * False iff it {@link #getDisplayName} would fall back to e164, email or unknown. */ public boolean hasAUserSetDisplayName(@NonNull Context context) { - return !TextUtils.isEmpty(getName(context)) || + return !TextUtils.isEmpty(getGroupName(context)) || + !TextUtils.isEmpty(getSystemProfileName().toString()) || !TextUtils.isEmpty(getProfileName().toString()); } public @NonNull String getDisplayName(@NonNull Context context) { - String name = getName(context); + String name = getGroupName(context); + + if (Util.isEmpty(name)) { + name = getSystemProfileName().toString(); + } if (Util.isEmpty(name)) { name = getProfileName().toString(); @@ -455,7 +463,11 @@ public boolean hasAUserSetDisplayName(@NonNull Context context) { } public @NonNull String getDisplayNameOrUsername(@NonNull Context context) { - String name = getName(context); + String name = getGroupName(context); + + if (Util.isEmpty(name)) { + name = getSystemProfileName().toString(); + } if (Util.isEmpty(name)) { name = StringUtil.isolateBidi(getProfileName().toString()); @@ -481,11 +493,16 @@ public boolean hasAUserSetDisplayName(@NonNull Context context) { } public @NonNull String getMentionDisplayName(@NonNull Context context) { - String name = isSelf ? getProfileName().toString() : getName(context); + String name = isSelf ? getProfileName().toString() : getGroupName(context); name = StringUtil.isolateBidi(name); if (Util.isEmpty(name)) { - name = isSelf ? getName(context) : getProfileName().toString(); + name = isSelf ? getGroupName(context) : getSystemProfileName().toString(); + name = StringUtil.isolateBidi(name); + } + + if (Util.isEmpty(name)) { + name = isSelf ? getGroupName(context) : getProfileName().toString(); name = StringUtil.isolateBidi(name); } @@ -505,7 +522,8 @@ public boolean hasAUserSetDisplayName(@NonNull Context context) { } public @NonNull String getShortDisplayName(@NonNull Context context) { - String name = Util.getFirstNonEmpty(getName(context), + String name = Util.getFirstNonEmpty(getGroupName(context), + getSystemProfileName().getGivenName(), getProfileName().getGivenName(), getDisplayName(context)); @@ -513,7 +531,8 @@ public boolean hasAUserSetDisplayName(@NonNull Context context) { } public @NonNull String getShortDisplayNameIncludingUsername(@NonNull Context context) { - String name = Util.getFirstNonEmpty(getName(context), + String name = Util.getFirstNonEmpty(getGroupName(context), + getSystemProfileName().getGivenName(), getProfileName().getGivenName(), getDisplayName(context), getUsername().orNull()); @@ -526,7 +545,7 @@ public boolean hasAUserSetDisplayName(@NonNull Context context) { return MaterialColor.GROUP; } else if (color != null) { return color; - } else if (name != null || profileSharing) { + } else if (groupName != null || profileSharing) { Log.w(TAG, "Had no color for " + id + "! Saving a new one."); Context context = ApplicationDependencies.getApplication(); @@ -672,7 +691,11 @@ public Optional getDefaultSubscriptionId() { } public @NonNull ProfileName getProfileName() { - return profileName; + return signalProfileName; + } + + private @NonNull ProfileName getSystemProfileName() { + return systemProfileName; } public @Nullable String getProfileAvatar() { @@ -744,12 +767,12 @@ public boolean isActiveGroup() { } public @NonNull FallbackContactPhoto getFallbackContactPhoto(@NonNull FallbackPhotoProvider fallbackPhotoProvider) { - if (isSelf) return fallbackPhotoProvider.getPhotoForLocalNumber(); - else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient(); - else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup(); - else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup(); - else if (!TextUtils.isEmpty(name)) return fallbackPhotoProvider.getPhotoForRecipientWithName(name); - else return fallbackPhotoProvider.getPhotoForRecipientWithoutName(); + if (isSelf) return fallbackPhotoProvider.getPhotoForLocalNumber(); + else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient(); + else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup(); + else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup(); + else if (!TextUtils.isEmpty(groupName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(groupName); + else return fallbackPhotoProvider.getPhotoForRecipientWithoutName(); } public @Nullable ContactPhoto getContactPhoto() { @@ -1003,11 +1026,12 @@ public boolean hasSameContent(@NonNull Recipient other) { registered == other.registered && Arrays.equals(profileKey, other.profileKey) && Objects.equals(profileKeyCredential, other.profileKeyCredential) && - Objects.equals(name, other.name) && + Objects.equals(groupName, other.groupName) && Objects.equals(systemContactPhoto, other.systemContactPhoto) && Objects.equals(customLabel, other.customLabel) && Objects.equals(contactUri, other.contactUri) && - Objects.equals(profileName, other.profileName) && + Objects.equals(signalProfileName, other.signalProfileName) && + Objects.equals(systemProfileName, other.systemProfileName) && Objects.equals(profileAvatar, other.profileAvatar) && Objects.equals(notificationChannel, other.notificationChannel) && unidentifiedAccessMode == other.unidentifiedAccessMode && diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java index d8af3b51af4..284fcdd4e48 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -2,7 +2,6 @@ import android.content.Context; import android.net.Uri; -import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -33,7 +32,7 @@ public class RecipientDetails { final String e164; final String email; final GroupId groupId; - final String name; + final String groupName; final String customLabel; final Uri systemContactPhoto; final Uri contactUri; @@ -69,8 +68,9 @@ public class RecipientDetails { final ChatWallpaper wallpaper; final String about; final String aboutEmoji; + final ProfileName systemProfileName; - public RecipientDetails(@Nullable String name, + public RecipientDetails(@Nullable String groupName, @NonNull Optional groupAvatarId, boolean systemContact, boolean isSelf, @@ -117,9 +117,8 @@ public RecipientDetails(@Nullable String name, this.wallpaper = settings.getWallpaper(); this.about = settings.getAbout(); this.aboutEmoji = settings.getAboutEmoji(); - - if (name == null) this.name = settings.getSystemDisplayName(); - else this.name = name; + this.systemProfileName = settings.getSystemProfileName(); + this.groupName = groupName; } /** @@ -159,7 +158,7 @@ public RecipientDetails(@Nullable String name, this.notificationChannel = null; this.unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN; this.forceSmsSelection = false; - this.name = null; + this.groupName = null; this.groupsV2Capability = Recipient.Capability.UNKNOWN; this.groupsV1MigrationCapability = Recipient.Capability.UNKNOWN; this.storageId = null; @@ -167,10 +166,11 @@ public RecipientDetails(@Nullable String name, this.wallpaper = null; this.about = null; this.aboutEmoji = null; + this.systemProfileName = ProfileName.EMPTY; } public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientSettings settings) { - boolean systemContact = !TextUtils.isEmpty(settings.getSystemDisplayName()); + boolean systemContact = !settings.getSystemProfileName().isEmpty(); boolean isSelf = (settings.getE164() != null && settings.getE164().equals(TextSecurePreferences.getLocalNumber(context))) || (settings.getUuid() != null && settings.getUuid().equals(TextSecurePreferences.getLocalUuid(context))); From e7f233db5b9499fdb8b7bbc779006129d0daeb38 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 3 Mar 2021 16:03:49 -0500 Subject: [PATCH 06/24] Add Device Transfer via WiFi Direct groundwork. --- .../java/org/signal/core/util/ThreadUtil.java | 6 + device-transfer/app/build.gradle | 30 ++ .../app/src/main/AndroidManifest.xml | 22 + .../devicetransfer/app/MainActivity.java | 152 +++++++ .../drawable-v24/ic_launcher_foreground.xml | 31 ++ .../res/drawable/ic_launcher_background.xml | 171 +++++++ .../src/main/res/drawable/ic_refresh_20.xml | 9 + .../app/src/main/res/layout/activity_main.xml | 58 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3593 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5339 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2636 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3388 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4926 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7472 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7909 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 11873 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10652 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 16570 bytes .../app/src/main/res/values-night/themes.xml | 16 + .../app/src/main/res/values/colors.xml | 10 + .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/themes.xml | 16 + device-transfer/lib/build.gradle | 30 ++ .../lib/src/main/AndroidManifest.xml | 20 + .../org/signal/devicetransfer/ClientTask.java | 22 + .../DeviceToDeviceTransferService.java | 248 +++++++++++ .../devicetransfer/DeviceTransferClient.java | 339 ++++++++++++++ .../devicetransfer/DeviceTransferServer.java | 290 ++++++++++++ .../org/signal/devicetransfer/IpExchange.java | 148 +++++++ .../org/signal/devicetransfer/ServerTask.java | 22 + .../devicetransfer/ShutdownCallback.java | 10 + .../signal/devicetransfer/TransferMode.java | 12 + .../org/signal/devicetransfer/WifiDirect.java | 416 ++++++++++++++++++ .../WifiDirectUnavailableException.java | 29 ++ .../lib/witness-verifications.gradle | 105 +++++ settings.gradle | 5 + 38 files changed, 2232 insertions(+) create mode 100644 device-transfer/app/build.gradle create mode 100644 device-transfer/app/src/main/AndroidManifest.xml create mode 100644 device-transfer/app/src/main/java/org/signal/devicetransfer/app/MainActivity.java create mode 100644 device-transfer/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 device-transfer/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 device-transfer/app/src/main/res/drawable/ic_refresh_20.xml create mode 100644 device-transfer/app/src/main/res/layout/activity_main.xml create mode 100644 device-transfer/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 device-transfer/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 device-transfer/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 device-transfer/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 device-transfer/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 device-transfer/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 device-transfer/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 device-transfer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 device-transfer/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 device-transfer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 device-transfer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 device-transfer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 device-transfer/app/src/main/res/values-night/themes.xml create mode 100644 device-transfer/app/src/main/res/values/colors.xml create mode 100644 device-transfer/app/src/main/res/values/strings.xml create mode 100644 device-transfer/app/src/main/res/values/themes.xml create mode 100644 device-transfer/lib/build.gradle create mode 100644 device-transfer/lib/src/main/AndroidManifest.xml create mode 100644 device-transfer/lib/src/main/java/org/signal/devicetransfer/ClientTask.java create mode 100644 device-transfer/lib/src/main/java/org/signal/devicetransfer/DeviceToDeviceTransferService.java create mode 100644 device-transfer/lib/src/main/java/org/signal/devicetransfer/DeviceTransferClient.java create mode 100644 device-transfer/lib/src/main/java/org/signal/devicetransfer/DeviceTransferServer.java create mode 100644 device-transfer/lib/src/main/java/org/signal/devicetransfer/IpExchange.java create mode 100644 device-transfer/lib/src/main/java/org/signal/devicetransfer/ServerTask.java create mode 100644 device-transfer/lib/src/main/java/org/signal/devicetransfer/ShutdownCallback.java create mode 100644 device-transfer/lib/src/main/java/org/signal/devicetransfer/TransferMode.java create mode 100644 device-transfer/lib/src/main/java/org/signal/devicetransfer/WifiDirect.java create mode 100644 device-transfer/lib/src/main/java/org/signal/devicetransfer/WifiDirectUnavailableException.java create mode 100644 device-transfer/lib/witness-verifications.gradle diff --git a/core-util/src/main/java/org/signal/core/util/ThreadUtil.java b/core-util/src/main/java/org/signal/core/util/ThreadUtil.java index 54d68ba432a..7c2b02d39b1 100644 --- a/core-util/src/main/java/org/signal/core/util/ThreadUtil.java +++ b/core-util/src/main/java/org/signal/core/util/ThreadUtil.java @@ -87,4 +87,10 @@ public static void sleep(long millis) { throw new AssertionError(e); } } + + public static void interruptableSleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException ignored) { } + } } diff --git a/device-transfer/app/build.gradle b/device-transfer/app/build.gradle new file mode 100644 index 00000000000..6bbc6ff010d --- /dev/null +++ b/device-transfer/app/build.gradle @@ -0,0 +1,30 @@ +apply plugin: 'com.android.application' + +android { + buildToolsVersion BUILD_TOOL_VERSION + compileSdkVersion COMPILE_SDK + + defaultConfig { + applicationId "org.signal.devicetransfer.app" + versionCode 1 + versionName "1.0" + + minSdkVersion MINIMUM_SDK + targetSdkVersion TARGET_SDK + } + + compileOptions { + sourceCompatibility JAVA_VERSION + targetCompatibility JAVA_VERSION + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.2.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + + testImplementation 'junit:junit:4.12' + + implementation project(':device-transfer') +} diff --git a/device-transfer/app/src/main/AndroidManifest.xml b/device-transfer/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..f52af0ad3d4 --- /dev/null +++ b/device-transfer/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/device-transfer/app/src/main/java/org/signal/devicetransfer/app/MainActivity.java b/device-transfer/app/src/main/java/org/signal/devicetransfer/app/MainActivity.java new file mode 100644 index 00000000000..2f19ea0c3ed --- /dev/null +++ b/device-transfer/app/src/main/java/org/signal/devicetransfer/app/MainActivity.java @@ -0,0 +1,152 @@ +package org.signal.devicetransfer.app; + +import android.Manifest; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.NotificationManagerCompat; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; +import org.signal.devicetransfer.ClientTask; +import org.signal.devicetransfer.DeviceToDeviceTransferService; +import org.signal.devicetransfer.DeviceToDeviceTransferService.TransferNotificationData; +import org.signal.devicetransfer.ServerTask; +import org.signal.devicetransfer.TransferMode; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Random; + +public class MainActivity extends AppCompatActivity { + + private static final String TRANSFER_NOTIFICATION_CHANNEL = "DEVICE_TO_DEVICE_TRANSFER"; + + private LinearLayout list; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + if (Build.VERSION.SDK_INT > 26) { + NotificationChannel deviceTransfer = new NotificationChannel(TRANSFER_NOTIFICATION_CHANNEL, "Device Transfer", NotificationManager.IMPORTANCE_DEFAULT); + NotificationManagerCompat.from(this).createNotificationChannel(deviceTransfer); + } + + list = findViewById(R.id.list); + + final TransferNotificationData data = new TransferNotificationData(1337, + TRANSFER_NOTIFICATION_CHANNEL, + R.drawable.ic_refresh_20); + + findViewById(R.id.start_server).setOnClickListener(v -> { + DeviceToDeviceTransferService.startServer(this, + 8888, + new ServerReceiveRandomBytes(), + data, + PendingIntent.getActivity(this, + 0, + new Intent(this, MainActivity.class), + 0)); + + list.removeAllViews(); + }); + + findViewById(R.id.start_client).setOnClickListener(v -> { + DeviceToDeviceTransferService.startClient(this, + 8888, + new ClientSendRandomBytes(), + data, + PendingIntent.getActivity(this, + 0, + new Intent(this, MainActivity.class), + 0)); + + list.removeAllViews(); + }); + + findViewById(R.id.stop).setOnClickListener(v -> { + DeviceToDeviceTransferService.stop(this); + }); + + findViewById(R.id.enable_permission).setOnClickListener(v -> { + if (Build.VERSION.SDK_INT >= 23 && checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 420); + } + }); + + EventBus.getDefault().register(this); + } + + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) + public void onEventMainThread(@NonNull TransferMode event) { + TextView text = new TextView(this); + text.setText(event.toString()); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + text.setLayoutParams(params); + list.addView(text); + } + + private static class ClientSendRandomBytes implements ClientTask { + + private static final String TAG = "ClientSend"; + + private final int rounds = 1000; + + @Override + public void run(@NonNull Context context, @NonNull OutputStream outputStream) throws IOException { + Random r = new Random(System.currentTimeMillis()); + byte[] data = new byte[8192]; + r.nextBytes(data); + + long start = System.currentTimeMillis(); + Log.i(TAG, "Sending " + ((data.length * rounds) / 1024 / 1024) + "MB of random data!!!"); + for (int i = 0; i < rounds; i++) { + outputStream.write(data); + outputStream.flush(); + } + long end = System.currentTimeMillis(); + Log.i(TAG, "Sending took: " + (end - start)); + } + } + + private static class ServerReceiveRandomBytes implements ServerTask { + + private static final String TAG = "ServerReceive"; + + @Override + public void run(@NonNull Context context, @NonNull InputStream inputStream) throws IOException { + long start = System.currentTimeMillis(); + byte[] data = new byte[8192]; + int result = 0; + + int i = 0; + Log.i(TAG, "Start drinking from the fire hose!"); + while (result >= 0) { + result = inputStream.read(data, 0, 8192); + i++; + if (i % 10000 == 0) { + Log.i(TAG, "Round: " + i); + } + } + long end = System.currentTimeMillis(); + Log.i(TAG, "Receive took: " + (end - start)); + } + } +} diff --git a/device-transfer/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/device-transfer/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000000..18c0565852e --- /dev/null +++ b/device-transfer/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/device-transfer/app/src/main/res/drawable/ic_launcher_background.xml b/device-transfer/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000000..c71b77f4d29 --- /dev/null +++ b/device-transfer/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/device-transfer/app/src/main/res/drawable/ic_refresh_20.xml b/device-transfer/app/src/main/res/drawable/ic_refresh_20.xml new file mode 100644 index 00000000000..915e0bbbca2 --- /dev/null +++ b/device-transfer/app/src/main/res/drawable/ic_refresh_20.xml @@ -0,0 +1,9 @@ + + + diff --git a/device-transfer/app/src/main/res/layout/activity_main.xml b/device-transfer/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000000..3a767e8ba71 --- /dev/null +++ b/device-transfer/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,58 @@ + + + + +