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_NICK = "nick";
 89	public static final String EXTRA_IS_PRIVATE_MESSAGE = "pm";
 90
 91	public static final int REQUEST_OPEN_MESSAGE = 0x9876;
 92	public static final int REQUEST_PLAY_PAUSE = 0x5432;
 93
 94
 95	//secondary fragment (when holding the conversation, must be initialized before refreshing the overview fragment
 96	private static final @IdRes
 97	int[] FRAGMENT_ID_NOTIFICATION_ORDER = {R.id.secondary_fragment, R.id.main_fragment};
 98	private final PendingItem<Intent> pendingViewIntent = new PendingItem<>();
 99	private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>();
100	private ActivityConversationsBinding binding;
101	private boolean mActivityPaused = true;
102	private AtomicBoolean mRedirectInProcess = new AtomicBoolean(false);
103
104	private static boolean isViewIntent(Intent i) {
105		return i != null && ACTION_VIEW_CONVERSATION.equals(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION);
106	}
107
108	private static Intent createLauncherIntent(Context context) {
109		final Intent intent = new Intent(context, ConversationsActivity.class);
110		intent.setAction(Intent.ACTION_MAIN);
111		intent.addCategory(Intent.CATEGORY_LAUNCHER);
112		return intent;
113	}
114
115	@Override
116	protected void refreshUiReal() {
117		for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
118			refreshFragment(id);
119		}
120	}
121
122	@Override
123	void onBackendConnected() {
124		if (performRedirectIfNecessary(true)) {
125			return;
126		}
127		xmppConnectionService.getNotificationService().setIsInForeground(true);
128		Intent intent = pendingViewIntent.pop();
129		if (intent != null) {
130			if (processViewIntent(intent)) {
131				if (binding.secondaryFragment != null) {
132					notifyFragmentOfBackendConnected(R.id.main_fragment);
133				}
134				invalidateActionBarTitle();
135				return;
136			}
137		}
138		for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
139			notifyFragmentOfBackendConnected(id);
140		}
141
142		ActivityResult activityResult = postponedActivityResult.pop();
143		if (activityResult != null) {
144			handleActivityResult(activityResult);
145		}
146
147		invalidateActionBarTitle();
148		if (binding.secondaryFragment != null && ConversationFragment.getConversation(this) == null) {
149			Conversation conversation = ConversationsOverviewFragment.getSuggestion(this);
150			if (conversation != null) {
151				openConversation(conversation, null);
152			}
153		}
154		showDialogsIfMainIsOverview();
155	}
156
157	private boolean performRedirectIfNecessary(boolean noAnimation) {
158		return performRedirectIfNecessary(null, noAnimation);
159	}
160
161	private boolean performRedirectIfNecessary(final Conversation ignore, final boolean noAnimation) {
162		if (xmppConnectionService == null) {
163			return false;
164		}
165		boolean isConversationsListEmpty = xmppConnectionService.isConversationsListEmpty(ignore);
166		if (isConversationsListEmpty && mRedirectInProcess.compareAndSet(false, true)) {
167			final Intent intent = getRedirectionIntent(noAnimation);
168			runOnUiThread(() -> {
169				startActivity(intent);
170				if (noAnimation) {
171					overridePendingTransition(0, 0);
172				}
173			});
174		}
175		return mRedirectInProcess.get();
176	}
177
178	private Intent getRedirectionIntent(boolean noAnimation) {
179		Account pendingAccount = xmppConnectionService.getPendingAccount();
180		Intent intent;
181		if (pendingAccount != null) {
182			intent = new Intent(this, EditAccountActivity.class);
183			intent.putExtra("jid", pendingAccount.getJid().asBareJid().toString());
184		} else {
185			if (xmppConnectionService.getAccounts().size() == 0) {
186				if (Config.X509_VERIFICATION) {
187					intent = new Intent(this, ManageAccountActivity.class);
188				} else if (Config.MAGIC_CREATE_DOMAIN != null) {
189					intent = new Intent(this, WelcomeActivity.class);
190					WelcomeActivity.addInviteUri(intent, getIntent());
191				} else {
192					intent = new Intent(this, EditAccountActivity.class);
193				}
194			} else {
195				intent = new Intent(this, StartConversationActivity.class);
196			}
197		}
198		intent.putExtra("init", true);
199		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
200		if (noAnimation) {
201			intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
202		}
203		return intent;
204	}
205
206	private void showDialogsIfMainIsOverview() {
207		if (xmppConnectionService == null) {
208			return;
209		}
210		final Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
211		if (fragment != null && fragment instanceof ConversationsOverviewFragment) {
212			if (ExceptionHelper.checkForCrash(this)) {
213				return;
214			}
215			openBatteryOptimizationDialogIfNeeded();
216		}
217	}
218
219	private String getBatteryOptimizationPreferenceKey() {
220		@SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
221		return "show_battery_optimization" + (device == null ? "" : device);
222	}
223
224	private void setNeverAskForBatteryOptimizationsAgain() {
225		getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
226	}
227
228	private void openBatteryOptimizationDialogIfNeeded() {
229		if (hasAccountWithoutPush()
230				&& isOptimizingBattery()
231				&& getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
232			AlertDialog.Builder builder = new AlertDialog.Builder(this);
233			builder.setTitle(R.string.battery_optimizations_enabled);
234			builder.setMessage(R.string.battery_optimizations_enabled_dialog);
235			builder.setPositiveButton(R.string.next, (dialog, which) -> {
236				Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
237				Uri uri = Uri.parse("package:" + getPackageName());
238				intent.setData(uri);
239				try {
240					startActivityForResult(intent, REQUEST_BATTERY_OP);
241				} catch (ActivityNotFoundException e) {
242					Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
243				}
244			});
245			builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
246			final AlertDialog dialog = builder.create();
247			dialog.setCanceledOnTouchOutside(false);
248			dialog.show();
249		}
250	}
251
252	private boolean hasAccountWithoutPush() {
253		for (Account account : xmppConnectionService.getAccounts()) {
254			if (account.getStatus() == Account.State.ONLINE && !xmppConnectionService.getPushManagementService().available(account)) {
255				return true;
256			}
257		}
258		return false;
259	}
260
261	private void notifyFragmentOfBackendConnected(@IdRes int id) {
262		final Fragment fragment = getFragmentManager().findFragmentById(id);
263		if (fragment != null && fragment instanceof OnBackendConnected) {
264			((OnBackendConnected) fragment).onBackendConnected();
265		}
266	}
267
268	private void refreshFragment(@IdRes int id) {
269		final Fragment fragment = getFragmentManager().findFragmentById(id);
270		if (fragment != null && fragment instanceof XmppFragment) {
271			((XmppFragment) fragment).refresh();
272		}
273	}
274
275	private boolean processViewIntent(Intent intent) {
276		String uuid = intent.getStringExtra(EXTRA_CONVERSATION);
277		Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null;
278		if (conversation == null) {
279			Log.d(Config.LOGTAG, "unable to view conversation with uuid:" + uuid);
280			return false;
281		}
282		openConversation(conversation, intent.getExtras());
283		return true;
284	}
285
286	@Override
287	public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
288		UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
289		if (grantResults.length > 0) {
290			if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
291				switch (requestCode) {
292					case REQUEST_OPEN_MESSAGE:
293						refreshUiReal();
294						ConversationFragment.openPendingMessage(this);
295						break;
296					case REQUEST_PLAY_PAUSE:
297						ConversationFragment.startStopPending(this);
298						break;
299				}
300			}
301		}
302	}
303
304	@Override
305	public void onActivityResult(int requestCode, int resultCode, final Intent data) {
306		super.onActivityResult(requestCode, resultCode, data);
307		ActivityResult activityResult = ActivityResult.of(requestCode, resultCode, data);
308		if (xmppConnectionService != null) {
309			handleActivityResult(activityResult);
310		} else {
311			this.postponedActivityResult.push(activityResult);
312		}
313	}
314
315	private void handleActivityResult(ActivityResult activityResult) {
316		if (activityResult.resultCode == Activity.RESULT_OK) {
317			handlePositiveActivityResult(activityResult.requestCode, activityResult.data);
318		} else {
319			handleNegativeActivityResult(activityResult.requestCode);
320		}
321	}
322
323	private void handleNegativeActivityResult(int requestCode) {
324		Conversation conversation = ConversationFragment.getConversationReliable(this);
325		switch (requestCode) {
326			case REQUEST_DECRYPT_PGP:
327				if (conversation == null) {
328					break;
329				}
330				conversation.getAccount().getPgpDecryptionService().giveUpCurrentDecryption();
331				break;
332			case REQUEST_BATTERY_OP:
333				setNeverAskForBatteryOptimizationsAgain();
334				break;
335		}
336	}
337
338	private void handlePositiveActivityResult(int requestCode, final Intent data) {
339		Conversation conversation = ConversationFragment.getConversationReliable(this);
340		if (conversation == null) {
341			Log.d(Config.LOGTAG, "conversation not found");
342			return;
343		}
344		switch (requestCode) {
345			case REQUEST_DECRYPT_PGP:
346				conversation.getAccount().getPgpDecryptionService().continueDecryption(data);
347				break;
348			case REQUEST_CHOOSE_PGP_ID:
349				long id = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, 0);
350				if (id != 0) {
351					conversation.getAccount().setPgpSignId(id);
352					announcePgp(conversation.getAccount(), null, null, onOpenPGPKeyPublished);
353				} else {
354					choosePgpSignId(conversation.getAccount());
355				}
356				break;
357			case REQUEST_ANNOUNCE_PGP:
358				announcePgp(conversation.getAccount(), conversation, data, onOpenPGPKeyPublished);
359				break;
360		}
361	}
362
363	@Override
364	protected void onCreate(final Bundle savedInstanceState) {
365		super.onCreate(savedInstanceState);
366		OmemoSetting.load(this);
367		new EmojiService(this).init();
368		this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations);
369		setSupportActionBar((Toolbar) binding.toolbar);
370		configureActionBar(getSupportActionBar());
371		this.getFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle);
372		this.getFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview);
373		this.initializeFragments();
374		this.invalidateActionBarTitle();
375		final Intent intent;
376		if (savedInstanceState == null) {
377			intent = getIntent();
378		} else {
379			intent = savedInstanceState.getParcelable("intent");
380		}
381		if (isViewIntent(intent)) {
382			pendingViewIntent.push(intent);
383			setIntent(createLauncherIntent(this));
384		}
385	}
386
387	@Override
388	public boolean onCreateOptionsMenu(Menu menu) {
389		getMenuInflater().inflate(R.menu.activity_conversations, menu);
390		MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
391		if (qrCodeScanMenuItem != null) {
392			if (isCameraFeatureAvailable()) {
393				Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
394				boolean visible = getResources().getBoolean(R.bool.show_qr_code_scan)
395						&& fragment != null
396						&& fragment instanceof ConversationsOverviewFragment;
397				qrCodeScanMenuItem.setVisible(visible);
398			} else {
399				qrCodeScanMenuItem.setVisible(false);
400			}
401		}
402		return super.onCreateOptionsMenu(menu);
403	}
404
405	@Override
406	public void onConversationSelected(Conversation conversation) {
407		if (ConversationFragment.getConversation(this) == conversation) {
408			Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open");
409			return;
410		}
411		openConversation(conversation, null);
412	}
413
414	private void openConversation(Conversation conversation, Bundle extras) {
415		ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment);
416		final boolean mainNeedsRefresh;
417		if (conversationFragment == null) {
418			mainNeedsRefresh = false;
419			Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
420			if (mainFragment != null && mainFragment instanceof ConversationFragment) {
421				conversationFragment = (ConversationFragment) mainFragment;
422			} else {
423				conversationFragment = new ConversationFragment();
424				FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
425				fragmentTransaction.replace(R.id.main_fragment, conversationFragment);
426				fragmentTransaction.addToBackStack(null);
427				fragmentTransaction.commitAllowingStateLoss(); //allowing state loss is probably fine since view intents et all are already stored and a click can probably be 'ignored'
428			}
429		} else {
430			mainNeedsRefresh = true;
431		}
432		conversationFragment.reInit(conversation, extras == null ? new Bundle() : extras);
433		if (mainNeedsRefresh) {
434			refreshFragment(R.id.main_fragment);
435		} else {
436			invalidateActionBarTitle();
437		}
438	}
439
440	@Override
441	public boolean onOptionsItemSelected(MenuItem item) {
442		if (MenuDoubleTabUtil.shouldIgnoreTap()) {
443			return false;
444		}
445		switch (item.getItemId()) {
446			case android.R.id.home:
447				FragmentManager fm = getFragmentManager();
448				if (fm.getBackStackEntryCount() > 0) {
449					fm.popBackStack();
450					return true;
451				}
452				break;
453			case R.id.action_scan_qr_code:
454				UriHandlerActivity.scan(this);
455				return true;
456		}
457		return super.onOptionsItemSelected(item);
458	}
459
460	@Override
461	public void onSaveInstanceState(Bundle savedInstanceState) {
462		Intent pendingIntent = pendingViewIntent.peek();
463		savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent());
464		super.onSaveInstanceState(savedInstanceState);
465	}
466
467	@Override
468	protected void onStart() {
469		final int theme = findTheme();
470		if (this.mTheme != theme) {
471			this.mSkipBackgroundBinding = true;
472			recreate();
473		} else {
474			this.mSkipBackgroundBinding = false;
475		}
476		mRedirectInProcess.set(false);
477		super.onStart();
478	}
479
480	@Override
481	protected void onNewIntent(final Intent intent) {
482		if (isViewIntent(intent)) {
483			if (xmppConnectionService != null) {
484				processViewIntent(intent);
485			} else {
486				pendingViewIntent.push(intent);
487			}
488		}
489		setIntent(createLauncherIntent(this));
490	}
491
492	@Override
493	public void onPause() {
494		this.mActivityPaused = true;
495		super.onPause();
496	}
497
498	@Override
499	public void onResume() {
500		super.onResume();
501		this.mActivityPaused = false;
502	}
503
504	private void initializeFragments() {
505		FragmentTransaction transaction = getFragmentManager().beginTransaction();
506		Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
507		Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment);
508		if (mainFragment != null) {
509			if (binding.secondaryFragment != null) {
510				if (mainFragment instanceof ConversationFragment) {
511					getFragmentManager().popBackStack();
512					transaction.remove(mainFragment);
513					transaction.commit();
514					getFragmentManager().executePendingTransactions();
515					transaction = getFragmentManager().beginTransaction();
516					transaction.replace(R.id.secondary_fragment, mainFragment);
517					transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
518					transaction.commit();
519					return;
520				}
521			} else {
522				if (secondaryFragment != null && secondaryFragment instanceof ConversationFragment) {
523					transaction.remove(secondaryFragment);
524					transaction.commit();
525					getFragmentManager().executePendingTransactions();
526					transaction = getFragmentManager().beginTransaction();
527					transaction.replace(R.id.main_fragment, secondaryFragment);
528					transaction.addToBackStack(null);
529					transaction.commit();
530					return;
531				}
532			}
533		} else {
534			transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
535		}
536		if (binding.secondaryFragment != null && secondaryFragment == null) {
537			transaction.replace(R.id.secondary_fragment, new ConversationFragment());
538		}
539		transaction.commit();
540	}
541
542	private void invalidateActionBarTitle() {
543		final ActionBar actionBar = getSupportActionBar();
544		if (actionBar != null) {
545			Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
546			if (mainFragment != null && mainFragment instanceof ConversationFragment) {
547				final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
548				if (conversation != null) {
549					actionBar.setTitle(conversation.getName());
550					actionBar.setDisplayHomeAsUpEnabled(true);
551					return;
552				}
553			}
554			actionBar.setTitle(R.string.app_name);
555			actionBar.setDisplayHomeAsUpEnabled(false);
556		}
557	}
558
559	@Override
560	public void onConversationArchived(Conversation conversation) {
561		if (performRedirectIfNecessary(conversation, false)) {
562			return;
563		}
564		Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
565		if (mainFragment != null && mainFragment instanceof ConversationFragment) {
566			getFragmentManager().popBackStack();
567			return;
568		}
569		Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment);
570		if (secondaryFragment != null && secondaryFragment instanceof ConversationFragment) {
571			if (((ConversationFragment) secondaryFragment).getConversation() == conversation) {
572				Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation);
573				if (suggestion != null) {
574					openConversation(suggestion, null);
575				}
576			}
577		}
578	}
579
580	@Override
581	public void onConversationsListItemUpdated() {
582		Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
583		if (fragment != null && fragment instanceof ConversationsOverviewFragment) {
584			((ConversationsOverviewFragment) fragment).refresh();
585		}
586	}
587
588	@Override
589	public void switchToConversation(Conversation conversation) {
590		Log.d(Config.LOGTAG, "override");
591		openConversation(conversation, null);
592	}
593
594	@Override
595	public void onConversationRead(Conversation conversation) {
596		if (!mActivityPaused && pendingViewIntent.peek() == null) {
597			xmppConnectionService.sendReadMarker(conversation);
598		} else {
599			Log.d(Config.LOGTAG, "ignoring read callback. mActivityPaused=" + Boolean.toString(mActivityPaused));
600		}
601	}
602
603	@Override
604	public void onAccountUpdate() {
605		this.refreshUi();
606	}
607
608	@Override
609	public void onConversationUpdate() {
610		if (performRedirectIfNecessary(false)) {
611			return;
612		}
613		this.refreshUi();
614	}
615
616	@Override
617	public void onRosterUpdate() {
618		this.refreshUi();
619	}
620
621	@Override
622	public void OnUpdateBlocklist(OnUpdateBlocklist.Status status) {
623		this.refreshUi();
624	}
625
626	@Override
627	public void onShowErrorToast(int resId) {
628		runOnUiThread(() -> Toast.makeText(this, resId, Toast.LENGTH_SHORT).show());
629	}
630}