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