Merge branch 'main' into r10s/remove-email-from-account-switcher

This commit is contained in:
adb
2026-01-02 13:45:27 +01:00
committed by GitHub
19 changed files with 387 additions and 297 deletions
@@ -62,9 +62,9 @@ public class ApplicationContext extends MultiDexApplication {
private Rpc rpc;
private DcContext dcContext;
public DcLocationManager dcLocationManager;
public DcEventCenter eventCenter;
public NotificationCenter notificationCenter;
private DcLocationManager dcLocationManager;
private DcEventCenter eventCenter;
private NotificationCenter notificationCenter;
private JobManager jobManager;
private int debugOnAvailableCount;
@@ -127,6 +127,33 @@ public class ApplicationContext extends MultiDexApplication {
}
}
/**
* Get DcLocationManager instance, waiting for initialization if necessary.
* This method is thread-safe and will block until initialization is complete.
*/
public DcLocationManager getLocationManager() {
ensureInitialized();
return dcLocationManager;
}
/**
* Get DcEventCenter instance, waiting for initialization if necessary.
* This method is thread-safe and will block until initialization is complete.
*/
public DcEventCenter getEventCenter() {
ensureInitialized();
return eventCenter;
}
/**
* Get NotificationCenter instance, waiting for initialization if necessary.
* This method is thread-safe and will block until initialization is complete.
*/
public NotificationCenter getNotificationCenter() {
ensureInitialized();
return notificationCenter;
}
@Override
public void onCreate() {
super.onCreate();
@@ -187,14 +214,13 @@ public class ApplicationContext extends MultiDexApplication {
dcContext = dcAccounts.getSelectedAccount();
notificationCenter = new NotificationCenter(this);
eventCenter = new DcEventCenter(this);
dcLocationManager = new DcLocationManager(this, dcContext);
// Mark as initialized before starting threads that depend on it
isInitialized = true;
initLock.notifyAll();
Log.i(TAG, "DcAccounts initialization complete");
dcLocationManager = new DcLocationManager(this); // depends on dcContext
new Thread(() -> {
Log.i(TAG, "Starting event loop");
DcEventEmitter emitter = dcAccounts.getEventEmitter();
@@ -74,6 +74,7 @@ import org.thoughtcrime.securesms.search.SearchFragment;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.ShareUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SendRelayedMessageUtil;
@@ -95,6 +96,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
public static final String CLEAR_NOTIFICATIONS = "clear_notifications";
public static final String ACCOUNT_ID_EXTRA = "account_id";
public static final String FROM_WELCOME = "from_welcome";
private static final int REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE = ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS+1;
private ConversationListFragment conversationListFragment;
public TextView title;
@@ -106,6 +108,11 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
private ViewGroup fragmentContainer;
private ViewGroup selfAvatarContainer;
/** used to store temporarily scanned QR to pass it back to QrCodeHandler when ScreenLockUtil is used */
private String qrData = null;
/** used to store temporarily profile ID to delete after authorization is granted via ScreenLockUtil */
private int deleteProfileId = 0;
@Override
protected void onPreCreate() {
dynamicTheme = new DynamicNoActionBarTheme();
@@ -491,7 +498,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
if (uri.getScheme().equalsIgnoreCase(OPENPGP4FPR) || Util.isInviteURL(uri)) {
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.handleQrData(uri.toString(), SecurejoinSource.ExternalLink, null);
qrCodeHandler.handleOnlySecureJoinQr(uri.toString(), SecurejoinSource.ExternalLink, null);
}
}
}
@@ -566,14 +573,55 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
startActivity(Intent.createChooser(intent, getString(R.string.chat_share_with_title)));
}
public void onDeleteProfile(int profileId) {
deleteProfileId = profileId;
boolean result = ScreenLockUtil.applyScreenLock(this, getString(R.string.delete_account), getString(R.string.enter_system_secret_to_continue), REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE);
if (!result) {
deleteProfile(profileId);
}
}
private void deleteProfile(int profileId) {
DcAccounts accounts = DcHelper.getAccounts(this);
boolean selected = profileId == accounts.getSelectedAccount().getAccountId();
DcHelper.getNotificationCenter(this).removeAllNotifications(profileId);
accounts.removeAccount(profileId);
if (selected) {
DcContext selAcc = accounts.getSelectedAccount();
AccountManager.getInstance().switchAccountAndStartActivity(this, selAcc.isOk()? selAcc.getAccountId() : 0);
} else {
AccountManager.getInstance().showSwitchAccountMenu(this);
}
// title update needed to show "Delta Chat" in case there is only one profile left
refreshTitle();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) return;
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
switch (requestCode) {
case IntentIntegrator.REQUEST_CODE:
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.onScanPerformed(scanResult, SecurejoinUiPath.QrIcon);
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
qrData = scanResult.getContents();
qrCodeHandler.handleQrData(qrData, SecurejoinSource.Scan, SecurejoinUiPath.QrIcon);
break;
case ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS:
// QrCodeHandler requested user authorization before adding a relay
// and it was granted, so proceed to add the relay
if (qrData != null) {
qrCodeHandler.addRelay(qrData);
qrData = null;
}
break;
case REQUEST_CODE_CONFIRM_CREDENTIALS_DELETE_PROFILE:
if (deleteProfileId != 0) {
deleteProfile(deleteProfileId);
deleteProfileId = 0;
}
break;
default:
break;
@@ -208,10 +208,8 @@ public class InstantOnboardingActivity extends BaseActionBarActivity implements
case IntentIntegrator.REQUEST_CODE:
String qrRaw = data.getStringExtra(RegistrationQrActivity.QRDATA_EXTRA);
if (qrRaw == null) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (scanResult != null && scanResult.getFormatName() != null) {
qrRaw = scanResult.getContents();
}
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
qrRaw = scanResult.getContents();
}
if (qrRaw != null) {
setProviderFromQr(qrRaw);
@@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.qr.QrActivity;
import org.thoughtcrime.securesms.qr.QrCodeHandler;
import org.thoughtcrime.securesms.util.MailtoUtil;
import chat.delta.rpc.types.SecurejoinSource;
import chat.delta.rpc.types.SecurejoinUiPath;
/**
@@ -135,11 +136,13 @@ public class NewConversationActivity extends ContactSelectionActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) return;
switch (requestCode) {
case IntentIntegrator.REQUEST_CODE:
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.onScanPerformed(scanResult, SecurejoinUiPath.NewContact);
qrCodeHandler.handleOnlySecureJoinQr(scanResult.getContents(), SecurejoinSource.Scan, SecurejoinUiPath.NewContact);
break;
default:
break;
@@ -304,7 +304,7 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
// invite-links should be handled directly
String schema = url.split(":")[0].toLowerCase();
if (schema.equals("openpgp4fpr") || url.startsWith("https://" + Util.INVITE_DOMAIN + "/")) {
new QrCodeHandler(this).handleQrData(url, SecurejoinSource.InternalLink, null);
new QrCodeHandler(this).handleOnlySecureJoinQr(url, SecurejoinSource.InternalLink, null);
return true; // abort internal loading
}
@@ -22,7 +22,6 @@ import androidx.appcompat.app.AlertDialog;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcLot;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
@@ -32,6 +31,7 @@ import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.mms.AttachmentManager;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.qr.BackupTransferActivity;
import org.thoughtcrime.securesms.qr.QrCodeHandler;
import org.thoughtcrime.securesms.qr.RegistrationQrActivity;
import org.thoughtcrime.securesms.service.GenericForegroundService;
import org.thoughtcrime.securesms.service.NotificationController;
@@ -336,39 +336,14 @@ public class WelcomeActivity extends BaseActionBarActivity implements DcEventCen
if (requestCode==IntentIntegrator.REQUEST_CODE) {
String qrRaw = data.getStringExtra(RegistrationQrActivity.QRDATA_EXTRA);
if (qrRaw == null) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (scanResult == null || scanResult.getFormatName() == null) {
return; // aborted
}
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
qrRaw = scanResult.getContents();
}
DcLot qrParsed = dcContext.checkQr(qrRaw);
switch (qrParsed.getState()) {
case DcContext.DC_QR_BACKUP2:
final String finalQrRaw = qrRaw;
new AlertDialog.Builder(this)
.setTitle(R.string.multidevice_receiver_title)
.setMessage(R.string.multidevice_receiver_scanning_ask)
.setPositiveButton(R.string.perm_continue, (dialog, which) -> startBackupTransfer(finalQrRaw))
.setNegativeButton(R.string.cancel, null)
.setCancelable(false)
.show();
break;
case DcContext.DC_QR_BACKUP_TOO_NEW:
new AlertDialog.Builder(this)
.setTitle(R.string.multidevice_receiver_title)
.setMessage(R.string.multidevice_receiver_needs_update)
.setPositiveButton(R.string.ok, null)
.show();
break;
default:
new AlertDialog.Builder(this)
.setMessage(R.string.qraccount_qr_code_cannot_be_used)
.setPositiveButton(R.string.ok, null)
.show();
break;
if (!new QrCodeHandler(this).handleBackupQr(qrRaw)) {
new AlertDialog.Builder(this)
.setMessage(R.string.qraccount_qr_code_cannot_be_used)
.setPositiveButton(R.string.ok, null)
.show();
}
} else if (requestCode == PICK_BACKUP) {
Uri uri = (data != null ? data.getData() : null);
@@ -47,9 +47,15 @@ import chat.delta.rpc.RpcException;
public class AccountSelectionListFragment extends DialogFragment implements DcEventCenter.DcEventDelegate
{
private static final String TAG = AccountSelectionListFragment.class.getSimpleName();
private final ConversationListActivity activity;
private RecyclerView recyclerView;
private AccountSelectionListAdapter adapter;
public AccountSelectionListFragment(ConversationListActivity activity) {
super();
this.activity = activity;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
@@ -132,7 +138,7 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
private void onContextItemSelected(MenuItem item, int accountId) {
int itemId = item.getItemId();
if (itemId == R.id.delete) {
onDeleteAccount(accountId);
onDeleteProfile(accountId);
} else if (itemId == R.id.menu_mute_notifications) {
onToggleMute(accountId);
} else if (itemId == R.id.menu_set_tag) {
@@ -167,8 +173,6 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
}
private void onSetTag(int accountId) {
Activity activity = getActivity();
if (activity == null) return;
AccountSelectionListFragment.this.dismiss();
DcContext dcContext = DcHelper.getAccounts(activity).getAccount(accountId);
@@ -190,10 +194,8 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
.show();
}
private void onDeleteAccount(int accountId) {
Activity activity = getActivity();
private void onDeleteProfile(int accountId) {
AccountSelectionListFragment.this.dismiss();
if (activity == null) return;
DcAccounts accounts = DcHelper.getAccounts(activity);
Rpc rpc = DcHelper.getRpc(activity);
@@ -229,22 +231,7 @@ public class AccountSelectionListFragment extends DialogFragment implements DcEv
.setTitle(R.string.delete_account)
.setView(dialogView)
.setNegativeButton(R.string.cancel, (d, which) -> AccountManager.getInstance().showSwitchAccountMenu(activity))
.setPositiveButton(R.string.delete, (d2, which2) -> {
boolean selected = accountId == accounts.getSelectedAccount().getAccountId();
DcHelper.getNotificationCenter(activity).removeAllNotifications(accountId);
accounts.removeAccount(accountId);
if (selected) {
DcContext selAcc = accounts.getSelectedAccount();
AccountManager.getInstance().switchAccountAndStartActivity(activity, selAcc.isOk()? selAcc.getAccountId() : 0);
} else {
AccountManager.getInstance().showSwitchAccountMenu(activity);
}
// title update needed to show "Delta Chat" in case there is only one profile left
if (activity instanceof ConversationListActivity) {
((ConversationListActivity)activity).refreshTitle();
}
})
.setPositiveButton(R.string.delete, (d2, w2) -> activity.onDeleteProfile(accountId))
.show();
Util.redPositiveButton(dialog);
}
@@ -147,8 +147,8 @@ public class AccountManager {
// ui
public void showSwitchAccountMenu(Activity activity) {
AccountSelectionListFragment dialog = new AccountSelectionListFragment();
public void showSwitchAccountMenu(ConversationListActivity activity) {
AccountSelectionListFragment dialog = new AccountSelectionListFragment(activity);
dialog.show(((FragmentActivity) activity).getSupportFragmentManager(), null);
}
@@ -77,11 +77,11 @@ public class DcHelper {
}
public static DcEventCenter getEventCenter(@NonNull Context context) {
return ApplicationContext.getInstance(context).eventCenter;
return ApplicationContext.getInstance(context).getEventCenter();
}
public static NotificationCenter getNotificationCenter(@NonNull Context context) {
return ApplicationContext.getInstance(context).notificationCenter;
return ApplicationContext.getInstance(context).getNotificationCenter();
}
public static boolean isConfigured(Context context) {
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.qr.QrCodeHandler;
import org.thoughtcrime.securesms.util.ViewUtil;
import chat.delta.rpc.types.SecurejoinSource;
import chat.delta.rpc.types.SecurejoinUiPath;
public class NewContactActivity extends PassphraseRequiredActionBarActivity
@@ -104,10 +105,10 @@ public class NewContactActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == IntentIntegrator.REQUEST_CODE) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == IntentIntegrator.REQUEST_CODE) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.onScanPerformed(scanResult, SecurejoinUiPath.NewContact);
qrCodeHandler.handleOnlySecureJoinQr(scanResult.getContents(), SecurejoinSource.Scan, SecurejoinUiPath.NewContact);
}
}
}
@@ -16,6 +16,8 @@ import java.util.Observer;
import static android.content.Context.BIND_AUTO_CREATE;
import com.b44t.messenger.DcContext;
public class DcLocationManager implements Observer {
private static final String TAG = DcLocationManager.class.getSimpleName();
@@ -40,10 +42,10 @@ public class DcLocationManager implements Observer {
}
};
public DcLocationManager(Context context) {
public DcLocationManager(Context context, DcContext dcContext) {
this.context = context.getApplicationContext();
DcLocation.getInstance().addObserver(this);
if (DcHelper.getContext(context).isSendingLocationsToChat(0)) {
if (dcContext.isSendingLocationsToChat(0)) {
startLocationEngine();
}
}
@@ -473,7 +473,7 @@ public class AttachmentManager {
public static void selectLocation(Activity activity, int chatId) {
ApplicationContext applicationContext = ApplicationContext.getInstance(activity);
DcLocationManager dcLocationManager = applicationContext.dcLocationManager;
DcLocationManager dcLocationManager = applicationContext.getLocationManager();
if (DcHelper.getContext(applicationContext).isSendingLocationsToChat(chatId)) {
dcLocationManager.stopSharingLocation(chatId);
@@ -16,7 +16,6 @@ import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -33,8 +32,8 @@ import org.thoughtcrime.securesms.relay.RelayListActivity;
import org.thoughtcrime.securesms.StatsSending;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.proxy.ProxySettingsActivity;
import org.thoughtcrime.securesms.util.IntentUtils;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.StreamUtil;
import org.thoughtcrime.securesms.util.Util;
@@ -212,7 +211,10 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment
Preference relayListBtn = this.findPreference("pref_relay_list_button");
if (relayListBtn != null) {
relayListBtn.setOnPreferenceClickListener(((preference) -> {
openRelayListActivity();
boolean result = ScreenLockUtil.applyScreenLock(requireActivity(), getString(R.string.transports), getString(R.string.enter_system_secret_to_continue), REQUEST_CODE_CONFIRM_CREDENTIALS_ACCOUNT);
if (!result) {
openRelayListActivity();
}
return true;
}));
}
@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.qr;
import android.Manifest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@@ -27,11 +26,10 @@ import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.client.android.Intents;
import com.google.zxing.common.HybridBinarizer;
import org.thoughtcrime.securesms.BaseActionBarActivity;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.NewConversationActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.contacts.NewContactActivity;
@@ -44,9 +42,6 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import java.io.FileNotFoundException;
import java.io.InputStream;
import chat.delta.rpc.types.SecurejoinSource;
import chat.delta.rpc.types.SecurejoinUiPath;
public class QrActivity extends BaseActionBarActivity implements View.OnClickListener {
private final static String TAG = QrActivity.class.getSimpleName();
@@ -154,8 +149,7 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis
} else if (itemId == R.id.load_from_image) {
AttachmentManager.selectImage(this, REQUEST_CODE_IMAGE);
} else if (itemId == R.id.paste) {
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.handleQrData(Util.getTextFromClipboard(this), SecurejoinSource.Clipboard, getUiPath());
setQrResult(Util.getTextFromClipboard(this));
}
return false;
@@ -203,10 +197,10 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis
RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels);
BinaryBitmap bBitmap = new BinaryBitmap(new HybridBinarizer(source));
MultiFormatReader reader = new MultiFormatReader();
try {
Result result = reader.decode(bBitmap);
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.handleQrData(result.getText(), SecurejoinSource.ImageLoaded, getUiPath());
setQrResult(result.getText());
} catch (NotFoundException e) {
Log.e(TAG, "decode exception", e);
Toast.makeText(this, getString(R.string.qrscan_failed), Toast.LENGTH_LONG).show();
@@ -219,19 +213,11 @@ public class QrActivity extends BaseActionBarActivity implements View.OnClickLis
}
}
private SecurejoinUiPath getUiPath() {
SecurejoinUiPath uiPath = null;
ComponentName caller = this.getCallingActivity();
if (caller != null) {
if (caller.getClassName().equals(NewConversationActivity.class.getName())) {
uiPath = SecurejoinUiPath.NewContact;
} else if (caller.getClassName().equals(ConversationListActivity.class.getName())
// RoutingActivity is an alias for ConversationListActivity
|| caller.getClassName().endsWith(".RoutingActivity")) {
uiPath = SecurejoinUiPath.QrIcon;
}
}
return uiPath;
private void setQrResult(String qrData) {
Intent intent = new Intent();
intent.putExtra(Intents.Scan.RESULT, qrData);
setResult(RESULT_OK, intent);
finish();
}
@Override
@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.connect.AccountManager;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.relay.RelayListActivity;
import org.thoughtcrime.securesms.util.IntentUtils;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.views.ProgressDialog;
@@ -53,118 +54,182 @@ public class QrCodeHandler {
accId = dcContext.getAccountId();
}
public void onScanPerformed(IntentResult scanResult, SecurejoinUiPath uipath) {
if (scanResult == null || scanResult.getFormatName() == null) {
return; // aborted
}
handleQrData(scanResult.getContents(), SecurejoinSource.Scan, uipath);
/** Process only QR about getting in contact or joining chats */
public void handleOnlySecureJoinQr(String rawString, SecurejoinSource source, SecurejoinUiPath uiPath) {
final DcLot qrParsed = dcContext.checkQr(rawString);
if (!handleSecureJoinQr(qrParsed, rawString, source, uiPath)) {
handleDefault(new AlertDialog.Builder(activity), rawString, qrParsed);
}
}
public void handleQrData(String rawString, SecurejoinSource source, SecurejoinUiPath uiPath) {
private boolean handleSecureJoinQr(DcLot qrParsed, String rawString, SecurejoinSource source, SecurejoinUiPath uiPath) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
String name = dcContext.getContact(qrParsed.getId()).getDisplayName();
switch (qrParsed.getState()) {
case DcContext.DC_QR_ASK_VERIFYCONTACT:
case DcContext.DC_QR_ASK_VERIFYGROUP:
case DcContext.DC_QR_ASK_JOIN_BROADCAST:
showVerifyContactOrGroup(builder, rawString, qrParsed, name, source, uiPath);
break;
case DcContext.DC_QR_WITHDRAW_VERIFYCONTACT:
case DcContext.DC_QR_WITHDRAW_VERIFYGROUP:
case DcContext.DC_QR_WITHDRAW_JOINBROADCAST:
String message = qrParsed.getState() == DcContext.DC_QR_WITHDRAW_VERIFYCONTACT ? activity.getString(R.string.withdraw_verifycontact_explain)
: qrParsed.getState() == DcContext.DC_QR_WITHDRAW_VERIFYCONTACT ? activity.getString(R.string.withdraw_verifygroup_explain, qrParsed.getText1())
: activity.getString(R.string.withdraw_joinbroadcast_explain, qrParsed.getText1());
builder.setTitle(R.string.qrshow_title);
builder.setMessage(message);
builder.setNeutralButton(R.string.reset, (dialog, which) -> {
dcContext.setConfigFromQr(rawString);
});
builder.setPositiveButton(R.string.ok, null);
Util.redButton(builder.show(), AlertDialog.BUTTON_NEUTRAL);
return true;
case DcContext.DC_QR_REVIVE_VERIFYCONTACT:
case DcContext.DC_QR_REVIVE_VERIFYGROUP:
case DcContext.DC_QR_REVIVE_JOINBROADCAST:
builder.setTitle(R.string.qrshow_title);
builder.setMessage(activity.getString(R.string.revive_verifycontact_explain));
builder.setNeutralButton(R.string.revive_qr_code, (dialog, which) -> {
dcContext.setConfigFromQr(rawString);
});
builder.setPositiveButton(R.string.ok, null);
break;
case DcContext.DC_QR_FPR_WITHOUT_ADDR:
showVerifyFingerprintWithoutAddress(builder, qrParsed);
break;
case DcContext.DC_QR_FPR_MISMATCH:
showFingerPrintError(builder, name);
break;
case DcContext.DC_QR_FPR_OK:
case DcContext.DC_QR_ADDR:
showFingerprintOrQrSuccess(builder, qrParsed, name);
break;
default:
return false;
}
builder.create().show();
return true;
}
/** Process only QR about adding relays/profiles (DCACCOUNT: / DCLOGIN:) */
public void handleOnlyAddRelayQr(String rawString) {
final DcLot qrParsed = dcContext.checkQr(rawString);
if (!handleAddRelayQr(qrParsed, rawString)) {
handleDefault(new AlertDialog.Builder(activity), rawString, qrParsed);
}
}
private boolean handleAddRelayQr(DcLot qrParsed, String rawString) {
switch (qrParsed.getState()) {
case DcContext.DC_QR_ACCOUNT:
case DcContext.DC_QR_LOGIN:
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final DcLot qrParsed = dcContext.checkQr(rawString);
String name = dcContext.getContact(qrParsed.getId()).getDisplayName();
switch (qrParsed.getState()) {
case DcContext.DC_QR_ASK_VERIFYCONTACT:
case DcContext.DC_QR_ASK_VERIFYGROUP:
case DcContext.DC_QR_ASK_JOIN_BROADCAST:
showVerifyContactOrGroup(activity, builder, rawString, qrParsed, name, source, uiPath);
break;
case DcContext.DC_QR_FPR_WITHOUT_ADDR:
showVerifyFingerprintWithoutAddress(builder, qrParsed);
break;
case DcContext.DC_QR_FPR_MISMATCH:
showFingerPrintError(builder, name);
break;
case DcContext.DC_QR_FPR_OK:
case DcContext.DC_QR_ADDR:
showFingerprintOrQrSuccess(builder, qrParsed, name);
break;
case DcContext.DC_QR_URL:
showQrUrl(builder, qrParsed);
break;
case DcContext.DC_QR_ACCOUNT:
case DcContext.DC_QR_LOGIN:
final String scope = qrParsed.getText1();
setAddTransportDialog(activity, builder, rawString, scope);
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
break;
case DcContext.DC_QR_BACKUP2:
builder.setTitle(R.string.multidevice_receiver_title);
builder.setMessage(activity.getString(R.string.multidevice_receiver_scanning_ask) + "\n\n" + activity.getString(R.string.multidevice_same_network_hint));
builder.setPositiveButton(R.string.perm_continue, (dialog, which) -> {
AccountManager.getInstance().addAccountFromSecondDevice(activity, rawString);
});
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
AlertDialog alertDialog = builder.create();
alertDialog.show();
BackupTransferActivity.appendSSID(activity, alertDialog.findViewById(android.R.id.message));
return;
case DcContext.DC_QR_BACKUP_TOO_NEW:
builder.setTitle(R.string.multidevice_receiver_title);
builder.setMessage(activity.getString(R.string.multidevice_receiver_needs_update));
builder.setNegativeButton(R.string.ok, null);
break;
case DcContext.DC_QR_PROXY:
builder.setTitle(R.string.proxy_use_proxy);
builder.setMessage(activity.getString(R.string.proxy_use_proxy_confirm, qrParsed.getText1()));
builder.setPositiveButton(R.string.proxy_use_proxy, (dlg, btn) -> {
dcContext.setConfigFromQr(rawString);
dcContext.restartIo();
showDoneToast(activity);
});
if (rawString.toLowerCase().startsWith("http")) {
builder.setNeutralButton(R.string.open, (d, b) -> IntentUtils.showInBrowser(activity, rawString));
}
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
break;
case DcContext.DC_QR_WITHDRAW_VERIFYCONTACT:
case DcContext.DC_QR_WITHDRAW_VERIFYGROUP:
case DcContext.DC_QR_WITHDRAW_JOINBROADCAST:
String message = qrParsed.getState() == DcContext.DC_QR_WITHDRAW_VERIFYCONTACT ? activity.getString(R.string.withdraw_verifycontact_explain)
: qrParsed.getState() == DcContext.DC_QR_WITHDRAW_VERIFYCONTACT ? activity.getString(R.string.withdraw_verifygroup_explain, qrParsed.getText1())
: activity.getString(R.string.withdraw_joinbroadcast_explain, qrParsed.getText1());
builder.setTitle(R.string.qrshow_title);
builder.setMessage(message);
builder.setNeutralButton(R.string.reset, (dialog, which) -> {
dcContext.setConfigFromQr(rawString);
});
builder.setPositiveButton(R.string.ok, null);
AlertDialog withdrawDialog = builder.show();
Util.redButton(withdrawDialog, AlertDialog.BUTTON_NEUTRAL);
return;
case DcContext.DC_QR_REVIVE_VERIFYCONTACT:
case DcContext.DC_QR_REVIVE_VERIFYGROUP:
case DcContext.DC_QR_REVIVE_JOINBROADCAST:
builder.setTitle(R.string.qrshow_title);
builder.setMessage(activity.getString(R.string.revive_verifycontact_explain));
builder.setNeutralButton(R.string.revive_qr_code, (dialog, which) -> {
dcContext.setConfigFromQr(rawString);
});
builder.setPositiveButton(R.string.ok, null);
break;
default:
handleDefault(builder, rawString, qrParsed);
break;
}
builder.setTitle(R.string.confirm_add_transport);
builder.setMessage(qrParsed.getText1());
builder.setPositiveButton(R.string.ok, (d, w) -> {
if (activity instanceof RelayListActivity) {
// no need to protect with lock, RelayListActivity is already protected
addRelay(rawString);
} else {
boolean result = ScreenLockUtil.applyScreenLock(activity, activity.getString(R.string.add_transport), activity.getString(R.string.enter_system_secret_to_continue), ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS);
if (!result) {
addRelay(rawString);
}
}
});
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
builder.create().show();
return true;
default:
return false;
}
}
/** Process a proxy QR, returns true if a dialog was shown, false if the QR is not a proxy QR */
public boolean handleProxyQr(String rawString) {
return handleProxyQr(dcContext.checkQr(rawString), rawString);
}
private boolean handleProxyQr(DcLot qrParsed, String rawString) {
if (qrParsed.getState() == DcContext.DC_QR_PROXY) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.proxy_use_proxy);
builder.setMessage(activity.getString(R.string.proxy_use_proxy_confirm, qrParsed.getText1()));
builder.setPositiveButton(R.string.proxy_use_proxy, (dlg, btn) -> {
dcContext.setConfigFromQr(rawString);
dcContext.restartIo();
showDoneToast();
});
if (rawString.toLowerCase().startsWith("http")) {
builder.setNeutralButton(R.string.open, (d, b) -> IntentUtils.showInBrowser(activity, rawString));
}
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
builder.create().show();
return true;
}
return false;
}
/** Process a backup QR, returns true if a dialog was shown, false if the QR is not a backup QR */
public boolean handleBackupQr(String rawString) {
return handleBackupQr(dcContext.checkQr(rawString), rawString);
}
private boolean handleBackupQr(DcLot qrParsed, String rawString) {
switch (qrParsed.getState()) {
case DcContext.DC_QR_BACKUP2:
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.multidevice_receiver_title);
builder.setMessage(activity.getString(R.string.multidevice_receiver_scanning_ask) + "\n\n" + activity.getString(R.string.multidevice_same_network_hint));
builder.setPositiveButton(R.string.perm_continue, (dialog, which) -> {
AccountManager.getInstance().addAccountFromSecondDevice(activity, rawString);
});
builder.setNegativeButton(R.string.cancel, null);
builder.setCancelable(false);
AlertDialog alertDialog = builder.create();
alertDialog.show();
BackupTransferActivity.appendSSID(activity, alertDialog.findViewById(android.R.id.message));
return true;
case DcContext.DC_QR_BACKUP_TOO_NEW:
new AlertDialog.Builder(activity)
.setTitle(R.string.multidevice_receiver_title)
.setMessage(activity.getString(R.string.multidevice_receiver_needs_update))
.setNegativeButton(R.string.ok, null)
.create().show();
return true;
default:
return false;
}
}
/** Handle any kind of QR showing an AlertDialog adapted to the QR type. */
public void handleQrData(String rawString, SecurejoinSource source, SecurejoinUiPath uiPath) {
final DcLot qrParsed = dcContext.checkQr(rawString);
if (handleSecureJoinQr(qrParsed, rawString, source, uiPath)
|| handleAddRelayQr(qrParsed, rawString)
|| handleProxyQr(qrParsed, rawString)
|| handleBackupQr(qrParsed, rawString)) return;
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
if (qrParsed.getState() == DcContext.DC_QR_URL) {
showQrUrl(builder, qrParsed);
} else {
handleDefault(builder, rawString, qrParsed);
}
builder.create().show();
}
private void handleDefault(AlertDialog.Builder builder, String qrRawString, DcLot qrParsed) {
String msg;
@@ -187,7 +252,7 @@ public class QrCodeHandler {
builder.setPositiveButton(android.R.string.ok, null);
builder.setNeutralButton(R.string.menu_copy_to_clipboard, (dialog, which) -> {
Util.writeTextToClipboard(activity, scannedText);
showDoneToast(activity);
showDoneToast();
});
}
@@ -199,11 +264,11 @@ public class QrCodeHandler {
builder.setNegativeButton(android.R.string.cancel, null);
builder.setNeutralButton(R.string.menu_copy_to_clipboard, (dialog, which) -> {
Util.writeTextToClipboard(activity, url);
showDoneToast(activity);
showDoneToast();
});
}
private void showDoneToast(Activity activity) {
private void showDoneToast() {
Toast.makeText(activity, activity.getString(R.string.done), Toast.LENGTH_SHORT).show();
}
@@ -232,12 +297,11 @@ public class QrCodeHandler {
builder.setPositiveButton(android.R.string.ok, null);
builder.setNeutralButton(R.string.menu_copy_to_clipboard, (dialog, which) -> {
Util.writeTextToClipboard(activity, qrParsed.getText1());
showDoneToast(activity);
showDoneToast();
});
}
private void showVerifyContactOrGroup(Activity activity,
AlertDialog.Builder builder,
private void showVerifyContactOrGroup(AlertDialog.Builder builder,
String qrRawString,
DcLot qrParsed,
String name,
@@ -275,51 +339,47 @@ public class QrCodeHandler {
builder.setNegativeButton(android.R.string.cancel, null);
}
private void setAddTransportDialog(Activity activity, AlertDialog.Builder builder, String qrData, String transportName) {
builder.setTitle(R.string.confirm_add_transport);
builder.setMessage(transportName);
builder.setPositiveButton(R.string.ok, (d, w) -> {
ProgressDialog progressDialog = new ProgressDialog(activity);
progressDialog.setMessage(activity.getResources().getString(R.string.one_moment));
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setCancelable(false);
String cancel = activity.getResources().getString(android.R.string.cancel);
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, cancel, (d2, w2) -> {
dcContext.stopOngoingProcess();
});
progressDialog.show();
public void addRelay(String qrData) {
ProgressDialog progressDialog = new ProgressDialog(activity);
progressDialog.setMessage(activity.getResources().getString(R.string.one_moment));
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setCancelable(false);
String cancel = activity.getResources().getString(android.R.string.cancel);
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, cancel, (d, w) -> {
dcContext.stopOngoingProcess();
});
progressDialog.show();
Util.runOnAnyBackgroundThread(() -> {
String error = null;
try {
rpc.addTransportFromQr(accId, qrData);
} catch (RpcException e) {
Log.w(TAG, e);
error = e.getMessage();
}
final String finalError = error;
Util.runOnMain(() -> {
if (!progressDialog.isShowing()) return; // canceled dialog, nothing to do
if (finalError != null) {
new AlertDialog.Builder(activity)
.setTitle(R.string.error)
.setMessage(finalError)
.setPositiveButton(R.string.ok, null)
.show();
} else {
showDoneToast(activity);
if (!(activity instanceof RelayListActivity)) {
activity.startActivity(new Intent(activity, RelayListActivity.class));
}
}
try {
progressDialog.dismiss();
} catch (IllegalArgumentException e) {
// see https://stackoverflow.com/a/5102572/4557005
Log.w(TAG, e);
}
});
});
Util.runOnAnyBackgroundThread(() -> {
String error = null;
try {
rpc.addTransportFromQr(accId, qrData);
} catch (RpcException e) {
Log.w(TAG, e);
error = e.getMessage();
}
final String finalError = error;
Util.runOnMain(() -> {
if (!progressDialog.isShowing()) return; // canceled dialog, nothing to do
if (finalError != null) {
new AlertDialog.Builder(activity)
.setTitle(R.string.error)
.setMessage(finalError)
.setPositiveButton(R.string.ok, null)
.show();
} else {
showDoneToast();
if (!(activity instanceof RelayListActivity)) {
activity.startActivity(new Intent(activity, RelayListActivity.class));
}
}
try {
progressDialog.dismiss();
} catch (IllegalArgumentException e) {
// see https://stackoverflow.com/a/5102572/4557005
Log.w(TAG, e);
}
});
});
}
}
@@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.qr.QrActivity;
import org.thoughtcrime.securesms.qr.QrCodeHandler;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -32,8 +33,6 @@ import java.util.List;
import chat.delta.rpc.Rpc;
import chat.delta.rpc.RpcException;
import chat.delta.rpc.types.EnteredLoginParam;
import chat.delta.rpc.types.SecurejoinSource;
import chat.delta.rpc.types.SecurejoinUiPath;
public class RelayListActivity extends BaseActionBarActivity
implements RelayListAdapter.OnRelayClickListener, DcEventCenter.DcEventDelegate {
@@ -45,6 +44,9 @@ public class RelayListActivity extends BaseActionBarActivity
private Rpc rpc;
private int accId;
/** QR provided via Intent extras needs to be saved to pass it to QrCodeHandler when authorization finishes */
private String qrData = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -67,6 +69,15 @@ public class RelayListActivity extends BaseActionBarActivity
// Apply insets to prevent fab from being covered by system bars
ViewUtil.applyWindowInsetsAsMargin(fabAdd);
qrData = getIntent().getStringExtra(EXTRA_QR_DATA);
if (qrData != null) {
// when the activity is opened with a QR data, we need to ask for authorization first
boolean result = ScreenLockUtil.applyScreenLock(this, getString(R.string.add_transport), getString(R.string.enter_system_secret_to_continue), ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS);
if (!result) {
new QrCodeHandler(this).handleOnlyAddRelayQr(qrData);
}
}
fabAdd.setOnClickListener(v -> {
new IntentIntegrator(this).setCaptureActivity(QrActivity.class).addExtra(QrActivity.EXTRA_SCAN_RELAY, true).initiateScan();
});
@@ -86,12 +97,6 @@ public class RelayListActivity extends BaseActionBarActivity
DcEventCenter eventCenter = DcHelper.getEventCenter(this);
eventCenter.addObserver(DcContext.DC_EVENT_CONFIGURE_PROGRESS, this);
String qrdata = getIntent().getStringExtra(EXTRA_QR_DATA);
if (qrdata != null) {
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.handleQrData(qrdata, SecurejoinSource.Unknown, SecurejoinUiPath.Unknown);
}
}
@Override
@@ -172,10 +177,22 @@ public class RelayListActivity extends BaseActionBarActivity
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) {
// if user canceled unlocking, then finish
if (requestCode == ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS) finish();
return;
}
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
if (requestCode == IntentIntegrator.REQUEST_CODE) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
QrCodeHandler qrCodeHandler = new QrCodeHandler(this);
qrCodeHandler.onScanPerformed(scanResult, SecurejoinUiPath.Unknown);
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
qrCodeHandler.handleOnlyAddRelayQr(scanResult.getContents());
} else if (requestCode == ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS) {
// user authorized, then proceed to handle the QR data
if (qrData != null) {
qrCodeHandler.handleOnlyAddRelayQr(qrData);
qrData = null;
}
}
}
@@ -82,15 +82,11 @@ public class LongClickCopySpan extends ClickableSpan {
}
} else if (Util.isInviteURL(url)) {
QrCodeHandler qrCodeHandler = new QrCodeHandler((Activity) widget.getContext());
qrCodeHandler.handleQrData(url, SecurejoinSource.InternalLink, null);
qrCodeHandler.handleOnlySecureJoinQr(url, SecurejoinSource.InternalLink, null);
} else {
Activity activity = (Activity) widget.getContext();
DcContext dcContext = DcHelper.getContext(activity);
if (dcContext.checkQr(url).getState() == DcContext.DC_QR_PROXY) {
QrCodeHandler qrCodeHandler = new QrCodeHandler(activity);
qrCodeHandler.handleQrData(url, null, null);
} else {
IntentUtils.showInBrowser(widget.getContext(), url);
if (!new QrCodeHandler(activity).handleProxyQr(url)) {
IntentUtils.showInBrowser(activity, url);
}
}
}