diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 56b5697f9..c5ff890c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -35,8 +35,6 @@ import org.conscrypt.Conscrypt; import org.signal.aesgcmprovider.AesGcmProvider; import org.signal.glide.SignalGlideCodecs; import org.signal.ringrtc.CallManager; -import org.thoughtcrime.securesms.components.TypingStatusRepository; -import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -70,8 +68,8 @@ import org.thoughtcrime.securesms.service.RotateSenderCertificateListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.UpdateApkRefreshListener; import org.thoughtcrime.securesms.storage.StorageSyncHelper; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.tracing.Trace; +import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -99,11 +97,9 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi private static final String TAG = ApplicationContext.class.getSimpleName(); - private ExpiringMessageManager expiringMessageManager; - private ViewOnceMessageManager viewOnceMessageManager; - private TypingStatusRepository typingStatusRepository; - private TypingStatusSender typingStatusSender; - private PersistentLogger persistentLogger; + private ExpiringMessageManager expiringMessageManager; + private ViewOnceMessageManager viewOnceMessageManager; + private PersistentLogger persistentLogger; private volatile boolean isAppVisible; @@ -125,8 +121,6 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi initializeMessageRetrieval(); initializeExpiringMessageManager(); initializeRevealableMessageManager(); - initializeTypingStatusRepository(); - initializeTypingStatusSender(); initializeGcmCheck(); initializeSignedPreKeyCheck(); initializePeriodicTasks(); @@ -187,14 +181,6 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi return viewOnceMessageManager; } - public TypingStatusRepository getTypingStatusRepository() { - return typingStatusRepository; - } - - public TypingStatusSender getTypingStatusSender() { - return typingStatusSender; - } - public boolean isAppVisible() { return isAppVisible; } @@ -294,14 +280,6 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi this.viewOnceMessageManager = new ViewOnceMessageManager(this); } - private void initializeTypingStatusRepository() { - this.typingStatusRepository = new TypingStatusRepository(); - } - - private void initializeTypingStatusSender() { - this.typingStatusSender = new TypingStatusSender(this); - } - private void initializePeriodicTasks() { RotateSignedPreKeyListener.schedule(this); DirectoryRefreshListener.schedule(this); 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 df48168ba..f749948fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TypingStatusSender.java @@ -2,9 +2,9 @@ package org.thoughtcrime.securesms.components; import android.annotation.SuppressLint; import android.content.Context; + import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.TypingSendJob; import org.thoughtcrime.securesms.util.Util; @@ -54,6 +54,10 @@ public class TypingStatusSender { onTypingStopped(threadId, false); } + public synchronized void onTypingStoppedWithNotify(long threadId) { + onTypingStopped(threadId, true); + } + private synchronized void onTypingStopped(long threadId, boolean notify) { TimerPair pair = Util.getOrDefault(selfTypingTimers, threadId, new TimerPair()); selfTypingTimers.put(threadId, pair); 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 dfc94dc84..62b70d7e1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -84,7 +84,6 @@ import com.bumptech.glide.request.transition.Transition; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.BlockUnblockDialog; import org.thoughtcrime.securesms.ExpirationDialog; import org.thoughtcrime.securesms.GroupMembersDialog; @@ -109,6 +108,7 @@ import org.thoughtcrime.securesms.components.InputPanel; import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener; import org.thoughtcrime.securesms.components.SendButton; import org.thoughtcrime.securesms.components.TooltipPopup; +import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider; import org.thoughtcrime.securesms.components.emoji.EmojiStrings; import org.thoughtcrime.securesms.components.emoji.MediaKeyboard; @@ -2561,11 +2561,10 @@ public class ConversationActivity extends PassphraseRequiredActivity long expiresIn = recipient.get().getExpireMessages() * 1000L; QuoteModel quote = result.isViewOnce() ? null : inputPanel.getQuote().orNull(); List mentions = new ArrayList<>(result.getMentions()); - boolean initiating = threadId == -1; OutgoingMediaMessage message = new OutgoingMediaMessage(recipient.get(), new SlideDeck(), result.getBody(), System.currentTimeMillis(), -1, expiresIn, result.isViewOnce(), distributionType, quote, Collections.emptyList(), Collections.emptyList(), mentions); OutgoingMediaMessage secureMessage = new OutgoingSecureMediaMessage(message); - ApplicationContext.getInstance(this).getTypingStatusSender().onTypingStopped(threadId); + ApplicationDependencies.getTypingStatusSender().onTypingStopped(threadId); inputPanel.clearQuote(); attachmentManager.clear(glideRequests, false); @@ -2637,7 +2636,7 @@ public class ConversationActivity extends PassphraseRequiredActivity if (isSecureText && !forceSms) { outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessageCandidate); - ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId); + ApplicationDependencies.getTypingStatusSender().onTypingStopped(threadId); } else { outgoingMessage = outgoingMessageCandidate; } @@ -2683,7 +2682,7 @@ public class ConversationActivity extends PassphraseRequiredActivity if (isSecureText && !forceSms) { message = new OutgoingEncryptedMessage(recipient.get(), messageBody, expiresIn); - ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId); + ApplicationDependencies.getTypingStatusSender().onTypingStopped(threadId); } else { message = new OutgoingTextMessage(recipient.get(), messageBody, expiresIn, subscriptionId); } @@ -3071,10 +3070,22 @@ public class ConversationActivity extends PassphraseRequiredActivity private boolean enabled = true; + private String previousText = ""; + @Override public void onTextChanged(String text) { if (enabled && threadId > 0 && isSecureText && !isSmsForced() && !recipient.get().isBlocked()) { - ApplicationContext.getInstance(ConversationActivity.this).getTypingStatusSender().onTypingStarted(threadId); + TypingStatusSender typingStatusSender = ApplicationDependencies.getTypingStatusSender(); + + if (text.length() == 0) { + typingStatusSender.onTypingStoppedWithNotify(threadId); + } else if (text.length() < previousText.length() && previousText.contains(text)) { + typingStatusSender.onTypingStopped(threadId); + } else { + typingStatusSender.onTypingStarted(threadId); + } + + previousText = text; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 66f6e22c5..72e3973ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -52,6 +52,7 @@ import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityOptionsCompat; import androidx.core.text.HtmlCompat; +import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import androidx.recyclerview.widget.LinearLayoutManager; @@ -70,6 +71,7 @@ import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.components.ConversationScrollToView; import org.thoughtcrime.securesms.components.ConversationTypingView; import org.thoughtcrime.securesms.components.TooltipPopup; +import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager; import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController; import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState; @@ -350,7 +352,7 @@ public class ConversationFragment extends LoggingFragment { @Override public void onStop() { super.onStop(); - ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypists(threadId).removeObservers(this); + ApplicationDependencies.getTypingStatusRepository().getTypists(threadId).removeObservers(this); } public void onNewIntent() { @@ -497,7 +499,7 @@ public class ConversationFragment extends LoggingFragment { list.addOnScrollListener(conversationScrollListener); if (oldThreadId != threadId) { - ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypists(oldThreadId).removeObservers(this); + ApplicationDependencies.getTypingStatusRepository().getTypists(oldThreadId).removeObservers(this); } } @@ -531,8 +533,10 @@ public class ConversationFragment extends LoggingFragment { return; } - ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypists(threadId).removeObservers(this); - ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypists(threadId).observe(this, typingState -> { + LiveData typists = ApplicationDependencies.getTypingStatusRepository().getTypists(threadId); + + typists.removeObservers(this); + typists.observe(this, typingState -> { List recipients; boolean replacedByIncomingMessage; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 4e59de9a9..5173dcb7f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -70,7 +70,6 @@ import com.google.android.material.snackbar.Snackbar; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.MainFragment; import org.thoughtcrime.securesms.MainNavigator; import org.thoughtcrime.securesms.NewConversationActivity; @@ -511,7 +510,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode } private void initializeTypingObserver() { - ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypingThreads().observe(this, threadIds -> { + ApplicationDependencies.getTypingStatusRepository().getTypingThreads().observe(this, threadIds -> { if (threadIds == null) { threadIds = Collections.emptySet(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index 7a288ba72..2bbf3f00e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -5,24 +5,24 @@ import android.app.Application; import androidx.annotation.MainThread; import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.KbsEnclave; -import org.thoughtcrime.securesms.messages.IncomingMessageProcessor; -import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever; +import org.thoughtcrime.securesms.components.TypingStatusRepository; +import org.thoughtcrime.securesms.components.TypingStatusSender; +import org.thoughtcrime.securesms.groups.GroupsV2Authorization; import org.thoughtcrime.securesms.groups.GroupsV2AuthorizationMemoryValueCache; import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.keyvalue.KeyValueStore; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.megaphone.MegaphoneRepository; +import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever; +import org.thoughtcrime.securesms.messages.IncomingMessageObserver; +import org.thoughtcrime.securesms.messages.IncomingMessageProcessor; import org.thoughtcrime.securesms.notifications.MessageNotifier; -import org.thoughtcrime.securesms.pin.KbsEnclaves; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.recipients.LiveRecipientCache; -import org.thoughtcrime.securesms.messages.IncomingMessageObserver; import org.thoughtcrime.securesms.service.TrimThreadsByDateManager; import org.thoughtcrime.securesms.util.EarlyMessageCache; -import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.FrameRateTracker; import org.thoughtcrime.securesms.util.Hex; import org.thoughtcrime.securesms.util.IasKeyStore; @@ -31,7 +31,6 @@ import org.whispersystems.signalservice.api.KeyBackupService; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageSender; -import org.thoughtcrime.securesms.groups.GroupsV2Authorization; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; /** @@ -64,6 +63,8 @@ public class ApplicationDependencies { private static EarlyMessageCache earlyMessageCache; private static MessageNotifier messageNotifier; private static TrimThreadsByDateManager trimThreadsByDateManager; + private static TypingStatusRepository typingStatusRepository; + private static TypingStatusSender typingStatusSender; @MainThread public static synchronized void init(@NonNull Application application, @NonNull Provider provider) { @@ -256,6 +257,26 @@ public class ApplicationDependencies { return trimThreadsByDateManager; } + public static TypingStatusRepository getTypingStatusRepository() { + assertInitialization(); + + if (typingStatusRepository == null) { + typingStatusRepository = provider.provideTypingStatusRepository(); + } + + return typingStatusRepository; + } + + public static TypingStatusSender getTypingStatusSender() { + assertInitialization(); + + if (typingStatusSender == null) { + typingStatusSender = provider.provideTypingStatusSender(); + } + + return typingStatusSender; + } + private static void assertInitialization() { if (application == null || provider == null) { throw new UninitializedException(); @@ -278,6 +299,8 @@ public class ApplicationDependencies { @NonNull MessageNotifier provideMessageNotifier(); @NonNull IncomingMessageObserver provideIncomingMessageObserver(); @NonNull TrimThreadsByDateManager provideTrimThreadsByDateManager(); + @NonNull TypingStatusRepository provideTypingStatusRepository(); + @NonNull TypingStatusSender provideTypingStatusSender(); } private static class UninitializedException extends IllegalStateException { diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index b9b247adb..899e9b9ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -7,7 +7,17 @@ import androidx.annotation.NonNull; import org.greenrobot.eventbus.EventBus; import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.components.TypingStatusRepository; +import org.thoughtcrime.securesms.components.TypingStatusSender; +import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.events.ReminderUpdateEvent; +import org.thoughtcrime.securesms.jobmanager.JobManager; +import org.thoughtcrime.securesms.jobmanager.JobMigrator; import org.thoughtcrime.securesms.jobmanager.impl.FactoryJobPredicate; +import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; +import org.thoughtcrime.securesms.jobs.FastJobStorage; +import org.thoughtcrime.securesms.jobs.JobManagerFactories; import org.thoughtcrime.securesms.jobs.MarkerJob; import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob; import org.thoughtcrime.securesms.jobs.PushGroupSendJob; @@ -16,26 +26,17 @@ import org.thoughtcrime.securesms.jobs.PushProcessMessageJob; import org.thoughtcrime.securesms.jobs.PushTextSendJob; import org.thoughtcrime.securesms.jobs.ReactionSendJob; import org.thoughtcrime.securesms.jobs.TypingSendJob; -import org.thoughtcrime.securesms.messages.IncomingMessageProcessor; -import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.events.ReminderUpdateEvent; -import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever; -import org.thoughtcrime.securesms.jobmanager.JobManager; -import org.thoughtcrime.securesms.jobmanager.JobMigrator; -import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; -import org.thoughtcrime.securesms.jobs.FastJobStorage; -import org.thoughtcrime.securesms.jobs.JobManagerFactories; -import org.thoughtcrime.securesms.keyvalue.KeyValueStore; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.megaphone.MegaphoneRepository; +import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever; +import org.thoughtcrime.securesms.messages.IncomingMessageObserver; +import org.thoughtcrime.securesms.messages.IncomingMessageProcessor; import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier; import org.thoughtcrime.securesms.push.SecurityEventListener; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.recipients.LiveRecipientCache; -import org.thoughtcrime.securesms.messages.IncomingMessageObserver; import org.thoughtcrime.securesms.service.TrimThreadsByDateManager; import org.thoughtcrime.securesms.util.AlarmSleepTimer; import org.thoughtcrime.securesms.util.EarlyMessageCache; @@ -178,6 +179,16 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr return new TrimThreadsByDateManager(context); } + @Override + public @NonNull TypingStatusRepository provideTypingStatusRepository() { + return new TypingStatusRepository(); + } + + @Override + public @NonNull TypingStatusSender provideTypingStatusSender() { + return new TypingStatusSender(context); + } + private static class DynamicCredentialsProvider implements CredentialsProvider { private final Context context; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java index a6fecb502..349e42a99 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java @@ -1587,10 +1587,10 @@ public final class PushProcessMessageJob extends BaseJob { if (typingMessage.isTypingStarted()) { Log.d(TAG, "Typing started on thread " + threadId); - ApplicationContext.getInstance(context).getTypingStatusRepository().onTypingStarted(context,threadId, author, content.getSenderDevice()); + ApplicationDependencies.getTypingStatusRepository().onTypingStarted(context,threadId, author, content.getSenderDevice()); } else { Log.d(TAG, "Typing stopped on thread " + threadId); - ApplicationContext.getInstance(context).getTypingStatusRepository().onTypingStopped(context, threadId, author, content.getSenderDevice(), false); + ApplicationDependencies.getTypingStatusRepository().onTypingStopped(context, threadId, author, content.getSenderDevice(), false); } } @@ -1811,7 +1811,7 @@ public final class PushProcessMessageJob extends BaseJob { if (threadId > 0) { Log.d(TAG, "Typing stopped on thread " + threadId + " due to an incoming message."); - ApplicationContext.getInstance(context).getTypingStatusRepository().onTypingStopped(context, threadId, author, device, true); + ApplicationDependencies.getTypingStatusRepository().onTypingStopped(context, threadId, author, device, true); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java index dd7595410..c6ec17368 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java @@ -28,7 +28,6 @@ import androidx.preference.Preference; import com.google.android.material.snackbar.Snackbar; -import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationPreferencesActivity; import org.thoughtcrime.securesms.PassphraseChangeActivity; import org.thoughtcrime.securesms.R; @@ -316,7 +315,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment SignalStore.settings().isLinkPreviewsEnabled())); if (!enabled) { - ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().clear(); + ApplicationDependencies.getTypingStatusRepository().clear(); } }); return true;