5e149cfcd1
* upstream/master: (27 commits) show 'using account …' in incoming call screen show contact jid in call screen bump copyright year Add handling of status code 333 increase default pw length do not build emoji flavors pulled translations from transifex add changelog fix ice candidate sending when different credentials are used remove security check that ensures rtp connection was properly finished code clean up bump agp store encrypted pgp files in private cache dir do not restart wakelock if activity is finishing delete pre lolipop weOwnFile() use try with resources. remove unused methods rename version suffix to playstore/free bump appcompat, migrate to emoji2 and get rid of emoji flavor fix rare npe store recordings and documents in their respective folders ...
722 lines
30 KiB
Java
722 lines
30 KiB
Java
/*
|
|
* Copyright (c) 2018, Daniel Gultsch All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation and/or
|
|
* other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
package eu.siacs.conversations.ui;
|
|
|
|
|
|
import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.app.Activity;
|
|
import android.app.Fragment;
|
|
import android.app.FragmentManager;
|
|
import android.app.FragmentTransaction;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.provider.Settings;
|
|
import android.util.Log;
|
|
import android.view.KeyEvent;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.annotation.IdRes;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.appcompat.app.ActionBar;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
import androidx.databinding.DataBindingUtil;
|
|
|
|
import org.openintents.openpgp.util.OpenPgpApi;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
import eu.siacs.conversations.Config;
|
|
import eu.siacs.conversations.R;
|
|
import eu.siacs.conversations.crypto.OmemoSetting;
|
|
import eu.siacs.conversations.databinding.ActivityConversationsBinding;
|
|
import eu.siacs.conversations.entities.Account;
|
|
import eu.siacs.conversations.entities.Contact;
|
|
import eu.siacs.conversations.entities.Conversation;
|
|
import eu.siacs.conversations.entities.Conversational;
|
|
import eu.siacs.conversations.services.XmppConnectionService;
|
|
import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
|
|
import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
|
|
import eu.siacs.conversations.ui.interfaces.OnConversationRead;
|
|
import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
|
|
import eu.siacs.conversations.ui.interfaces.OnConversationsListItemUpdated;
|
|
import eu.siacs.conversations.ui.util.ActionBarUtil;
|
|
import eu.siacs.conversations.ui.util.ActivityResult;
|
|
import eu.siacs.conversations.ui.util.ConversationMenuConfigurator;
|
|
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
|
|
import eu.siacs.conversations.ui.util.PendingItem;
|
|
import eu.siacs.conversations.utils.ExceptionHelper;
|
|
import eu.siacs.conversations.utils.SignupUtils;
|
|
import eu.siacs.conversations.utils.XmppUri;
|
|
import eu.siacs.conversations.xmpp.Jid;
|
|
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
|
|
|
public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged {
|
|
|
|
public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW";
|
|
public static final String EXTRA_CONVERSATION = "conversationUuid";
|
|
public static final String EXTRA_DOWNLOAD_UUID = "eu.siacs.conversations.download_uuid";
|
|
public static final String EXTRA_AS_QUOTE = "eu.siacs.conversations.as_quote";
|
|
public static final String EXTRA_NICK = "nick";
|
|
public static final String EXTRA_IS_PRIVATE_MESSAGE = "pm";
|
|
public static final String EXTRA_DO_NOT_APPEND = "do_not_append";
|
|
public static final String EXTRA_POST_INIT_ACTION = "post_init_action";
|
|
public static final String POST_ACTION_RECORD_VOICE = "record_voice";
|
|
public static final String EXTRA_TYPE = "type";
|
|
|
|
private static final List<String> VIEW_AND_SHARE_ACTIONS = Arrays.asList(
|
|
ACTION_VIEW_CONVERSATION,
|
|
Intent.ACTION_SEND,
|
|
Intent.ACTION_SEND_MULTIPLE
|
|
);
|
|
|
|
public static final int REQUEST_OPEN_MESSAGE = 0x9876;
|
|
public static final int REQUEST_PLAY_PAUSE = 0x5432;
|
|
|
|
|
|
//secondary fragment (when holding the conversation, must be initialized before refreshing the overview fragment
|
|
private static final @IdRes
|
|
int[] FRAGMENT_ID_NOTIFICATION_ORDER = {R.id.secondary_fragment, R.id.main_fragment};
|
|
private final PendingItem<Intent> pendingViewIntent = new PendingItem<>();
|
|
private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>();
|
|
private ActivityConversationsBinding binding;
|
|
private boolean mActivityPaused = true;
|
|
private final AtomicBoolean mRedirectInProcess = new AtomicBoolean(false);
|
|
|
|
private static boolean isViewOrShareIntent(Intent i) {
|
|
Log.d(Config.LOGTAG, "action: " + (i == null ? null : i.getAction()));
|
|
return i != null && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION);
|
|
}
|
|
|
|
private static Intent createLauncherIntent(Context context) {
|
|
final Intent intent = new Intent(context, ConversationsActivity.class);
|
|
intent.setAction(Intent.ACTION_MAIN);
|
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
|
return intent;
|
|
}
|
|
|
|
@Override
|
|
protected void refreshUiReal() {
|
|
invalidateOptionsMenu();
|
|
for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
|
|
refreshFragment(id);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
void onBackendConnected() {
|
|
if (performRedirectIfNecessary(true)) {
|
|
return;
|
|
}
|
|
xmppConnectionService.getNotificationService().setIsInForeground(true);
|
|
Intent intent = pendingViewIntent.pop();
|
|
if (intent != null) {
|
|
if (processViewIntent(intent)) {
|
|
if (binding.secondaryFragment != null) {
|
|
notifyFragmentOfBackendConnected(R.id.main_fragment);
|
|
}
|
|
invalidateActionBarTitle();
|
|
return;
|
|
}
|
|
}
|
|
for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
|
|
notifyFragmentOfBackendConnected(id);
|
|
}
|
|
|
|
ActivityResult activityResult = postponedActivityResult.pop();
|
|
if (activityResult != null) {
|
|
handleActivityResult(activityResult);
|
|
}
|
|
|
|
invalidateActionBarTitle();
|
|
if (binding.secondaryFragment != null && ConversationFragment.getConversation(this) == null) {
|
|
Conversation conversation = ConversationsOverviewFragment.getSuggestion(this);
|
|
if (conversation != null) {
|
|
openConversation(conversation, null);
|
|
}
|
|
}
|
|
showDialogsIfMainIsOverview();
|
|
}
|
|
|
|
private boolean performRedirectIfNecessary(boolean noAnimation) {
|
|
return performRedirectIfNecessary(null, noAnimation);
|
|
}
|
|
|
|
private boolean performRedirectIfNecessary(final Conversation ignore, final boolean noAnimation) {
|
|
if (xmppConnectionService == null) {
|
|
return false;
|
|
}
|
|
boolean isConversationsListEmpty = xmppConnectionService.isConversationsListEmpty(ignore);
|
|
if (isConversationsListEmpty && mRedirectInProcess.compareAndSet(false, true)) {
|
|
final Intent intent = SignupUtils.getRedirectionIntent(this);
|
|
if (noAnimation) {
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
|
}
|
|
runOnUiThread(() -> {
|
|
startActivity(intent);
|
|
if (noAnimation) {
|
|
overridePendingTransition(0, 0);
|
|
}
|
|
});
|
|
}
|
|
return mRedirectInProcess.get();
|
|
}
|
|
|
|
private void showDialogsIfMainIsOverview() {
|
|
if (xmppConnectionService == null) {
|
|
return;
|
|
}
|
|
final Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
|
|
if (fragment instanceof ConversationsOverviewFragment) {
|
|
if (ExceptionHelper.checkForCrash(this)) {
|
|
return;
|
|
}
|
|
openBatteryOptimizationDialogIfNeeded();
|
|
}
|
|
}
|
|
|
|
private String getBatteryOptimizationPreferenceKey() {
|
|
@SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
|
|
return "show_battery_optimization" + (device == null ? "" : device);
|
|
}
|
|
|
|
private void setNeverAskForBatteryOptimizationsAgain() {
|
|
getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
|
|
}
|
|
|
|
private void openBatteryOptimizationDialogIfNeeded() {
|
|
if (isOptimizingBattery()
|
|
&& android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M
|
|
&& getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setTitle(R.string.battery_optimizations_enabled);
|
|
builder.setMessage(getString(R.string.battery_optimizations_enabled_dialog, getString(R.string.app_name)));
|
|
builder.setPositiveButton(R.string.next, (dialog, which) -> {
|
|
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
|
Uri uri = Uri.parse("package:" + getPackageName());
|
|
intent.setData(uri);
|
|
try {
|
|
startActivityForResult(intent, REQUEST_BATTERY_OP);
|
|
} catch (ActivityNotFoundException e) {
|
|
Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
|
|
}
|
|
});
|
|
builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
|
|
final AlertDialog dialog = builder.create();
|
|
dialog.setCanceledOnTouchOutside(false);
|
|
dialog.show();
|
|
}
|
|
}
|
|
|
|
private void notifyFragmentOfBackendConnected(@IdRes int id) {
|
|
final Fragment fragment = getFragmentManager().findFragmentById(id);
|
|
if (fragment instanceof OnBackendConnected) {
|
|
((OnBackendConnected) fragment).onBackendConnected();
|
|
}
|
|
}
|
|
|
|
private void refreshFragment(@IdRes int id) {
|
|
final Fragment fragment = getFragmentManager().findFragmentById(id);
|
|
if (fragment instanceof XmppFragment) {
|
|
((XmppFragment) fragment).refresh();
|
|
}
|
|
}
|
|
|
|
private boolean processViewIntent(Intent intent) {
|
|
final String uuid = intent.getStringExtra(EXTRA_CONVERSATION);
|
|
final Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null;
|
|
if (conversation == null) {
|
|
Log.d(Config.LOGTAG, "unable to view conversation with uuid:" + uuid);
|
|
return false;
|
|
}
|
|
openConversation(conversation, intent.getExtras());
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
|
|
if (grantResults.length > 0) {
|
|
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
switch (requestCode) {
|
|
case REQUEST_OPEN_MESSAGE:
|
|
refreshUiReal();
|
|
ConversationFragment.openPendingMessage(this);
|
|
break;
|
|
case REQUEST_PLAY_PAUSE:
|
|
ConversationFragment.startStopPending(this);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onActivityResult(int requestCode, int resultCode, final Intent data) {
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
ActivityResult activityResult = ActivityResult.of(requestCode, resultCode, data);
|
|
if (xmppConnectionService != null) {
|
|
handleActivityResult(activityResult);
|
|
} else {
|
|
this.postponedActivityResult.push(activityResult);
|
|
}
|
|
}
|
|
|
|
private void handleActivityResult(ActivityResult activityResult) {
|
|
if (activityResult.resultCode == Activity.RESULT_OK) {
|
|
handlePositiveActivityResult(activityResult.requestCode, activityResult.data);
|
|
} else {
|
|
handleNegativeActivityResult(activityResult.requestCode);
|
|
}
|
|
}
|
|
|
|
private void handleNegativeActivityResult(int requestCode) {
|
|
Conversation conversation = ConversationFragment.getConversationReliable(this);
|
|
switch (requestCode) {
|
|
case REQUEST_DECRYPT_PGP:
|
|
if (conversation == null) {
|
|
break;
|
|
}
|
|
conversation.getAccount().getPgpDecryptionService().giveUpCurrentDecryption();
|
|
break;
|
|
case REQUEST_BATTERY_OP:
|
|
setNeverAskForBatteryOptimizationsAgain();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void handlePositiveActivityResult(int requestCode, final Intent data) {
|
|
Conversation conversation = ConversationFragment.getConversationReliable(this);
|
|
if (conversation == null) {
|
|
Log.d(Config.LOGTAG, "conversation not found");
|
|
return;
|
|
}
|
|
switch (requestCode) {
|
|
case REQUEST_DECRYPT_PGP:
|
|
conversation.getAccount().getPgpDecryptionService().continueDecryption(data);
|
|
break;
|
|
case REQUEST_CHOOSE_PGP_ID:
|
|
long id = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, 0);
|
|
if (id != 0) {
|
|
conversation.getAccount().setPgpSignId(id);
|
|
announcePgp(conversation.getAccount(), null, null, onOpenPGPKeyPublished);
|
|
} else {
|
|
choosePgpSignId(conversation.getAccount());
|
|
}
|
|
break;
|
|
case REQUEST_ANNOUNCE_PGP:
|
|
announcePgp(conversation.getAccount(), conversation, data, onOpenPGPKeyPublished);
|
|
break;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onCreate(final Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
ConversationMenuConfigurator.reloadFeatures(this);
|
|
OmemoSetting.load(this);
|
|
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations);
|
|
setSupportActionBar(binding.toolbar);
|
|
configureActionBar(getSupportActionBar());
|
|
this.getFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle);
|
|
this.getFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview);
|
|
this.initializeFragments();
|
|
this.invalidateActionBarTitle();
|
|
final Intent intent;
|
|
if (savedInstanceState == null) {
|
|
intent = getIntent();
|
|
} else {
|
|
intent = savedInstanceState.getParcelable("intent");
|
|
}
|
|
if (isViewOrShareIntent(intent)) {
|
|
pendingViewIntent.push(intent);
|
|
setIntent(createLauncherIntent(this));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
getMenuInflater().inflate(R.menu.activity_conversations, menu);
|
|
final MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
|
|
if (qrCodeScanMenuItem != null) {
|
|
if (isCameraFeatureAvailable()) {
|
|
Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
|
|
boolean visible = getResources().getBoolean(R.bool.show_qr_code_scan)
|
|
&& fragment instanceof ConversationsOverviewFragment;
|
|
qrCodeScanMenuItem.setVisible(visible);
|
|
} else {
|
|
qrCodeScanMenuItem.setVisible(false);
|
|
}
|
|
}
|
|
return super.onCreateOptionsMenu(menu);
|
|
}
|
|
|
|
@Override
|
|
public void onConversationSelected(Conversation conversation) {
|
|
clearPendingViewIntent();
|
|
if (ConversationFragment.getConversation(this) == conversation) {
|
|
Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open");
|
|
return;
|
|
}
|
|
openConversation(conversation, null);
|
|
}
|
|
|
|
public void clearPendingViewIntent() {
|
|
if (pendingViewIntent.clear()) {
|
|
Log.e(Config.LOGTAG, "cleared pending view intent");
|
|
}
|
|
}
|
|
|
|
private void displayToast(final String msg) {
|
|
runOnUiThread(() -> Toast.makeText(ConversationsActivity.this, msg, Toast.LENGTH_SHORT).show());
|
|
}
|
|
|
|
@Override
|
|
public void onAffiliationChangedSuccessful(Jid jid) {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onAffiliationChangeFailed(Jid jid, int resId) {
|
|
displayToast(getString(resId, jid.asBareJid().toString()));
|
|
}
|
|
|
|
private void openConversation(Conversation conversation, Bundle extras) {
|
|
final FragmentManager fragmentManager = getFragmentManager();
|
|
executePendingTransactions(fragmentManager);
|
|
ConversationFragment conversationFragment = (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment);
|
|
final boolean mainNeedsRefresh;
|
|
if (conversationFragment == null) {
|
|
mainNeedsRefresh = false;
|
|
final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
|
|
if (mainFragment instanceof ConversationFragment) {
|
|
conversationFragment = (ConversationFragment) mainFragment;
|
|
} else {
|
|
conversationFragment = new ConversationFragment();
|
|
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
|
fragmentTransaction.replace(R.id.main_fragment, conversationFragment);
|
|
fragmentTransaction.addToBackStack(null);
|
|
try {
|
|
fragmentTransaction.commit();
|
|
} catch (IllegalStateException e) {
|
|
Log.w(Config.LOGTAG, "sate loss while opening conversation", e);
|
|
//allowing state loss is probably fine since view intents et all are already stored and a click can probably be 'ignored'
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
mainNeedsRefresh = true;
|
|
}
|
|
conversationFragment.reInit(conversation, extras == null ? new Bundle() : extras);
|
|
if (mainNeedsRefresh) {
|
|
refreshFragment(R.id.main_fragment);
|
|
} else {
|
|
invalidateActionBarTitle();
|
|
}
|
|
}
|
|
|
|
private static void executePendingTransactions(final FragmentManager fragmentManager) {
|
|
try {
|
|
fragmentManager.executePendingTransactions();
|
|
} catch (final Exception e) {
|
|
Log.e(Config.LOGTAG,"unable to execute pending fragment transactions");
|
|
}
|
|
}
|
|
|
|
public boolean onXmppUriClicked(Uri uri) {
|
|
XmppUri xmppUri = new XmppUri(uri);
|
|
if (xmppUri.isValidJid() && !xmppUri.hasFingerprints()) {
|
|
final Conversation conversation = xmppConnectionService.findUniqueConversationByJid(xmppUri);
|
|
if (conversation != null) {
|
|
openConversation(conversation, null);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
if (MenuDoubleTabUtil.shouldIgnoreTap()) {
|
|
return false;
|
|
}
|
|
switch (item.getItemId()) {
|
|
case android.R.id.home:
|
|
FragmentManager fm = getFragmentManager();
|
|
if (fm.getBackStackEntryCount() > 0) {
|
|
try {
|
|
fm.popBackStack();
|
|
} catch (IllegalStateException e) {
|
|
Log.w(Config.LOGTAG, "Unable to pop back stack after pressing home button");
|
|
}
|
|
return true;
|
|
}
|
|
break;
|
|
case R.id.action_scan_qr_code:
|
|
UriHandlerActivity.scan(this);
|
|
return true;
|
|
case R.id.action_search_all_conversations:
|
|
startActivity(new Intent(this, SearchActivity.class));
|
|
return true;
|
|
case R.id.action_search_this_conversation:
|
|
final Conversation conversation = ConversationFragment.getConversation(this);
|
|
if (conversation == null) {
|
|
return true;
|
|
}
|
|
final Intent intent = new Intent(this, SearchActivity.class);
|
|
intent.putExtra(SearchActivity.EXTRA_CONVERSATION_UUID, conversation.getUuid());
|
|
startActivity(intent);
|
|
return true;
|
|
}
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
|
|
if (keyCode == KeyEvent.KEYCODE_DPAD_UP && keyEvent.isCtrlPressed()) {
|
|
final ConversationFragment conversationFragment = ConversationFragment.get(this);
|
|
if (conversationFragment != null && conversationFragment.onArrowUpCtrlPressed()) {
|
|
return true;
|
|
}
|
|
}
|
|
return super.onKeyDown(keyCode, keyEvent);
|
|
}
|
|
|
|
@Override
|
|
public void onSaveInstanceState(final Bundle savedInstanceState) {
|
|
final Intent pendingIntent = pendingViewIntent.peek();
|
|
savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent());
|
|
super.onSaveInstanceState(savedInstanceState);
|
|
}
|
|
|
|
@Override
|
|
protected void onStart() {
|
|
super.onStart();
|
|
final int theme = findTheme();
|
|
if (this.mTheme != theme) {
|
|
this.mSkipBackgroundBinding = true;
|
|
recreate();
|
|
} else {
|
|
this.mSkipBackgroundBinding = false;
|
|
}
|
|
mRedirectInProcess.set(false);
|
|
}
|
|
|
|
@Override
|
|
protected void onNewIntent(final Intent intent) {
|
|
super.onNewIntent(intent);
|
|
if (isViewOrShareIntent(intent)) {
|
|
if (xmppConnectionService != null) {
|
|
clearPendingViewIntent();
|
|
processViewIntent(intent);
|
|
} else {
|
|
pendingViewIntent.push(intent);
|
|
}
|
|
}
|
|
setIntent(createLauncherIntent(this));
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
this.mActivityPaused = true;
|
|
super.onPause();
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
this.mActivityPaused = false;
|
|
}
|
|
|
|
private void initializeFragments() {
|
|
final FragmentManager fragmentManager = getFragmentManager();
|
|
FragmentTransaction transaction = fragmentManager.beginTransaction();
|
|
final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
|
|
final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
|
|
if (mainFragment != null) {
|
|
if (binding.secondaryFragment != null) {
|
|
if (mainFragment instanceof ConversationFragment) {
|
|
getFragmentManager().popBackStack();
|
|
transaction.remove(mainFragment);
|
|
transaction.commit();
|
|
fragmentManager.executePendingTransactions();
|
|
transaction = fragmentManager.beginTransaction();
|
|
transaction.replace(R.id.secondary_fragment, mainFragment);
|
|
transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
|
|
transaction.commit();
|
|
return;
|
|
}
|
|
} else {
|
|
if (secondaryFragment instanceof ConversationFragment) {
|
|
transaction.remove(secondaryFragment);
|
|
transaction.commit();
|
|
getFragmentManager().executePendingTransactions();
|
|
transaction = fragmentManager.beginTransaction();
|
|
transaction.replace(R.id.main_fragment, secondaryFragment);
|
|
transaction.addToBackStack(null);
|
|
transaction.commit();
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
|
|
}
|
|
if (binding.secondaryFragment != null && secondaryFragment == null) {
|
|
transaction.replace(R.id.secondary_fragment, new ConversationFragment());
|
|
}
|
|
transaction.commit();
|
|
}
|
|
|
|
private void invalidateActionBarTitle() {
|
|
final ActionBar actionBar = getSupportActionBar();
|
|
if (actionBar == null) {
|
|
return;
|
|
}
|
|
final FragmentManager fragmentManager = getFragmentManager();
|
|
final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
|
|
if (mainFragment instanceof ConversationFragment) {
|
|
final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
|
|
if (conversation != null) {
|
|
actionBar.setTitle(conversation.getName());
|
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
|
ActionBarUtil.setActionBarOnClickListener(
|
|
binding.toolbar,
|
|
(v) -> openConversationDetails(conversation)
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
actionBar.setTitle(R.string.app_name);
|
|
actionBar.setDisplayHomeAsUpEnabled(false);
|
|
ActionBarUtil.resetActionBarOnClickListeners(binding.toolbar);
|
|
}
|
|
|
|
private void openConversationDetails(final Conversation conversation) {
|
|
if (conversation.getMode() == Conversational.MODE_MULTI) {
|
|
ConferenceDetailsActivity.open(this, conversation);
|
|
} else {
|
|
final Contact contact = conversation.getContact();
|
|
if (contact.isSelf()) {
|
|
switchToAccount(conversation.getAccount());
|
|
} else {
|
|
switchToContactDetails(contact);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onConversationArchived(Conversation conversation) {
|
|
if (performRedirectIfNecessary(conversation, false)) {
|
|
return;
|
|
}
|
|
final FragmentManager fragmentManager = getFragmentManager();
|
|
final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
|
|
if (mainFragment instanceof ConversationFragment) {
|
|
try {
|
|
fragmentManager.popBackStack();
|
|
} catch (final IllegalStateException e) {
|
|
Log.w(Config.LOGTAG, "state loss while popping back state after archiving conversation", e);
|
|
//this usually means activity is no longer active; meaning on the next open we will run through this again
|
|
}
|
|
return;
|
|
}
|
|
final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
|
|
if (secondaryFragment instanceof ConversationFragment) {
|
|
if (((ConversationFragment) secondaryFragment).getConversation() == conversation) {
|
|
Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation);
|
|
if (suggestion != null) {
|
|
openConversation(suggestion, null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onConversationsListItemUpdated() {
|
|
Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
|
|
if (fragment instanceof ConversationsOverviewFragment) {
|
|
((ConversationsOverviewFragment) fragment).refresh();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void switchToConversation(Conversation conversation) {
|
|
Log.d(Config.LOGTAG, "override");
|
|
openConversation(conversation, null);
|
|
}
|
|
|
|
@Override
|
|
public void onConversationRead(Conversation conversation, String upToUuid) {
|
|
if (!mActivityPaused && pendingViewIntent.peek() == null) {
|
|
xmppConnectionService.sendReadMarker(conversation, upToUuid);
|
|
} else {
|
|
Log.d(Config.LOGTAG, "ignoring read callback. mActivityPaused=" + mActivityPaused);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onAccountUpdate() {
|
|
this.refreshUi();
|
|
}
|
|
|
|
@Override
|
|
public void onConversationUpdate() {
|
|
if (performRedirectIfNecessary(false)) {
|
|
return;
|
|
}
|
|
this.refreshUi();
|
|
}
|
|
|
|
@Override
|
|
public void onRosterUpdate() {
|
|
this.refreshUi();
|
|
}
|
|
|
|
@Override
|
|
public void OnUpdateBlocklist(OnUpdateBlocklist.Status status) {
|
|
this.refreshUi();
|
|
}
|
|
|
|
@Override
|
|
public void onShowErrorToast(int resId) {
|
|
runOnUiThread(() -> Toast.makeText(this, resId, Toast.LENGTH_SHORT).show());
|
|
}
|
|
}
|