ConversationActivity.java

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