mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
402 lines
16 KiB
Java
402 lines
16 KiB
Java
package org.thoughtcrime.securesms;
|
|
|
|
import android.Manifest;
|
|
import android.annotation.SuppressLint;
|
|
import android.app.Activity;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.text.util.Linkify;
|
|
import android.util.Log;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.widget.Button;
|
|
import android.widget.TextView;
|
|
|
|
import androidx.activity.OnBackPressedCallback;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.appcompat.app.ActionBar;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
|
|
import com.b44t.messenger.DcContext;
|
|
import com.b44t.messenger.DcEvent;
|
|
import com.google.zxing.integration.android.IntentIntegrator;
|
|
import com.google.zxing.integration.android.IntentResult;
|
|
|
|
import org.thoughtcrime.securesms.connect.AccountManager;
|
|
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
|
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;
|
|
import org.thoughtcrime.securesms.util.Prefs;
|
|
import org.thoughtcrime.securesms.util.StorageUtil;
|
|
import org.thoughtcrime.securesms.util.StreamUtil;
|
|
import org.thoughtcrime.securesms.util.Util;
|
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
|
import org.thoughtcrime.securesms.util.views.ProgressDialog;
|
|
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
|
|
public class WelcomeActivity extends BaseActionBarActivity implements DcEventCenter.DcEventDelegate {
|
|
public static final String BACKUP_QR_EXTRA = "backup_qr_extra";
|
|
public static final int PICK_BACKUP = 20574;
|
|
private final static String TAG = WelcomeActivity.class.getSimpleName();
|
|
public static final String TMP_BACKUP_FILE = "tmp-backup-file";
|
|
|
|
private ProgressDialog progressDialog = null;
|
|
private boolean imexUserAborted;
|
|
DcContext dcContext;
|
|
private NotificationController notificationController;
|
|
|
|
@Override
|
|
public void onCreate(Bundle bundle) {
|
|
super.onCreate(bundle);
|
|
setContentView(R.layout.welcome_activity);
|
|
|
|
// add padding to avoid content hidden behind system bars
|
|
ViewUtil.applyWindowInsets(findViewById(R.id.content_container));
|
|
|
|
Button signUpButton = findViewById(R.id.signup_button);
|
|
Button signInButton = findViewById(R.id.signin_button);
|
|
|
|
View view = View.inflate(this, R.layout.login_options_view, null);
|
|
AlertDialog signInDialog = new AlertDialog.Builder(this)
|
|
.setView(view)
|
|
.setTitle(R.string.onboarding_alternative_logins)
|
|
.setNegativeButton(R.string.cancel, null)
|
|
.create();
|
|
view.findViewById(R.id.add_as_second_device_button).setOnClickListener((v) -> {
|
|
showSignInDialogWithPermission(signInDialog);
|
|
});
|
|
view.findViewById(R.id.backup_button).setOnClickListener((v) -> {
|
|
startImportBackup();
|
|
signInDialog.dismiss();
|
|
});
|
|
|
|
signUpButton.setOnClickListener((v) -> startActivity(new Intent(this, InstantOnboardingActivity.class)));
|
|
signInButton.setOnClickListener((v) -> signInDialog.show());
|
|
|
|
registerForEvents();
|
|
initializeActionBar();
|
|
|
|
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
|
@Override
|
|
public void handleOnBackPressed() {
|
|
AccountManager accountManager = AccountManager.getInstance();
|
|
if (accountManager.canRollbackAccountCreation(WelcomeActivity.this)) {
|
|
accountManager.rollbackAccountCreation(WelcomeActivity.this);
|
|
} else {
|
|
setEnabled(false);
|
|
getOnBackPressedDispatcher().onBackPressed();
|
|
}
|
|
}
|
|
});
|
|
|
|
DcHelper.maybeShowMigrationError(this);
|
|
}
|
|
|
|
private void showSignInDialogWithPermission(AlertDialog signInDialog) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
|
&& !Prefs.getBooleanPreference(this, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, false)) {
|
|
Prefs.setBooleanPreference(this, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, true);
|
|
Permissions.with(this)
|
|
.request(Manifest.permission.POST_NOTIFICATIONS)
|
|
.ifNecessary()
|
|
.onAllGranted(() -> {
|
|
startAddAsSecondDeviceActivity();
|
|
signInDialog.dismiss();
|
|
})
|
|
.onAnyDenied(() -> {
|
|
startAddAsSecondDeviceActivity();
|
|
signInDialog.dismiss();
|
|
})
|
|
.execute();
|
|
} else {
|
|
startAddAsSecondDeviceActivity();
|
|
signInDialog.dismiss();
|
|
}
|
|
}
|
|
|
|
protected void initializeActionBar() {
|
|
ActionBar supportActionBar = getSupportActionBar();
|
|
if (supportActionBar == null) throw new AssertionError();
|
|
|
|
boolean canGoBack = AccountManager.getInstance().canRollbackAccountCreation(this);
|
|
supportActionBar.setDisplayHomeAsUpEnabled(canGoBack);
|
|
getSupportActionBar().setTitle(canGoBack? R.string.add_account : R.string.welcome_desktop);
|
|
}
|
|
|
|
private void registerForEvents() {
|
|
dcContext = DcHelper.getContext(this);
|
|
DcHelper.getEventCenter(this).addObserver(DcContext.DC_EVENT_IMEX_PROGRESS, this);
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
super.onOptionsItemSelected(item);
|
|
|
|
switch (item.getItemId()) {
|
|
case android.R.id.home:
|
|
getOnBackPressedDispatcher().onBackPressed();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
protected void onNewIntent(Intent intent) {
|
|
super.onNewIntent(intent);
|
|
setIntent(intent);
|
|
}
|
|
|
|
@Override
|
|
public void onStart() {
|
|
super.onStart();
|
|
String backupQr = getIntent().getStringExtra(BACKUP_QR_EXTRA);
|
|
if (backupQr != null) {
|
|
getIntent().removeExtra(BACKUP_QR_EXTRA);
|
|
startBackupTransfer(backupQr);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
super.onDestroy();
|
|
DcHelper.getEventCenter(this).removeObservers(this);
|
|
}
|
|
|
|
@Override
|
|
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
|
|
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
|
}
|
|
|
|
private void startAddAsSecondDeviceActivity() {
|
|
new IntentIntegrator(this).setCaptureActivity(RegistrationQrActivity.class)
|
|
.addExtra(RegistrationQrActivity.ADD_AS_SECOND_DEVICE_EXTRA, true)
|
|
.initiateScan();
|
|
}
|
|
|
|
@SuppressLint("InlinedApi")
|
|
private void startImportBackup() {
|
|
Permissions.with(this)
|
|
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
.alwaysGrantOnSdk30()
|
|
.ifNecessary()
|
|
.withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied))
|
|
.onAllGranted(() -> {
|
|
File imexDir = DcHelper.getImexDir();
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
AttachmentManager.selectMediaType(this, "application/x-tar", null, PICK_BACKUP, StorageUtil.getDownloadUri());
|
|
} else {
|
|
final String backupFile = dcContext.imexHasBackup(imexDir.getAbsolutePath());
|
|
if (backupFile != null) {
|
|
new AlertDialog.Builder(this)
|
|
.setTitle(R.string.import_backup_title)
|
|
.setMessage(String.format(getResources().getString(R.string.import_backup_ask), backupFile))
|
|
.setNegativeButton(android.R.string.cancel, null)
|
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> startImport(backupFile, null))
|
|
.show();
|
|
}
|
|
else {
|
|
new AlertDialog.Builder(this)
|
|
.setTitle(R.string.import_backup_title)
|
|
.setMessage(String.format(getResources().getString(R.string.import_backup_no_backup_found), imexDir.getAbsolutePath()))
|
|
.setPositiveButton(android.R.string.ok, null)
|
|
.show();
|
|
}
|
|
}
|
|
})
|
|
.execute();
|
|
}
|
|
|
|
private void startImport(@Nullable final String backupFile, final @Nullable Uri backupFileUri)
|
|
{
|
|
notificationController = GenericForegroundService.startForegroundTask(this, getString(R.string.import_backup_title));
|
|
|
|
if( progressDialog!=null ) {
|
|
progressDialog.dismiss();
|
|
progressDialog = null;
|
|
}
|
|
|
|
imexUserAborted = false;
|
|
progressDialog = new ProgressDialog(this);
|
|
progressDialog.setMessage(getResources().getString(R.string.one_moment));
|
|
progressDialog.setCanceledOnTouchOutside(false);
|
|
progressDialog.setCancelable(false);
|
|
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getResources().getString(android.R.string.cancel), (dialog, which) -> {
|
|
imexUserAborted = true;
|
|
dcContext.stopOngoingProcess();
|
|
notificationController.close();
|
|
cleanupTempBackupFile();
|
|
});
|
|
progressDialog.show();
|
|
|
|
Util.runOnBackground(() -> {
|
|
String file = backupFile;
|
|
if (backupFile == null) {
|
|
try {
|
|
file = copyToCacheDir(backupFileUri).getAbsolutePath();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
notificationController.close();
|
|
cleanupTempBackupFile();
|
|
return;
|
|
}
|
|
}
|
|
|
|
DcHelper.getEventCenter(this).captureNextError();
|
|
dcContext.imex(DcContext.DC_IMEX_IMPORT_BACKUP, file);
|
|
});
|
|
}
|
|
|
|
private File copyToCacheDir(Uri uri) throws IOException {
|
|
try (InputStream inputStream = getContentResolver().openInputStream(uri)) {
|
|
File file = File.createTempFile(TMP_BACKUP_FILE, ".tmp", getCacheDir());
|
|
try (OutputStream outputStream = new FileOutputStream(file)) {
|
|
StreamUtil.copy(inputStream, outputStream);
|
|
}
|
|
return file;
|
|
}
|
|
}
|
|
|
|
private void startBackupTransfer(String qrCode)
|
|
{
|
|
if (progressDialog!=null) {
|
|
progressDialog.dismiss();
|
|
progressDialog = null;
|
|
}
|
|
|
|
Intent intent = new Intent(this, BackupTransferActivity.class);
|
|
intent.putExtra(BackupTransferActivity.TRANSFER_MODE, BackupTransferActivity.TransferMode.RECEIVER_SCAN_QR.getInt());
|
|
intent.putExtra(BackupTransferActivity.QR_CODE, qrCode);
|
|
startActivity(intent);
|
|
}
|
|
|
|
private void progressError(String data2) {
|
|
progressDialog.dismiss();
|
|
maybeShowConfigurationError(this, data2);
|
|
}
|
|
|
|
private void progressUpdate(int progress) {
|
|
int percent = progress / 10;
|
|
progressDialog.setMessage(getResources().getString(R.string.one_moment)+String.format(" %d%%", percent));
|
|
}
|
|
|
|
private void progressSuccess() {
|
|
DcHelper.getEventCenter(this).endCaptureNextError();
|
|
progressDialog.dismiss();
|
|
Intent intent = new Intent(getApplicationContext(), ConversationListActivity.class);
|
|
intent.putExtra(ConversationListActivity.FROM_WELCOME, true);
|
|
startActivity(intent);
|
|
finish();
|
|
}
|
|
|
|
public static void maybeShowConfigurationError(Activity activity, String data2) {
|
|
if (activity.isFinishing()) return; // avoid android.view.WindowManager$BadTokenException
|
|
|
|
if (data2 != null && !data2.isEmpty()) {
|
|
AlertDialog d = new AlertDialog.Builder(activity)
|
|
.setMessage(data2)
|
|
.setPositiveButton(android.R.string.ok, null)
|
|
.create();
|
|
d.show();
|
|
try {
|
|
//noinspection ConstantConditions
|
|
Linkify.addLinks((TextView) d.findViewById(android.R.id.message), Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES);
|
|
} catch(NullPointerException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void handleEvent(@NonNull DcEvent event) {
|
|
int eventId = event.getId();
|
|
|
|
if (eventId== DcContext.DC_EVENT_IMEX_PROGRESS ) {
|
|
long progress = event.getData1Int();
|
|
if (progressDialog == null || notificationController == null) {
|
|
// IMEX runs in BackupTransferActivity
|
|
if (progress == 1000) {
|
|
finish(); // transfer done - remove ourself from the activity stack (finishAffinity is available in API 16, we're targeting API 14)
|
|
}
|
|
return;
|
|
}
|
|
if (progress==0/*error/aborted*/) {
|
|
if (!imexUserAborted) {
|
|
progressError(dcContext.getLastError());
|
|
}
|
|
notificationController.close();
|
|
cleanupTempBackupFile();
|
|
}
|
|
else if (progress<1000/*progress in permille*/) {
|
|
progressUpdate((int)progress);
|
|
notificationController.setProgress(1000, progress, String.format(" %d%%", (int) progress / 10));
|
|
}
|
|
else if (progress==1000/*done*/) {
|
|
DcHelper.getAccounts(this).startIo();
|
|
progressSuccess();
|
|
notificationController.close();
|
|
cleanupTempBackupFile();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
|
|
if (resultCode != RESULT_OK) {
|
|
return;
|
|
}
|
|
|
|
if (requestCode==IntentIntegrator.REQUEST_CODE) {
|
|
String qrRaw = data.getStringExtra(RegistrationQrActivity.QRDATA_EXTRA);
|
|
if (qrRaw == null) {
|
|
IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
|
|
qrRaw = scanResult.getContents();
|
|
}
|
|
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);
|
|
if (uri == null) {
|
|
Log.e(TAG, " Can't import null URI");
|
|
return;
|
|
}
|
|
startImport(null, uri);
|
|
}
|
|
}
|
|
|
|
private void cleanupTempBackupFile() {
|
|
try {
|
|
File[] files = getCacheDir().listFiles((dir, name) -> name.startsWith(TMP_BACKUP_FILE));
|
|
for (File file : files) {
|
|
if (file.getName().endsWith("tmp")) {
|
|
Log.i(TAG, "Deleting temp backup file " + file);
|
|
file.delete();
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|