Move mms and security checks into ViewModel/Repository.

fork-5.53.8
Alex Hart 2022-07-19 14:53:54 -03:00 zatwierdzone przez Cody Henthorne
rodzic c5f4a9c89e
commit b696a0f758
6 zmienionych plików z 174 dodań i 118 usunięć

Wyświetl plik

@ -328,6 +328,10 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
/**
@ -454,13 +458,12 @@ public class ConversationParentFragment extends Fragment
private boolean isSecureText;
private boolean isDefaultSms;
private int reactWithAnyEmojiStartPage = -1;
private boolean isMmsEnabled = true;
private boolean isSecurityInitialized = false;
private boolean isSearchRequested = false;
private boolean hasProcessedShareData = false;
private final LifecycleDisposable disposables = new LifecycleDisposable();
private final Debouncer optionsMenuDebouncer = new Debouncer(50);
private final LifecycleDisposable disposables = new LifecycleDisposable();
private final Debouncer optionsMenuDebouncer = new Debouncer(50);
private IdentityRecordList identityRecords = new IdentityRecordList(Collections.emptyList());
private Callback callback;
@ -528,8 +531,6 @@ public class ConversationParentFragment extends Fragment
.commitNow();
}
final boolean typingIndicatorsEnabled = TextSecurePreferences.isTypingIndicatorsEnabled(requireContext());
if (savedInstanceState != null) {
hasProcessedShareData = savedInstanceState.getBoolean("SHARED", false);
}
@ -549,45 +550,14 @@ public class ConversationParentFragment extends Fragment
initializeEnabledCheck();
initializePendingRequestsBanner();
initializeGroupV1MigrationsBanners();
initializeSecurity(recipient.get().isRegistered(), isDefaultSms).addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
if (getActivity() == null) {
Log.w(TAG, "Activity is not attached. Not proceeding with initialization.");
return;
}
if (requireActivity().isFinishing()) {
Log.w(TAG, "Activity is finishing. Not proceeding with initialization.");
return;
}
Flowable<ConversationSecurityInfo> observableSecurityInfo = viewModel.getConversationState()
.map(ConversationState::getSecurityInfo)
.filter(ConversationSecurityInfo::isInitialized);
initializeProfiles();
initializeGv1Migration();
disposables.add(observableSecurityInfo.subscribe(securityInfo -> handleSecurityChange(securityInfo.isPushAvailable(), securityInfo.isDefaultSmsApplication())));
disposables.add(viewModel.getConversationSecurityInfo(args.getRecipientId()).firstOrError().subscribe(unused -> onInitialSecurityConfigurationLoaded()));
Log.d(TAG, "Initializing data from onViewCreated...");
initializeDraft(args).addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean loadedDraft) {
if (loadedDraft != null && loadedDraft) {
Log.i(TAG, "Finished loading draft");
ThreadUtil.runOnMain(() -> {
if (fragment != null && fragment.isResumed()) {
fragment.moveToLastSeen();
} else {
Log.w(TAG, "Wanted to move to the last seen position, but the fragment was in an invalid state");
}
});
}
if (typingIndicatorsEnabled) {
composeText.addTextChangedListener(typingTextWatcher);
}
composeText.setSelection(composeText.length(), composeText.length());
}
});
}
});
initializeInsightObserver();
initializeActionBar();
@ -616,7 +586,7 @@ public class ConversationParentFragment extends Fragment
WindowUtil.setLightStatusBarFromTheme(requireActivity());
EventBus.getDefault().register(this);
initializeMmsEnabledCheck();
viewModel.checkIfMmsIsEnabled();
initializeIdentityRecords();
composeText.setMessageSendType(sendButton.getSelectedSendType());
@ -758,7 +728,7 @@ public class ConversationParentFragment extends Fragment
attachmentManager.setLocation(place, getCurrentMediaConstraints());
break;
case SMS_DEFAULT:
initializeSecurity(isSecureText, isDefaultSms);
viewModel.updateSecurityInfo();
break;
case PICK_GIF:
case MEDIA_SENDER:
@ -845,6 +815,38 @@ public class ConversationParentFragment extends Fragment
}
}
private void onInitialSecurityConfigurationLoaded() {
Log.d(TAG, "Initial security configuration loaded.");
if (isDetached()) {
Log.w(TAG, "Fragment has become detached. Ignoring configuration call.");
}
initializeProfiles();
initializeGv1Migration();
Log.d(TAG, "Initializing draft from initial security configuration load...");
initializeDraft(viewModel.getArgs()).addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean loadedDraft) {
if (loadedDraft != null && loadedDraft) {
Log.i(TAG, "Finished loading draft");
ThreadUtil.runOnMain(() -> {
if (fragment != null && fragment.isResumed()) {
fragment.moveToLastSeen();
} else {
Log.w(TAG, "Wanted to move to the last seen position, but the fragment was in an invalid state");
}
});
}
if (TextSecurePreferences.isTypingIndicatorsEnabled(requireContext())) {
composeText.addTextChangedListener(typingTextWatcher);
}
composeText.setSelection(composeText.length(), composeText.length());
}
});
}
private void setVisibleThread(long threadId) {
if (!isInBubble()) {
// TODO [alex] LargeScreenSupport -- Inform MainActivityViewModel that the conversation was opened.
@ -1489,7 +1491,7 @@ public class ConversationParentFragment extends Fragment
}
private void handleAddAttachment() {
if (this.isMmsEnabled || isSecureText) {
if (viewModel.getConversationStateSnapshot().isMmsEnabled() || isSecureText) {
viewModel.getRecentMedia().removeObservers(this);
if (attachmentKeyboardStub.resolved() && container.isInputOpen() && container.getCurrentInput() == attachmentKeyboardStub.get()) {
@ -1598,6 +1600,7 @@ public class ConversationParentFragment extends Fragment
calculateCharactersRemaining();
invalidateOptionsMenu();
setBlockedUserState(recipient.get(), isSecureText, isDefaultSms);
onSecurityUpdated();
}
///// Initializers
@ -1643,6 +1646,7 @@ public class ConversationParentFragment extends Fragment
}
if (draftText != null) {
Log.d(TAG, "Handling shared text");
composeText.setText("");
composeText.append(draftText);
result.set(true);
@ -1654,6 +1658,7 @@ public class ConversationParentFragment extends Fragment
}
if (draftText == null && draftMedia == null && draftMediaType == null) {
Log.d(TAG, "Initializing draft from database");
return initializeDraftFromDatabase();
} else {
updateToggleButtonState();
@ -1844,60 +1849,6 @@ public class ConversationParentFragment extends Fragment
return future;
}
private ListenableFuture<Boolean> initializeSecurity(final boolean currentSecureText,
final boolean currentIsDefaultSms)
{
final SettableFuture<Boolean> future = new SettableFuture<>();
final Context context = requireContext().getApplicationContext();
handleSecurityChange(currentSecureText || isPushGroupConversation(), currentIsDefaultSms);
new AsyncTask<Recipient, Void, boolean[]>() {
@Override
protected boolean[] doInBackground(Recipient... params) {
Recipient recipient = params[0].resolve();
Log.i(TAG, "Resolving registered state...");
RegisteredState registeredState;
if (recipient.isPushGroup()) {
Log.i(TAG, "Push group recipient...");
registeredState = RegisteredState.REGISTERED;
} else {
Log.i(TAG, "Checking through resolved recipient");
registeredState = recipient.resolve().getRegistered();
}
Log.i(TAG, "Resolved registered state: " + registeredState);
boolean signalEnabled = Recipient.self().isRegistered();
if (registeredState == RegisteredState.UNKNOWN) {
try {
Log.i(TAG, "Refreshing directory for user: " + recipient.getId().serialize());
registeredState = ContactDiscovery.refresh(context, recipient, false);
} catch (IOException e) {
Log.w(TAG, e);
}
}
Log.i(TAG, "Returning registered state...");
return new boolean[] {registeredState == RegisteredState.REGISTERED && signalEnabled,
Util.isDefaultSmsProvider(context)};
}
@Override
protected void onPostExecute(boolean[] result) {
if (result[0] != currentSecureText || result[1] != currentIsDefaultSms) {
Log.i(TAG, "onPostExecute() handleSecurityChange: " + result[0] + " , " + result[1]);
handleSecurityChange(result[0], result[1]);
}
future.set(true);
onSecurityUpdated();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient.get());
return future;
}
private void onSecurityUpdated() {
Log.i(TAG, "onSecurityUpdated()");
updateReminders();
@ -1989,22 +1940,6 @@ public class ConversationParentFragment extends Fragment
sendButton.setDefaultSubscriptionId(defaultSubscriptionId.orElse(null));
}
private void initializeMmsEnabledCheck() {
final Context context = requireContext().getApplicationContext();
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
return Util.isMmsCapable(context);
}
@Override
protected void onPostExecute(Boolean isMmsEnabled) {
ConversationParentFragment.this.isMmsEnabled = isMmsEnabled;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private ListenableFuture<Boolean> initializeIdentityRecords() {
final SettableFuture<Boolean> future = new SettableFuture<>();
final Context context = requireContext().getApplicationContext();
@ -2594,7 +2529,6 @@ public class ConversationParentFragment extends Fragment
updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId());
updatePaymentsAvailable();
updateSendButtonColor(sendButton.getSelectedSendType());
initializeSecurity(isSecureText, isDefaultSms);
if (searchViewItem == null || !searchViewItem.isActionViewExpanded()) {
invalidateOptionsMenu();
@ -2658,7 +2592,7 @@ public class ConversationParentFragment extends Fragment
securityUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
initializeSecurity(isSecureText, isDefaultSms);
viewModel.updateSecurityInfo();
calculateCharactersRemaining();
}
};
@ -3024,7 +2958,7 @@ public class ConversationParentFragment extends Fragment
Log.i(TAG, "[sendMessage] recipient: " + recipient.getId() + ", threadId: " + threadId + ", sendType: " + (sendType.usesSignalTransport() ? "signal" : "sms") + ", isManual: " + sendButton.isManualSelection());
if ((recipient.isMmsGroup() || recipient.getEmail().isPresent()) && !isMmsEnabled) {
if ((recipient.isMmsGroup() || recipient.getEmail().isPresent()) && !viewModel.getConversationStateSnapshot().isMmsEnabled()) {
handleManualMmsRequired();
} else if (sendType.usesSignalTransport() && (identityRecords.isUnverified(true) || identityRecords.isUntrusted(true))) {
handleRecentSafetyNumberChange();

Wyświetl plik

@ -8,23 +8,33 @@ import androidx.annotation.WorkerThread;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceViewedUpdateJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.BubbleUtil;
import org.thoughtcrime.securesms.util.ConversationUtil;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Scheduler;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
class ConversationRepository {
private static final String TAG = Log.tag(ConversationRepository.class);
@ -115,4 +125,41 @@ class ConversationRepository {
}
});
}
@NonNull Single<Boolean> checkIfMmsIsEnabled() {
return Single.fromCallable(() -> Util.isMmsCapable(context)).subscribeOn(Schedulers.io());
}
@NonNull Observable<ConversationSecurityInfo> getSecurityInfo(RecipientId recipientId) {
return Recipient.observable(recipientId).map(recipient -> {
Log.i(TAG, "Resolving registered state...");
RecipientDatabase.RegisteredState registeredState;
if (recipient.isPushGroup()) {
Log.i(TAG, "Push group recipient...");
registeredState = RecipientDatabase.RegisteredState.REGISTERED;
} else {
Log.i(TAG, "Checking through resolved recipient");
registeredState = recipient.resolve().getRegistered();
}
Log.i(TAG, "Resolved registered state: " + registeredState);
boolean signalEnabled = Recipient.self().isRegistered();
if (registeredState == RecipientDatabase.RegisteredState.UNKNOWN) {
try {
Log.i(TAG, "Refreshing directory for user: " + recipient.getId().serialize());
registeredState = ContactDiscovery.refresh(context, recipient, false);
} catch (IOException e) {
Log.w(TAG, e);
}
}
Log.i(TAG, "Returning registered state...");
return new ConversationSecurityInfo(recipientId,
registeredState == RecipientDatabase.RegisteredState.REGISTERED && signalEnabled,
Util.isDefaultSmsProvider(context),
true);
}).subscribeOn(Schedulers.io());
}
}

Wyświetl plik

@ -0,0 +1,10 @@
package org.thoughtcrime.securesms.conversation
import org.thoughtcrime.securesms.recipients.RecipientId
data class ConversationSecurityInfo(
val recipientId: RecipientId = RecipientId.UNKNOWN,
val isPushAvailable: Boolean = false,
val isDefaultSmsApplication: Boolean = false,
val isInitialized: Boolean = false
)

Wyświetl plik

@ -0,0 +1,21 @@
package org.thoughtcrime.securesms.conversation
/**
* State holder for different values we are interested in for a given
* conversation. This is to be used for different values normally stored
* directly in the fragment that would be better relegated to a ViewModel.
*/
data class ConversationState(
val isMmsEnabled: Boolean = true,
val securityInfo: ConversationSecurityInfo = ConversationSecurityInfo()
) {
companion object {
@JvmStatic
fun create(): ConversationState {
return ConversationState()
}
}
fun withMmsEnabled(isMmsEnabled: Boolean) = copy(isMmsEnabled = isMmsEnabled)
fun withSecurityInfo(securityInfo: ConversationSecurityInfo) = copy(securityInfo = securityInfo)
}

Wyświetl plik

@ -17,12 +17,12 @@ import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.util.Pair;
import org.signal.paging.ObservablePagedData;
import org.signal.paging.PagedData;
import org.signal.paging.PagingConfig;
import org.signal.paging.PagingController;
import org.signal.paging.ProxyPagingController;
import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.NotificationProfilesRepository;
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.conversation.colors.GroupAuthorNameColorHelper;
@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.util.livedata.Store;
import org.thoughtcrime.securesms.util.rx.RxStore;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
import java.util.Collections;
@ -55,9 +56,12 @@ import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
import kotlin.Unit;
public class ConversationViewModel extends ViewModel {
@ -88,6 +92,9 @@ public class ConversationViewModel extends ViewModel {
private final NotificationProfilesRepository notificationProfilesRepository;
private final MutableLiveData<String> searchQuery;
private final GroupAuthorNameColorHelper groupAuthorNameColorHelper;
private final RxStore<ConversationState> conversationStateStore;
private final CompositeDisposable disposables;
private final BehaviorSubject<Unit> conversationStateTick;
private ConversationIntents.Args args;
private int jumpToPosition;
@ -113,6 +120,9 @@ public class ConversationViewModel extends ViewModel {
this.recipientId = BehaviorSubject.create();
this.threadId = BehaviorSubject.create();
this.groupAuthorNameColorHelper = new GroupAuthorNameColorHelper();
this.conversationStateStore = new RxStore<>(ConversationState.create(), Schedulers.io());
this.disposables = new CompositeDisposable();
this.conversationStateTick = BehaviorSubject.createDefault(Unit.INSTANCE);
BehaviorSubject<Recipient> recipientCache = BehaviorSubject.create();
@ -122,6 +132,12 @@ public class ConversationViewModel extends ViewModel {
.map(Recipient::resolved)
.subscribe(recipientCache);
conversationStateStore.update(Observable.combineLatest(recipientId, conversationStateTick, (id, tick) -> id)
.distinctUntilChanged()
.switchMap(conversationRepository::getSecurityInfo)
.toFlowable(BackpressureStrategy.LATEST),
(securityInfo, state) -> state.withSecurityInfo(securityInfo));
BehaviorSubject<ConversationData> conversationMetadata = BehaviorSubject.create();
Observable.combineLatest(threadId, recipientCache, Pair::new)
@ -270,6 +286,29 @@ public class ConversationViewModel extends ViewModel {
conversationRepository.markGiftBadgeRevealed(messageId);
}
void checkIfMmsIsEnabled() {
disposables.add(conversationRepository.checkIfMmsIsEnabled().subscribe(isEnabled -> {
conversationStateStore.update(state -> state.withMmsEnabled(true));
}));
}
@NonNull Flowable<ConversationState> getConversationState() {
return conversationStateStore.getStateFlowable().observeOn(AndroidSchedulers.mainThread());
}
@NonNull Flowable<ConversationSecurityInfo> getConversationSecurityInfo(@NonNull RecipientId recipientId) {
return getConversationState().map(ConversationState::getSecurityInfo)
.filter(info -> info.isInitialized() && Objects.equals(info.getRecipientId(), recipientId));
}
void updateSecurityInfo() {
conversationStateTick.onNext(Unit.INSTANCE);
}
@NonNull ConversationState getConversationStateSnapshot() {
return conversationStateStore.getState();
}
@NonNull LiveData<String> getSearchQuery() {
return searchQuery;
}
@ -372,6 +411,7 @@ public class ConversationViewModel extends ViewModel {
ApplicationDependencies.getDatabaseObserver().unregisterObserver(conversationObserver);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageUpdateObserver);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageInsertObserver);
disposables.clear();
EventBus.getDefault().unregister(this);
}

Wyświetl plik

@ -26,6 +26,10 @@ class LifecycleDisposable : DefaultLifecycleObserver {
return this
}
fun clear() {
disposables.clear()
}
override fun onDestroy(owner: LifecycleOwner) {
owner.lifecycle.removeObserver(this)
disposables.clear()