1package eu.siacs.conversations.ui;
2
3import android.annotation.SuppressLint;
4import android.support.v7.app.AlertDialog;
5import android.app.FragmentTransaction;
6import android.app.PendingIntent;
7import android.content.ActivityNotFoundException;
8import android.content.ClipData;
9import android.content.Intent;
10import android.content.IntentSender.SendIntentException;
11import android.net.Uri;
12import android.os.Build;
13import android.os.Bundle;
14import android.provider.Settings;
15import android.support.v4.widget.SlidingPaneLayout;
16import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
17import android.support.v7.app.ActionBar;
18import android.util.Log;
19import android.util.Pair;
20import android.view.KeyEvent;
21import android.view.Menu;
22import android.view.MenuItem;
23import android.view.Surface;
24import android.view.View;
25import android.widget.AdapterView;
26import android.widget.AdapterView.OnItemClickListener;
27import android.widget.ArrayAdapter;
28import android.widget.Toast;
29
30
31import java.util.ArrayList;
32import java.util.Iterator;
33import java.util.List;
34import java.util.concurrent.atomic.AtomicBoolean;
35
36import de.timroes.android.listview.EnhancedListView;
37import eu.siacs.conversations.Config;
38import eu.siacs.conversations.R;
39import eu.siacs.conversations.crypto.axolotl.AxolotlService;
40import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
41import eu.siacs.conversations.entities.Account;
42import eu.siacs.conversations.entities.Blockable;
43import eu.siacs.conversations.entities.Conversation;
44import eu.siacs.conversations.entities.Message;
45import eu.siacs.conversations.services.XmppConnectionService;
46import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
47import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
48import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
49import eu.siacs.conversations.ui.adapter.ConversationAdapter;
50import eu.siacs.conversations.ui.service.EmojiService;
51import eu.siacs.conversations.utils.ExceptionHelper;
52import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
53import eu.siacs.conversations.xmpp.XmppConnection;
54import eu.siacs.conversations.xmpp.jid.InvalidJidException;
55import eu.siacs.conversations.xmpp.jid.Jid;
56
57public class ConversationActivity extends XmppActivity
58 implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast {
59
60 public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW";
61 public static final String CONVERSATION = "conversationUuid";
62 public static final String EXTRA_DOWNLOAD_UUID = "eu.siacs.conversations.download_uuid";
63 public static final String TEXT = "text";
64 public static final String NICK = "nick";
65 public static final String PRIVATE_MESSAGE = "pm";
66
67 private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
68 private static final String STATE_PANEL_OPEN = "state_panel_open";
69 private static final String STATE_PENDING_URI = "state_pending_uri";
70 private static final String STATE_FIRST_VISIBLE = "first_visible";
71 private static final String STATE_OFFSET_FROM_TOP = "offset_from_top";
72
73 private String mOpenConversation = null;
74 private boolean mPanelOpen = true;
75 private AtomicBoolean mShouldPanelBeOpen = new AtomicBoolean(false);
76 private Pair<Integer, Integer> mScrollPosition = null;
77 private boolean forbidProcessingPendings = false;
78
79 private boolean conversationWasSelectedByKeyboard = false;
80
81 private View mContentView;
82
83 private List<Conversation> conversationList = new ArrayList<>();
84 private Conversation swipedConversation = null;
85 private Conversation mSelectedConversation = null;
86 private EnhancedListView listView;
87 private ConversationFragment mConversationFragment;
88
89 private ArrayAdapter<Conversation> listAdapter;
90
91 private boolean mActivityPaused = false;
92 private AtomicBoolean mRedirected = new AtomicBoolean(false);
93 private boolean mUnprocessedNewIntent = false;
94
95 public Conversation getSelectedConversation() {
96 return this.mSelectedConversation;
97 }
98
99 public void setSelectedConversation(Conversation conversation) {
100 this.mSelectedConversation = conversation;
101 }
102
103 public void showConversationsOverview() {
104 if (mConversationFragment != null) {
105 mConversationFragment.stopScrolling();
106 }
107 if (mContentView instanceof SlidingPaneLayout) {
108 SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
109 mShouldPanelBeOpen.set(true);
110 mSlidingPaneLayout.openPane();
111 }
112 }
113
114 @Override
115 protected String getShareableUri() {
116 Conversation conversation = getSelectedConversation();
117 if (conversation != null) {
118 return conversation.getAccount().getShareableUri();
119 } else {
120 return "";
121 }
122 }
123
124 public void hideConversationsOverview() {
125 if (mContentView instanceof SlidingPaneLayout) {
126 SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
127 mShouldPanelBeOpen.set(false);
128 mSlidingPaneLayout.closePane();
129 }
130 }
131
132 public boolean isConversationsOverviewHideable() {
133 return mContentView instanceof SlidingPaneLayout;
134 }
135
136 public boolean isConversationsOverviewVisable() {
137 if (mContentView instanceof SlidingPaneLayout) {
138 return mShouldPanelBeOpen.get();
139 } else {
140 return true;
141 }
142 }
143
144 @Override
145 protected void onCreate(final Bundle savedInstanceState) {
146 super.onCreate(savedInstanceState);
147 new EmojiService(this).init();
148 if (savedInstanceState != null) {
149 mOpenConversation = savedInstanceState.getString(STATE_OPEN_CONVERSATION, null);
150 mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
151 int pos = savedInstanceState.getInt(STATE_FIRST_VISIBLE, -1);
152 int offset = savedInstanceState.getInt(STATE_OFFSET_FROM_TOP, 1);
153 if (pos >= 0 && offset <= 0) {
154 Log.d(Config.LOGTAG, "retrieved scroll position from instanceState " + pos + ":" + offset);
155 mScrollPosition = new Pair<>(pos, offset);
156 } else {
157 mScrollPosition = null;
158 }
159 }
160
161 setContentView(R.layout.fragment_conversations_overview);
162
163 this.mConversationFragment = new ConversationFragment();
164 FragmentTransaction transaction = getFragmentManager().beginTransaction();
165 transaction.replace(R.id.selected_conversation, this.mConversationFragment, "conversation");
166 transaction.commit();
167
168 this.listView = findViewById(R.id.list);
169 this.listAdapter = new ConversationAdapter(this, conversationList);
170 this.listView.setAdapter(this.listAdapter);
171 this.listView.setSwipeDirection(EnhancedListView.SwipeDirection.END);
172
173 final ActionBar actionBar = getSupportActionBar();
174 if (actionBar != null) {
175 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE);
176 }
177
178 listView.setOnItemClickListener(new OnItemClickListener() {
179
180 @Override
181 public void onItemClick(AdapterView<?> arg0, View clickedView,
182 int position, long arg3) {
183 if (getSelectedConversation() != conversationList.get(position)) {
184 ConversationActivity.this.mConversationFragment.stopScrolling();
185 setSelectedConversation(conversationList.get(position));
186 ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation());
187 conversationWasSelectedByKeyboard = false;
188 }
189 hideConversationsOverview();
190 openConversation();
191 }
192 });
193
194 listView.setDismissCallback(new EnhancedListView.OnDismissCallback() {
195
196 @Override
197 public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) {
198
199 final int index = listView.getFirstVisiblePosition();
200 View v = listView.getChildAt(0);
201 final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
202
203 try {
204 swipedConversation = listAdapter.getItem(position);
205 } catch (IndexOutOfBoundsException e) {
206 return null;
207 }
208 listAdapter.remove(swipedConversation);
209 xmppConnectionService.markRead(swipedConversation);
210
211 final boolean formerlySelected = (getSelectedConversation() == swipedConversation);
212 if (position == 0 && listAdapter.getCount() == 0) {
213 endConversation(swipedConversation, false, true);
214 return null;
215 } else if (formerlySelected) {
216 setSelectedConversation(listAdapter.getItem(0));
217 ConversationActivity.this.mConversationFragment
218 .reInit(getSelectedConversation());
219 }
220
221 return new EnhancedListView.Undoable() {
222
223 @Override
224 public void undo() {
225 listAdapter.insert(swipedConversation, position);
226 if (formerlySelected) {
227 setSelectedConversation(swipedConversation);
228 ConversationActivity.this.mConversationFragment
229 .reInit(getSelectedConversation());
230 }
231 swipedConversation = null;
232 listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top);
233 }
234
235 @Override
236 public void discard() {
237 if (!swipedConversation.isRead()
238 && swipedConversation.getMode() == Conversation.MODE_SINGLE) {
239 swipedConversation = null;
240 return;
241 }
242 endConversation(swipedConversation, false, false);
243 swipedConversation = null;
244 }
245
246 @Override
247 public String getTitle() {
248 if (swipedConversation.getMode() == Conversation.MODE_MULTI) {
249 return getResources().getString(R.string.title_undo_swipe_out_muc);
250 } else {
251 return getResources().getString(R.string.title_undo_swipe_out_conversation);
252 }
253 }
254 };
255 }
256 });
257 listView.enableSwipeToDismiss();
258 listView.setSwipingLayout(R.id.swipeable_item);
259 listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP);
260 listView.setUndoHideDelay(5000);
261 listView.setRequireTouchBeforeDismiss(false);
262
263 mContentView = findViewById(R.id.content_view_spl);
264 if (mContentView == null) {
265 mContentView = findViewById(R.id.content_view_ll);
266 }
267 if (mContentView instanceof SlidingPaneLayout) {
268 SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
269 mSlidingPaneLayout.setShadowResource(R.drawable.es_slidingpane_shadow);
270 mSlidingPaneLayout.setSliderFadeColor(0);
271 mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() {
272
273 @Override
274 public void onPanelOpened(View arg0) {
275 mShouldPanelBeOpen.set(true);
276 updateActionBarTitle();
277 invalidateOptionsMenu();
278 hideKeyboard();
279 if (xmppConnectionServiceBound) {
280 xmppConnectionService.getNotificationService().setOpenConversation(null);
281 }
282 closeContextMenu();
283 }
284
285 @Override
286 public void onPanelClosed(View arg0) {
287 mShouldPanelBeOpen.set(false);
288 listView.discardUndo();
289 openConversation();
290 }
291
292 @Override
293 public void onPanelSlide(View arg0, float arg1) {
294 // TODO Auto-generated method stub
295
296 }
297 });
298 }
299 }
300
301 @Override
302 public void switchToConversation(Conversation conversation) {
303 setSelectedConversation(conversation);
304 runOnUiThread(() -> {
305 ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation());
306 openConversation();
307 });
308 }
309
310 private void updateActionBarTitle() {
311 updateActionBarTitle(isConversationsOverviewHideable() && !isConversationsOverviewVisable());
312 }
313
314 private void updateActionBarTitle(boolean titleShouldBeName) {
315 final ActionBar ab = getSupportActionBar();
316 final Conversation conversation = getSelectedConversation();
317 if (ab != null) {
318 if (titleShouldBeName && conversation != null) {
319 if ((ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != ActionBar.DISPLAY_HOME_AS_UP) {
320 ab.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE);
321 }
322 if (conversation.getMode() == Conversation.MODE_SINGLE || useSubjectToIdentifyConference()) {
323 ab.setTitle(conversation.getName());
324 } else {
325 ab.setTitle(conversation.getJid().toBareJid().toString());
326 }
327 } else {
328 if ((ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) == ActionBar.DISPLAY_HOME_AS_UP) {
329 ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE);
330 }
331 ab.setTitle(R.string.app_name);
332 }
333 }
334 }
335
336 private void openConversation() {
337 this.updateActionBarTitle();
338 this.invalidateOptionsMenu();
339 if (xmppConnectionServiceBound) {
340 final Conversation conversation = getSelectedConversation();
341 xmppConnectionService.getNotificationService().setOpenConversation(conversation);
342 sendReadMarkerIfNecessary(conversation);
343 }
344 listAdapter.notifyDataSetChanged();
345 }
346
347 public void sendReadMarkerIfNecessary(final Conversation conversation) {
348 if (!mActivityPaused && !mUnprocessedNewIntent && conversation != null) {
349 xmppConnectionService.sendReadMarker(conversation);
350 }
351 }
352
353 @Override
354 public boolean onCreateOptionsMenu(Menu menu) {
355 getMenuInflater().inflate(R.menu.activity_conversations, menu);
356 return super.onCreateOptionsMenu(menu);
357 }
358
359 @Override
360 public boolean onOptionsItemSelected(final MenuItem item) {
361 if (item.getItemId() == android.R.id.home) {
362 showConversationsOverview();
363 return true;
364 } else if (item.getItemId() == R.id.action_add) {
365 startActivity(new Intent(this, StartConversationActivity.class));
366 return true;
367 } else {
368 return super.onOptionsItemSelected(item);
369 }
370 }
371
372 public void endConversation(Conversation conversation) {
373 endConversation(conversation, true, true);
374 }
375
376 public void endConversation(Conversation conversation, boolean showOverview, boolean reinit) {
377 if (showOverview) {
378 showConversationsOverview();
379 }
380 xmppConnectionService.archiveConversation(conversation);
381 if (reinit) {
382 if (conversationList.size() > 0) {
383 setSelectedConversation(conversationList.get(0));
384 this.mConversationFragment.reInit(getSelectedConversation());
385 } else {
386 setSelectedConversation(null);
387 if (mRedirected.compareAndSet(false, true)) {
388 Intent intent = new Intent(this, StartConversationActivity.class);
389 intent.putExtra("init", true);
390 startActivity(intent);
391 finish();
392 }
393 }
394 }
395 }
396
397 @Override
398 public void onBackPressed() {
399 if (!isConversationsOverviewVisable()) {
400 showConversationsOverview();
401 } else {
402 super.onBackPressed();
403 }
404 }
405
406 @Override
407 public boolean onKeyUp(int key, KeyEvent event) {
408 int rotation = getWindowManager().getDefaultDisplay().getRotation();
409 final int upKey;
410 final int downKey;
411 switch (rotation) {
412 case Surface.ROTATION_90:
413 upKey = KeyEvent.KEYCODE_DPAD_LEFT;
414 downKey = KeyEvent.KEYCODE_DPAD_RIGHT;
415 break;
416 case Surface.ROTATION_180:
417 upKey = KeyEvent.KEYCODE_DPAD_DOWN;
418 downKey = KeyEvent.KEYCODE_DPAD_UP;
419 break;
420 case Surface.ROTATION_270:
421 upKey = KeyEvent.KEYCODE_DPAD_RIGHT;
422 downKey = KeyEvent.KEYCODE_DPAD_LEFT;
423 break;
424 case Surface.ROTATION_0:
425 default:
426 upKey = KeyEvent.KEYCODE_DPAD_UP;
427 downKey = KeyEvent.KEYCODE_DPAD_DOWN;
428 }
429 final boolean modifier = event.isCtrlPressed() || (event.getMetaState() & KeyEvent.META_ALT_LEFT_ON) != 0;
430 if (modifier && key == KeyEvent.KEYCODE_TAB && isConversationsOverviewHideable()) {
431 toggleConversationsOverview();
432 return true;
433 } else if (modifier && key == KeyEvent.KEYCODE_SPACE) {
434 startActivity(new Intent(this, StartConversationActivity.class));
435 return true;
436 } else if (modifier && key == downKey) {
437 if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) {
438 showConversationsOverview();
439 ;
440 }
441 return selectDownConversation();
442 } else if (modifier && key == upKey) {
443 if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) {
444 showConversationsOverview();
445 }
446 return selectUpConversation();
447 } else if (modifier && key == KeyEvent.KEYCODE_1) {
448 return openConversationByIndex(0);
449 } else if (modifier && key == KeyEvent.KEYCODE_2) {
450 return openConversationByIndex(1);
451 } else if (modifier && key == KeyEvent.KEYCODE_3) {
452 return openConversationByIndex(2);
453 } else if (modifier && key == KeyEvent.KEYCODE_4) {
454 return openConversationByIndex(3);
455 } else if (modifier && key == KeyEvent.KEYCODE_5) {
456 return openConversationByIndex(4);
457 } else if (modifier && key == KeyEvent.KEYCODE_6) {
458 return openConversationByIndex(5);
459 } else if (modifier && key == KeyEvent.KEYCODE_7) {
460 return openConversationByIndex(6);
461 } else if (modifier && key == KeyEvent.KEYCODE_8) {
462 return openConversationByIndex(7);
463 } else if (modifier && key == KeyEvent.KEYCODE_9) {
464 return openConversationByIndex(8);
465 } else if (modifier && key == KeyEvent.KEYCODE_0) {
466 return openConversationByIndex(9);
467 } else {
468 return super.onKeyUp(key, event);
469 }
470 }
471
472 private void toggleConversationsOverview() {
473 if (isConversationsOverviewVisable()) {
474 hideConversationsOverview();
475 if (mConversationFragment != null) {
476 mConversationFragment.setFocusOnInputField();
477 }
478 } else {
479 showConversationsOverview();
480 }
481 }
482
483 private boolean selectUpConversation() {
484 if (this.mSelectedConversation != null) {
485 int index = this.conversationList.indexOf(this.mSelectedConversation);
486 if (index > 0) {
487 return openConversationByIndex(index - 1);
488 }
489 }
490 return false;
491 }
492
493 private boolean selectDownConversation() {
494 if (this.mSelectedConversation != null) {
495 int index = this.conversationList.indexOf(this.mSelectedConversation);
496 if (index != -1 && index < this.conversationList.size() - 1) {
497 return openConversationByIndex(index + 1);
498 }
499 }
500 return false;
501 }
502
503 private boolean openConversationByIndex(int index) {
504 try {
505 this.conversationWasSelectedByKeyboard = true;
506 this.mConversationFragment.stopScrolling();
507 setSelectedConversation(this.conversationList.get(index));
508 this.mConversationFragment.reInit(getSelectedConversation());
509 if (index > listView.getLastVisiblePosition() - 1 || index < listView.getFirstVisiblePosition() + 1) {
510 this.listView.setSelection(index);
511 }
512 openConversation();
513 return true;
514 } catch (IndexOutOfBoundsException e) {
515 return false;
516 }
517 }
518
519 @Override
520 protected void onNewIntent(final Intent intent) {
521 if (intent != null && ACTION_VIEW_CONVERSATION.equals(intent.getAction())) {
522 mOpenConversation = null;
523 mUnprocessedNewIntent = true;
524 if (xmppConnectionServiceBound) {
525 handleViewConversationIntent(intent);
526 intent.setAction(Intent.ACTION_MAIN);
527 } else {
528 setIntent(intent);
529 }
530 }
531 }
532
533 @Override
534 public void onStart() {
535 super.onStart();
536 this.mRedirected.set(false);
537 if (this.xmppConnectionServiceBound) {
538 this.onBackendConnected();
539 }
540 if (conversationList.size() >= 1) {
541 this.onConversationUpdate();
542 }
543 }
544
545 @Override
546 public void onPause() {
547 listView.discardUndo();
548 super.onPause();
549 this.mActivityPaused = true;
550 }
551
552 @Override
553 public void onResume() {
554 super.onResume();
555 final int theme = findTheme();
556 final boolean usingEnterKey = usingEnterKey();
557 if (this.mTheme != theme || usingEnterKey != mUsingEnterKey) {
558 recreate();
559 }
560 this.mActivityPaused = false;
561 if (!isConversationsOverviewVisable() || !isConversationsOverviewHideable()) {
562 sendReadMarkerIfNecessary(getSelectedConversation());
563 }
564 }
565
566 @Override
567 public void onSaveInstanceState(final Bundle savedInstanceState) {
568 Conversation conversation = getSelectedConversation();
569 if (conversation != null) {
570 savedInstanceState.putString(STATE_OPEN_CONVERSATION, conversation.getUuid());
571 Pair<Integer, Integer> scrollPosition = mConversationFragment.getScrollPosition();
572 if (scrollPosition != null) {
573 savedInstanceState.putInt(STATE_FIRST_VISIBLE, scrollPosition.first);
574 savedInstanceState.putInt(STATE_OFFSET_FROM_TOP, scrollPosition.second);
575 }
576 } else {
577 savedInstanceState.remove(STATE_OPEN_CONVERSATION);
578 }
579 savedInstanceState.putBoolean(STATE_PANEL_OPEN, isConversationsOverviewVisable());
580 /*if (this.mPendingImageUris.size() >= 1) {
581 Log.d(Config.LOGTAG, "ConversationsActivity.onSaveInstanceState() - saving pending image uri");
582 savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString());
583 } else {
584 savedInstanceState.remove(STATE_PENDING_URI);
585 }*/
586 super.onSaveInstanceState(savedInstanceState);
587 }
588
589 private void clearPending() {
590 mConversationFragment.clearPending();
591 }
592
593 private void redirectToStartConversationActivity(boolean noAnimation) {
594 Account pendingAccount = xmppConnectionService.getPendingAccount();
595 if (pendingAccount == null) {
596 Intent startConversationActivity = new Intent(this, StartConversationActivity.class);
597 startConversationActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
598 if (noAnimation) {
599 startConversationActivity.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
600 }
601 startConversationActivity.putExtra("init", true);
602 startActivity(startConversationActivity);
603 if (noAnimation) {
604 overridePendingTransition(0, 0);
605 }
606 } else {
607 switchToAccount(pendingAccount, true);
608 }
609 }
610
611 @Override
612 void onBackendConnected() {
613 this.xmppConnectionService.getNotificationService().setIsInForeground(true);
614 updateConversationList();
615
616 if (mPendingConferenceInvite != null) {
617 if (mPendingConferenceInvite.execute(this)) {
618 mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
619 mToast.show();
620 }
621 mPendingConferenceInvite = null;
622 }
623
624 final Intent intent = getIntent();
625
626 if (xmppConnectionService.getAccounts().size() == 0) {
627 if (mRedirected.compareAndSet(false, true)) {
628 if (Config.X509_VERIFICATION) {
629 Intent redirectionIntent = new Intent(this, ManageAccountActivity.class);
630 redirectionIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
631 startActivity(redirectionIntent);
632 overridePendingTransition(0, 0);
633 } else if (Config.MAGIC_CREATE_DOMAIN != null) {
634 WelcomeActivity.launch(this);
635 } else {
636 Intent editAccount = new Intent(this, EditAccountActivity.class);
637 editAccount.putExtra("init", true);
638 editAccount.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
639 startActivity(editAccount);
640 overridePendingTransition(0, 0);
641 }
642 }
643 } else if (conversationList.size() <= 0) {
644 if (mRedirected.compareAndSet(false, true)) {
645 redirectToStartConversationActivity(true);
646 }
647 } else if (selectConversationByUuid(mOpenConversation)) {
648 if (mPanelOpen) {
649 showConversationsOverview();
650 } else {
651 if (isConversationsOverviewHideable()) {
652 openConversation();
653 updateActionBarTitle(true);
654 }
655 }
656 if (this.mConversationFragment.reInit(getSelectedConversation())) {
657 Log.d(Config.LOGTAG, "setting scroll position on fragment");
658 this.mConversationFragment.setScrollPosition(mScrollPosition);
659 }
660 mOpenConversation = null;
661 } else if (intent != null && ACTION_VIEW_CONVERSATION.equals(intent.getAction())) {
662 clearPending();
663 handleViewConversationIntent(intent);
664 intent.setAction(Intent.ACTION_MAIN);
665 } else if (getSelectedConversation() == null) {
666 reInitLatestConversation();
667 } else {
668 this.mConversationFragment.messageListAdapter.updatePreferences();
669 this.mConversationFragment.messagesView.invalidateViews();
670 this.mConversationFragment.setupIme();
671 }
672
673 mConversationFragment.onBackendConnected();
674
675 if (!ExceptionHelper.checkForCrash(this, this.xmppConnectionService) && !mRedirected.get()) {
676 openBatteryOptimizationDialogIfNeeded();
677 }
678 if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) {
679 xmppConnectionService.getNotificationService().setOpenConversation(null);
680 } else {
681 xmppConnectionService.getNotificationService().setOpenConversation(getSelectedConversation());
682 }
683 }
684
685 private boolean isStopping() {
686 if (Build.VERSION.SDK_INT >= 17) {
687 return isFinishing() || isDestroyed();
688 } else {
689 return isFinishing();
690 }
691 }
692
693 private void reInitLatestConversation() {
694 showConversationsOverview();
695 clearPending();
696 setSelectedConversation(conversationList.get(0));
697 this.mConversationFragment.reInit(getSelectedConversation());
698 }
699
700 private void handleViewConversationIntent(final Intent intent) {
701 final String uuid = intent.getStringExtra(CONVERSATION);
702 final String downloadUuid = intent.getStringExtra(EXTRA_DOWNLOAD_UUID);
703 final String text = intent.getStringExtra(TEXT);
704 final String nick = intent.getStringExtra(NICK);
705 final boolean pm = intent.getBooleanExtra(PRIVATE_MESSAGE, false);
706 this.mConversationFragment.stopScrolling();
707 if (selectConversationByUuid(uuid)) {
708 this.mConversationFragment.reInit(getSelectedConversation());
709 if (nick != null) {
710 if (pm) {
711 Jid jid = getSelectedConversation().getJid();
712 try {
713 Jid next = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), nick);
714 this.mConversationFragment.privateMessageWith(next);
715 } catch (final InvalidJidException ignored) {
716 //do nothing
717 }
718 } else {
719 this.mConversationFragment.highlightInConference(nick);
720 }
721 } else {
722 this.mConversationFragment.appendText(text);
723 }
724 hideConversationsOverview();
725 mUnprocessedNewIntent = false;
726 openConversation();
727 if (mContentView instanceof SlidingPaneLayout) {
728 updateActionBarTitle(true); //fixes bug where slp isn't properly closed yet
729 }
730 if (downloadUuid != null) {
731 final Message message = mSelectedConversation.findMessageWithFileAndUuid(downloadUuid);
732 if (message != null) {
733 //startDownloadable(message);
734 }
735 }
736 } else {
737 mUnprocessedNewIntent = false;
738 }
739 }
740
741 private boolean selectConversationByUuid(String uuid) {
742 if (uuid == null) {
743 return false;
744 }
745 for (Conversation aConversationList : conversationList) {
746 if (aConversationList.getUuid().equals(uuid)) {
747 setSelectedConversation(aConversationList);
748 return true;
749 }
750 }
751 return false;
752 }
753
754 @Override
755 protected void unregisterListeners() {
756 super.unregisterListeners();
757 xmppConnectionService.getNotificationService().setOpenConversation(null);
758 }
759
760 @Override
761 protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
762 super.onActivityResult(requestCode, resultCode, data);
763 if (resultCode != RESULT_OK) {
764 if (requestCode == REQUEST_BATTERY_OP) {
765 setNeverAskForBatteryOptimizationsAgain();
766 }
767 }
768 }
769
770 public long getMaxHttpUploadSize(Conversation conversation) {
771 final XmppConnection connection = conversation.getAccount().getXmppConnection();
772 return connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize();
773 }
774
775 private String getBatteryOptimizationPreferenceKey() {
776 @SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
777 return "show_battery_optimization" + (device == null ? "" : device);
778 }
779
780 private void setNeverAskForBatteryOptimizationsAgain() {
781 getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
782 }
783
784 private void openBatteryOptimizationDialogIfNeeded() {
785 if (hasAccountWithoutPush()
786 && isOptimizingBattery()
787 && getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
788 AlertDialog.Builder builder = new AlertDialog.Builder(this);
789 builder.setTitle(R.string.battery_optimizations_enabled);
790 builder.setMessage(R.string.battery_optimizations_enabled_dialog);
791 builder.setPositiveButton(R.string.next, (dialog, which) -> {
792 Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
793 Uri uri = Uri.parse("package:" + getPackageName());
794 intent.setData(uri);
795 try {
796 startActivityForResult(intent, REQUEST_BATTERY_OP);
797 } catch (ActivityNotFoundException e) {
798 Toast.makeText(ConversationActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
799 }
800 });
801 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
802 builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
803 }
804 AlertDialog dialog = builder.create();
805 dialog.setCanceledOnTouchOutside(false);
806 dialog.show();
807 }
808 }
809
810 private boolean hasAccountWithoutPush() {
811 for (Account account : xmppConnectionService.getAccounts()) {
812 if (account.getStatus() == Account.State.ONLINE && !xmppConnectionService.getPushManagementService().available(account)) {
813 return true;
814 }
815 }
816 return false;
817 }
818
819 public void updateConversationList() {
820 xmppConnectionService.populateWithOrderedConversations(conversationList);
821 if (!conversationList.contains(mSelectedConversation)) {
822 mSelectedConversation = null;
823 }
824 if (swipedConversation != null) {
825 if (swipedConversation.isRead()) {
826 conversationList.remove(swipedConversation);
827 } else {
828 listView.discardUndo();
829 }
830 }
831 listAdapter.notifyDataSetChanged();
832 }
833
834 public void runIntent(PendingIntent pi, int requestCode) {
835 try {
836 this.startIntentSenderForResult(pi.getIntentSender(), requestCode,
837 null, 0, 0, 0);
838 } catch (final SendIntentException ignored) {
839 }
840 }
841
842 public boolean useSendButtonToIndicateStatus() {
843 return getPreferences().getBoolean("send_button_status", getResources().getBoolean(R.bool.send_button_status));
844 }
845
846 public boolean indicateReceived() {
847 return getPreferences().getBoolean("indicate_received", getResources().getBoolean(R.bool.indicate_received));
848 }
849
850 public boolean useGreenBackground() {
851 return getPreferences().getBoolean("use_green_background", getResources().getBoolean(R.bool.use_green_background));
852 }
853
854 @Override
855 protected void refreshUiReal() {
856 updateConversationList();
857 if (conversationList.size() > 0) {
858 if (!this.mConversationFragment.isAdded()) {
859 Log.d(Config.LOGTAG, "fragment NOT added to activity. detached=" + Boolean.toString(mConversationFragment.isDetached()));
860 }
861 if (getSelectedConversation() == null) {
862 reInitLatestConversation();
863 } else {
864 ConversationActivity.this.mConversationFragment.updateMessages();
865 updateActionBarTitle();
866 invalidateOptionsMenu();
867 }
868 } else {
869 if (!isStopping() && mRedirected.compareAndSet(false, true)) {
870 redirectToStartConversationActivity(false);
871 }
872 Log.d(Config.LOGTAG, "not updating conversations fragment because conversations list size was 0");
873 }
874 }
875
876 @Override
877 public void onAccountUpdate() {
878 this.refreshUi();
879 }
880
881 @Override
882 public void onConversationUpdate() {
883 this.refreshUi();
884 }
885
886 @Override
887 public void onRosterUpdate() {
888 this.refreshUi();
889 }
890
891 @Override
892 public void OnUpdateBlocklist(Status status) {
893 this.refreshUi();
894 }
895
896
897 public boolean enterIsSend() {
898 return getPreferences().getBoolean("enter_is_send", getResources().getBoolean(R.bool.enter_is_send));
899 }
900
901 @Override
902 public void onShowErrorToast(final int resId) {
903 runOnUiThread(() -> Toast.makeText(ConversationActivity.this, resId, Toast.LENGTH_SHORT).show());
904 }
905
906 public boolean highlightSelectedConversations() {
907 return !isConversationsOverviewHideable() || this.conversationWasSelectedByKeyboard;
908 }
909}