ConversationsActivity.java

  1/*
  2 * Copyright (c) 2018, Daniel Gultsch All rights reserved.
  3 *
  4 * Redistribution and use in source and binary forms, with or without modification,
  5 * are permitted provided that the following conditions are met:
  6 *
  7 * 1. Redistributions of source code must retain the above copyright notice, this
  8 * list of conditions and the following disclaimer.
  9 *
 10 * 2. Redistributions in binary form must reproduce the above copyright notice,
 11 * this list of conditions and the following disclaimer in the documentation and/or
 12 * other materials provided with the distribution.
 13 *
 14 * 3. Neither the name of the copyright holder nor the names of its contributors
 15 * may be used to endorse or promote products derived from this software without
 16 * specific prior written permission.
 17 *
 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 22 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28 */
 29
 30package eu.siacs.conversations.ui;
 31
 32
 33import android.annotation.SuppressLint;
 34import android.app.Activity;
 35import android.app.Fragment;
 36import android.app.FragmentManager;
 37import android.app.FragmentTransaction;
 38import android.content.ActivityNotFoundException;
 39import android.content.Context;
 40import android.content.Intent;
 41import android.content.pm.PackageManager;
 42import android.databinding.DataBindingUtil;
 43import android.net.Uri;
 44import android.os.Build;
 45import android.os.Bundle;
 46import android.provider.Settings;
 47import android.support.annotation.IdRes;
 48import android.support.annotation.NonNull;
 49import android.support.v7.app.ActionBar;
 50import android.support.v7.app.AlertDialog;
 51import android.support.v7.widget.Toolbar;
 52import android.util.Log;
 53import android.view.Menu;
 54import android.view.MenuItem;
 55import android.widget.Toast;
 56
 57import org.openintents.openpgp.util.OpenPgpApi;
 58
 59import java.util.concurrent.atomic.AtomicBoolean;
 60
 61import eu.siacs.conversations.Config;
 62import eu.siacs.conversations.R;
 63import eu.siacs.conversations.crypto.OmemoSetting;
 64import eu.siacs.conversations.databinding.ActivityConversationsBinding;
 65import eu.siacs.conversations.entities.Account;
 66import eu.siacs.conversations.entities.Conversation;
 67import eu.siacs.conversations.services.XmppConnectionService;
 68import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
 69import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
 70import eu.siacs.conversations.ui.interfaces.OnConversationRead;
 71import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
 72import eu.siacs.conversations.ui.interfaces.OnConversationsListItemUpdated;
 73import eu.siacs.conversations.ui.service.EmojiService;
 74import eu.siacs.conversations.ui.util.ActivityResult;
 75import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
 76import eu.siacs.conversations.ui.util.PendingItem;
 77import eu.siacs.conversations.utils.ExceptionHelper;
 78import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 79
 80import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP;
 81
 82public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast {
 83
 84	public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW";
 85	public static final String EXTRA_CONVERSATION = "conversationUuid";
 86	public static final String EXTRA_DOWNLOAD_UUID = "eu.siacs.conversations.download_uuid";
 87	public static final String EXTRA_TEXT = "text";
 88	public static final String EXTRA_AS_QUOTE = "as_quote";
 89	public static final String EXTRA_NICK = "nick";
 90	public static final String EXTRA_IS_PRIVATE_MESSAGE = "pm";
 91
 92	public static final int REQUEST_OPEN_MESSAGE = 0x9876;
 93	public static final int REQUEST_PLAY_PAUSE = 0x5432;
 94
 95
 96	//secondary fragment (when holding the conversation, must be initialized before refreshing the overview fragment
 97	private static final @IdRes
 98	int[] FRAGMENT_ID_NOTIFICATION_ORDER = {R.id.secondary_fragment, R.id.main_fragment};
 99	private final PendingItem<Intent> pendingViewIntent = new PendingItem<>();
100	private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>();
101	private ActivityConversationsBinding binding;
102	private boolean mActivityPaused = true;
103	private AtomicBoolean mRedirectInProcess = new AtomicBoolean(false);
104
105	private static boolean isViewIntent(Intent i) {
106		return i != null && ACTION_VIEW_CONVERSATION.equals(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION);
107	}
108
109	private static Intent createLauncherIntent(Context context) {
110		final Intent intent = new Intent(context, ConversationsActivity.class);
111		intent.setAction(Intent.ACTION_MAIN);
112		intent.addCategory(Intent.CATEGORY_LAUNCHER);
113		return intent;
114	}
115
116	@Override
117	protected void refreshUiReal() {
118		for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
119			refreshFragment(id);
120		}
121	}
122
123	@Override
124	void onBackendConnected() {
125		if (performRedirectIfNecessary(true)) {
126			return;
127		}
128		xmppConnectionService.getNotificationService().setIsInForeground(true);
129		Intent intent = pendingViewIntent.pop();
130		if (intent != null) {
131			if (processViewIntent(intent)) {
132				if (binding.secondaryFragment != null) {
133					notifyFragmentOfBackendConnected(R.id.main_fragment);
134				}
135				invalidateActionBarTitle();
136				return;
137			}
138		}
139		for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
140			notifyFragmentOfBackendConnected(id);
141		}
142
143		ActivityResult activityResult = postponedActivityResult.pop();
144		if (activityResult != null) {
145			handleActivityResult(activityResult);
146		}
147
148		invalidateActionBarTitle();
149		if (binding.secondaryFragment != null && ConversationFragment.getConversation(this) == null) {
150			Conversation conversation = ConversationsOverviewFragment.getSuggestion(this);
151			if (conversation != null) {
152				openConversation(conversation, null);
153			}
154		}
155		showDialogsIfMainIsOverview();
156	}
157
158	private boolean performRedirectIfNecessary(boolean noAnimation) {
159		return performRedirectIfNecessary(null, noAnimation);
160	}
161
162	private boolean performRedirectIfNecessary(final Conversation ignore, final boolean noAnimation) {
163		if (xmppConnectionService == null) {
164			return false;
165		}
166		boolean isConversationsListEmpty = xmppConnectionService.isConversationsListEmpty(ignore);
167		if (isConversationsListEmpty && mRedirectInProcess.compareAndSet(false, true)) {
168			final Intent intent = getRedirectionIntent(noAnimation);
169			runOnUiThread(() -> {
170				startActivity(intent);
171				if (noAnimation) {
172					overridePendingTransition(0, 0);
173				}
174			});
175		}
176		return mRedirectInProcess.get();
177	}
178
179	private Intent getRedirectionIntent(boolean noAnimation) {
180		Account pendingAccount = xmppConnectionService.getPendingAccount();
181		Intent intent;
182		if (pendingAccount != null) {
183			intent = new Intent(this, EditAccountActivity.class);
184			intent.putExtra("jid", pendingAccount.getJid().asBareJid().toString());
185		} else {
186			if (xmppConnectionService.getAccounts().size() == 0) {
187				if (Config.X509_VERIFICATION) {
188					intent = new Intent(this, ManageAccountActivity.class);
189				} else if (Config.MAGIC_CREATE_DOMAIN != null) {
190					intent = new Intent(this, WelcomeActivity.class);
191					WelcomeActivity.addInviteUri(intent, getIntent());
192				} else {
193					intent = new Intent(this, EditAccountActivity.class);
194				}
195			} else {
196				intent = new Intent(this, StartConversationActivity.class);
197			}
198		}
199		intent.putExtra("init", true);
200		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
201		if (noAnimation) {
202			intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
203		}
204		return intent;
205	}
206
207	private void showDialogsIfMainIsOverview() {
208		if (xmppConnectionService == null) {
209			return;
210		}
211		final Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
212		if (fragment != null && fragment instanceof ConversationsOverviewFragment) {
213			if (ExceptionHelper.checkForCrash(this)) {
214				return;
215			}
216			openBatteryOptimizationDialogIfNeeded();
217		}
218	}
219
220	private String getBatteryOptimizationPreferenceKey() {
221		@SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
222		return "show_battery_optimization" + (device == null ? "" : device);
223	}
224
225	private void setNeverAskForBatteryOptimizationsAgain() {
226		getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
227	}
228
229	private void openBatteryOptimizationDialogIfNeeded() {
230		if (hasAccountWithoutPush()
231				&& isOptimizingBattery()
232				&& getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
233			AlertDialog.Builder builder = new AlertDialog.Builder(this);
234			builder.setTitle(R.string.battery_optimizations_enabled);
235			builder.setMessage(R.string.battery_optimizations_enabled_dialog);
236			builder.setPositiveButton(R.string.next, (dialog, which) -> {
237				Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
238				Uri uri = Uri.parse("package:" + getPackageName());
239				intent.setData(uri);
240				try {
241					startActivityForResult(intent, REQUEST_BATTERY_OP);
242				} catch (ActivityNotFoundException e) {
243					Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
244				}
245			});
246			builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
247			final AlertDialog dialog = builder.create();
248			dialog.setCanceledOnTouchOutside(false);
249			dialog.show();
250		}
251	}
252
253	private boolean hasAccountWithoutPush() {
254		for (Account account : xmppConnectionService.getAccounts()) {
255			if (account.getStatus() == Account.State.ONLINE && !xmppConnectionService.getPushManagementService().available(account)) {
256				return true;
257			}
258		}
259		return false;
260	}
261
262	private void notifyFragmentOfBackendConnected(@IdRes int id) {
263		final Fragment fragment = getFragmentManager().findFragmentById(id);
264		if (fragment != null && fragment instanceof OnBackendConnected) {
265			((OnBackendConnected) fragment).onBackendConnected();
266		}
267	}
268
269	private void refreshFragment(@IdRes int id) {
270		final Fragment fragment = getFragmentManager().findFragmentById(id);
271		if (fragment != null && fragment instanceof XmppFragment) {
272			((XmppFragment) fragment).refresh();
273		}
274	}
275
276	private boolean processViewIntent(Intent intent) {
277		String uuid = intent.getStringExtra(EXTRA_CONVERSATION);
278		Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null;
279		if (conversation == null) {
280			Log.d(Config.LOGTAG, "unable to view conversation with uuid:" + uuid);
281			return false;
282		}
283		openConversation(conversation, intent.getExtras());
284		return true;
285	}
286
287	@Override
288	public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
289		UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
290		if (grantResults.length > 0) {
291			if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
292				switch (requestCode) {
293					case REQUEST_OPEN_MESSAGE:
294						refreshUiReal();
295						ConversationFragment.openPendingMessage(this);
296						break;
297					case REQUEST_PLAY_PAUSE:
298						ConversationFragment.startStopPending(this);
299						break;
300				}
301			}
302		}
303	}
304
305	@Override
306	public void onActivityResult(int requestCode, int resultCode, final Intent data) {
307		super.onActivityResult(requestCode, resultCode, data);
308		ActivityResult activityResult = ActivityResult.of(requestCode, resultCode, data);
309		if (xmppConnectionService != null) {
310			handleActivityResult(activityResult);
311		} else {
312			this.postponedActivityResult.push(activityResult);
313		}
314	}
315
316	private void handleActivityResult(ActivityResult activityResult) {
317		if (activityResult.resultCode == Activity.RESULT_OK) {
318			handlePositiveActivityResult(activityResult.requestCode, activityResult.data);
319		} else {
320			handleNegativeActivityResult(activityResult.requestCode);
321		}
322	}
323
324	private void handleNegativeActivityResult(int requestCode) {
325		Conversation conversation = ConversationFragment.getConversationReliable(this);
326		switch (requestCode) {
327			case REQUEST_DECRYPT_PGP:
328				if (conversation == null) {
329					break;
330				}
331				conversation.getAccount().getPgpDecryptionService().giveUpCurrentDecryption();
332				break;
333			case REQUEST_BATTERY_OP:
334				setNeverAskForBatteryOptimizationsAgain();
335				break;
336		}
337	}
338
339	private void handlePositiveActivityResult(int requestCode, final Intent data) {
340		Conversation conversation = ConversationFragment.getConversationReliable(this);
341		if (conversation == null) {
342			Log.d(Config.LOGTAG, "conversation not found");
343			return;
344		}
345		switch (requestCode) {
346			case REQUEST_DECRYPT_PGP:
347				conversation.getAccount().getPgpDecryptionService().continueDecryption(data);
348				break;
349			case REQUEST_CHOOSE_PGP_ID:
350				long id = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, 0);
351				if (id != 0) {
352					conversation.getAccount().setPgpSignId(id);
353					announcePgp(conversation.getAccount(), null, null, onOpenPGPKeyPublished);
354				} else {
355					choosePgpSignId(conversation.getAccount());
356				}
357				break;
358			case REQUEST_ANNOUNCE_PGP:
359				announcePgp(conversation.getAccount(), conversation, data, onOpenPGPKeyPublished);
360				break;
361		}
362	}
363
364	@Override
365	protected void onCreate(final Bundle savedInstanceState) {
366		super.onCreate(savedInstanceState);
367		OmemoSetting.load(this);
368		new EmojiService(this).init();
369		this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations);
370		setSupportActionBar((Toolbar) binding.toolbar);
371		configureActionBar(getSupportActionBar());
372		this.getFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle);
373		this.getFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview);
374		this.initializeFragments();
375		this.invalidateActionBarTitle();
376		final Intent intent;
377		if (savedInstanceState == null) {
378			intent = getIntent();
379		} else {
380			intent = savedInstanceState.getParcelable("intent");
381		}
382		if (isViewIntent(intent)) {
383			pendingViewIntent.push(intent);
384			setIntent(createLauncherIntent(this));
385		}
386	}
387
388	@Override
389	public boolean onCreateOptionsMenu(Menu menu) {
390		getMenuInflater().inflate(R.menu.activity_conversations, menu);
391		MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
392		if (qrCodeScanMenuItem != null) {
393			if (isCameraFeatureAvailable()) {
394				Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
395				boolean visible = getResources().getBoolean(R.bool.show_qr_code_scan)
396						&& fragment != null
397						&& fragment instanceof ConversationsOverviewFragment;
398				qrCodeScanMenuItem.setVisible(visible);
399			} else {
400				qrCodeScanMenuItem.setVisible(false);
401			}
402		}
403		return super.onCreateOptionsMenu(menu);
404	}
405
406	@Override
407	public void onConversationSelected(Conversation conversation) {
408		if (ConversationFragment.getConversation(this) == conversation) {
409			Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open");
410			return;
411		}
412		openConversation(conversation, null);
413	}
414
415	private void openConversation(Conversation conversation, Bundle extras) {
416		ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment);
417		final boolean mainNeedsRefresh;
418		if (conversationFragment == null) {
419			mainNeedsRefresh = false;
420			Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
421			if (mainFragment != null && mainFragment instanceof ConversationFragment) {
422				conversationFragment = (ConversationFragment) mainFragment;
423			} else {
424				conversationFragment = new ConversationFragment();
425				FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
426				fragmentTransaction.replace(R.id.main_fragment, conversationFragment);
427				fragmentTransaction.addToBackStack(null);
428				try {
429					fragmentTransaction.commit();
430				} catch (IllegalStateException e) {
431					Log.w(Config.LOGTAG,"sate loss while opening conversation",e);
432					//allowing state loss is probably fine since view intents et all are already stored and a click can probably be 'ignored'
433					return;
434				}
435			}
436		} else {
437			mainNeedsRefresh = true;
438		}
439		conversationFragment.reInit(conversation, extras == null ? new Bundle() : extras);
440		if (mainNeedsRefresh) {
441			refreshFragment(R.id.main_fragment);
442		} else {
443			invalidateActionBarTitle();
444		}
445	}
446
447	@Override
448	public boolean onOptionsItemSelected(MenuItem item) {
449		if (MenuDoubleTabUtil.shouldIgnoreTap()) {
450			return false;
451		}
452		switch (item.getItemId()) {
453			case android.R.id.home:
454				FragmentManager fm = getFragmentManager();
455				if (fm.getBackStackEntryCount() > 0) {
456					fm.popBackStack();
457					return true;
458				}
459				break;
460			case R.id.action_scan_qr_code:
461				UriHandlerActivity.scan(this);
462				return true;
463		}
464		return super.onOptionsItemSelected(item);
465	}
466
467	@Override
468	public void onSaveInstanceState(Bundle savedInstanceState) {
469		Intent pendingIntent = pendingViewIntent.peek();
470		savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent());
471		super.onSaveInstanceState(savedInstanceState);
472	}
473
474	@Override
475	protected void onStart() {
476		final int theme = findTheme();
477		if (this.mTheme != theme) {
478			this.mSkipBackgroundBinding = true;
479			recreate();
480		} else {
481			this.mSkipBackgroundBinding = false;
482		}
483		mRedirectInProcess.set(false);
484		super.onStart();
485	}
486
487	@Override
488	protected void onNewIntent(final Intent intent) {
489		if (isViewIntent(intent)) {
490			if (xmppConnectionService != null) {
491				processViewIntent(intent);
492			} else {
493				pendingViewIntent.push(intent);
494			}
495		}
496		setIntent(createLauncherIntent(this));
497	}
498
499	@Override
500	public void onPause() {
501		this.mActivityPaused = true;
502		super.onPause();
503	}
504
505	@Override
506	public void onResume() {
507		super.onResume();
508		this.mActivityPaused = false;
509	}
510
511	private void initializeFragments() {
512		FragmentTransaction transaction = getFragmentManager().beginTransaction();
513		Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
514		Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment);
515		if (mainFragment != null) {
516			if (binding.secondaryFragment != null) {
517				if (mainFragment instanceof ConversationFragment) {
518					getFragmentManager().popBackStack();
519					transaction.remove(mainFragment);
520					transaction.commit();
521					getFragmentManager().executePendingTransactions();
522					transaction = getFragmentManager().beginTransaction();
523					transaction.replace(R.id.secondary_fragment, mainFragment);
524					transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
525					transaction.commit();
526					return;
527				}
528			} else {
529				if (secondaryFragment != null && secondaryFragment instanceof ConversationFragment) {
530					transaction.remove(secondaryFragment);
531					transaction.commit();
532					getFragmentManager().executePendingTransactions();
533					transaction = getFragmentManager().beginTransaction();
534					transaction.replace(R.id.main_fragment, secondaryFragment);
535					transaction.addToBackStack(null);
536					transaction.commit();
537					return;
538				}
539			}
540		} else {
541			transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
542		}
543		if (binding.secondaryFragment != null && secondaryFragment == null) {
544			transaction.replace(R.id.secondary_fragment, new ConversationFragment());
545		}
546		transaction.commit();
547	}
548
549	private void invalidateActionBarTitle() {
550		final ActionBar actionBar = getSupportActionBar();
551		if (actionBar != null) {
552			Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
553			if (mainFragment != null && mainFragment instanceof ConversationFragment) {
554				final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
555				if (conversation != null) {
556					actionBar.setTitle(conversation.getName());
557					actionBar.setDisplayHomeAsUpEnabled(true);
558					return;
559				}
560			}
561			actionBar.setTitle(R.string.app_name);
562			actionBar.setDisplayHomeAsUpEnabled(false);
563		}
564	}
565
566	@Override
567	public void onConversationArchived(Conversation conversation) {
568		if (performRedirectIfNecessary(conversation, false)) {
569			return;
570		}
571		Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
572		if (mainFragment != null && mainFragment instanceof ConversationFragment) {
573			try {
574				getFragmentManager().popBackStack();
575			} catch (IllegalStateException e) {
576				Log.w(Config.LOGTAG,"state loss while popping back state after archiving conversation",e);
577				//this usually means activity is no longer active; meaning on the next open we will run through this again
578			}
579			return;
580		}
581		Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment);
582		if (secondaryFragment != null && secondaryFragment instanceof ConversationFragment) {
583			if (((ConversationFragment) secondaryFragment).getConversation() == conversation) {
584				Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation);
585				if (suggestion != null) {
586					openConversation(suggestion, null);
587				}
588			}
589		}
590	}
591
592	@Override
593	public void onConversationsListItemUpdated() {
594		Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
595		if (fragment != null && fragment instanceof ConversationsOverviewFragment) {
596			((ConversationsOverviewFragment) fragment).refresh();
597		}
598	}
599
600	@Override
601	public void switchToConversation(Conversation conversation) {
602		Log.d(Config.LOGTAG, "override");
603		openConversation(conversation, null);
604	}
605
606	@Override
607	public void onConversationRead(Conversation conversation, String upToUuid) {
608		if (!mActivityPaused && pendingViewIntent.peek() == null) {
609			xmppConnectionService.sendReadMarker(conversation, upToUuid);
610		} else {
611			Log.d(Config.LOGTAG, "ignoring read callback. mActivityPaused=" + Boolean.toString(mActivityPaused));
612		}
613	}
614
615	@Override
616	public void onAccountUpdate() {
617		this.refreshUi();
618	}
619
620	@Override
621	public void onConversationUpdate() {
622		if (performRedirectIfNecessary(false)) {
623			return;
624		}
625		this.refreshUi();
626	}
627
628	@Override
629	public void onRosterUpdate() {
630		this.refreshUi();
631	}
632
633	@Override
634	public void OnUpdateBlocklist(OnUpdateBlocklist.Status status) {
635		this.refreshUi();
636	}
637
638	@Override
639	public void onShowErrorToast(int resId) {
640		runOnUiThread(() -> Toast.makeText(this, resId, Toast.LENGTH_SHORT).show());
641	}
642}