ConversationActivity.java

  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}