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}