diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Activities/AuthenticateActivity.java b/app/src/main/java/org/shadowice/flocke/andotp/Activities/AuthenticateActivity.java index 0e6dadb2..a3e284c7 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Activities/AuthenticateActivity.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Activities/AuthenticateActivity.java @@ -22,19 +22,13 @@ package org.shadowice.flocke.andotp.Activities; -import android.app.Fragment; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Bundle; import com.google.android.material.textfield.TextInputLayout; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; -import androidx.lifecycle.DefaultLifecycleObserver; -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.ProcessLifecycleOwner; import android.text.Editable; import android.text.InputType; @@ -59,17 +53,14 @@ import org.shadowice.flocke.andotp.View.AutoFillable.AutoFillableTextInputEditTe import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod; -public class AuthenticateActivity extends BaseActivity +public class AuthenticateActivity extends BackgroundTaskActivity implements EditText.OnEditorActionListener, View.OnClickListener { private final AutoFillableTextInputEditText.AutoFillTextListener autoFillTextListener = text -> startAuthTask(text.toString()); - private static final String TAG_TASK_FRAGMENT = "AuthenticateActivity.TaskFragmentTag"; - private AuthMethod authMethod; private String newEncryption = ""; private String existingAuthCredentials; private boolean isAuthUpgrade = false; - private ProcessLifecycleObserver observer; private TextInputLayout passwordLayout; AutoFillableTextInputEditText passwordInput; @@ -113,10 +104,6 @@ public class AuthenticateActivity extends BaseActivity } }); - observer = new ProcessLifecycleObserver(); - ProcessLifecycleOwner.get().getLifecycle() - .addObserver(observer); - getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); } @@ -171,58 +158,21 @@ public class AuthenticateActivity extends BaseActivity unlockProgress = v.findViewById(R.id.unlockProgress); } - private void cancelBackgroundTask() { - TaskFragment taskFragment = findTaskFragment(); - if (taskFragment != null) { - taskFragment.task.cancel(); - } - setupUiForTaskState(false); - } - - private class ProcessLifecycleObserver implements DefaultLifecycleObserver { - @Override - public void onStop(@NonNull LifecycleOwner owner) { - if (settings.getRelockOnBackground()) { - cancelBackgroundTask(); - } - } + @Override + protected boolean cancelTaskOnScreenOff() { + return true; } @Override - public void onResume() { - super.onResume(); - checkBackgroundTask(); - } - - private void checkBackgroundTask() { - TaskFragment taskFragment = findTaskFragment(); - if (taskFragment != null) { - if (taskFragment.task.isCanceled()) { - // The task was canceled, so remove the task fragment and reset password input. - getFragmentManager().beginTransaction() - .remove(taskFragment) - .commit(); - resetPasswordInput(); - } else { - taskFragment.task.setCallback(this::handleResult); - setupUiForTaskState(true); - } - } - } - - private void resetPasswordInput() { + protected void onReturnToCanceledTask() { passwordInput.setText(""); passwordInput.requestFocus(); InputMethodManager keyboard = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); keyboard.showSoftInput(passwordInput, 0); } - @Nullable - private TaskFragment findTaskFragment() { - return (TaskFragment) getFragmentManager().findFragmentByTag(TAG_TASK_FRAGMENT); - } - - private void setupUiForTaskState(boolean isTaskRunning) { + @Override + protected void setupUiForTaskState(boolean isTaskRunning) { passwordLayout.setEnabled(!isTaskRunning); passwordInput.setEnabled(!isTaskRunning); unlockButton.setEnabled(!isTaskRunning); @@ -246,25 +196,12 @@ public class AuthenticateActivity extends BaseActivity } private void startAuthTask(String plainPassword) { - TaskFragment taskFragment = findTaskFragment(); - // Don't start a task if we already have an active task running. - if (taskFragment == null || taskFragment.task.isCanceled()) { - AuthenticationTask task = new AuthenticationTask(this, isAuthUpgrade, existingAuthCredentials, plainPassword); - task.setCallback(this::handleResult); - - if (taskFragment == null) { - taskFragment = new TaskFragment(); - getFragmentManager() - .beginTransaction() - .add(taskFragment, TAG_TASK_FRAGMENT) - .commit(); - } - taskFragment.startTask(task); - setupUiForTaskState(true); - } + AuthenticationTask task = new AuthenticationTask(this, isAuthUpgrade, existingAuthCredentials, plainPassword); + startBackgroundTask(task); } - private void handleResult(Result result) { + @Override + void onTaskResult(Result result) { if (result.authUpgradeFailed) { Toast.makeText(this, R.string.settings_toast_auth_upgrade_failed, Toast.LENGTH_LONG).show(); } @@ -296,47 +233,14 @@ public class AuthenticateActivity extends BaseActivity } } - @Override - protected void onPause() { - super.onPause(); - // We don't want the task to callback to a dead activity and cause a memory leak, so null it here. - TaskFragment taskFragment = findTaskFragment(); - if (taskFragment != null) { - taskFragment.task.setCallback(null); - } - } - @Override protected void onStop() { passwordInput.setAutoFillTextListener(null); super.onStop(); } - @Override - protected void onDestroy() { - ProcessLifecycleOwner.get().getLifecycle() - .removeObserver(observer); - super.onDestroy(); - } - @Override protected boolean shouldDestroyOnScreenOff() { return false; } - - /** Retained instance fragment to hold a running {@link AuthenticationTask} between configuration changes.*/ - public static class TaskFragment extends Fragment { - - AuthenticationTask task; - - public TaskFragment() { - super(); - setRetainInstance(true); - } - - public void startTask(@NonNull AuthenticationTask task) { - this.task = task; - task.execute(); - } - } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Activities/BackgroundTaskActivity.java b/app/src/main/java/org/shadowice/flocke/andotp/Activities/BackgroundTaskActivity.java new file mode 100644 index 00000000..845dee4b --- /dev/null +++ b/app/src/main/java/org/shadowice/flocke/andotp/Activities/BackgroundTaskActivity.java @@ -0,0 +1,191 @@ +package org.shadowice.flocke.andotp.Activities; + +import android.app.Fragment; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ProcessLifecycleOwner; + +import org.shadowice.flocke.andotp.Tasks.UiBasedBackgroundTask; + +public abstract class BackgroundTaskActivity extends BaseActivity { + final protected String TAG_TASK_FRAGMENT = this.getClass().getSimpleName() + ".TaskFragment"; + + private ProcessLifecycleObserver observer = null; + + abstract void onTaskResult(Result result); + + protected void onReturnToCanceledTask() { + // This can be overwritten if we need to do something here + } + + protected void setupUiForTaskState(boolean running) { + // This can be overwritten when we need to give some UI feedback + } + + protected boolean cancelTaskOnScreenOff() { + return false; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (cancelTaskOnScreenOff()) { + observer = new ProcessLifecycleObserver(); + + ProcessLifecycleOwner.get().getLifecycle() + .addObserver(observer); + } + } + + @Override + protected void onDestroy() { + if (observer != null) { + ProcessLifecycleOwner.get().getLifecycle() + .removeObserver(observer); + + observer = null; + } + + super.onDestroy(); + } + + @Override + public void onResume() { + super.onResume(); + checkBackgroundTask(); + } + + @Override + protected void onPause() { + super.onPause(); + + // We don't want the task to callback to a dead activity and cause a memory leak, so null it here. + TaskFragment taskFragment = findTaskFragment(); + + if (taskFragment != null) + taskFragment.setCallback(null); + } + + protected void cancelBackgroundTask() { + TaskFragment taskFragment = findTaskFragment(); + + if (taskFragment != null) + taskFragment.cancelTask(); + + setupUiForTaskState(false); + } + + protected void startBackgroundTask(UiBasedBackgroundTask task) { + TaskFragment taskFragment = findTaskFragment(); + + // Don't start a task if we already have an active task running. + if (taskFragment == null || taskFragment.isCanceled()) { + task.setCallback(this::handleTaskResult); + + if (taskFragment == null) { + taskFragment = new TaskFragment<>(); + getFragmentManager() + .beginTransaction() + .add(taskFragment, TAG_TASK_FRAGMENT) + .commit(); + } + + taskFragment.startTask(task); + + setupUiForTaskState(true); + } + } + + private void handleTaskResult(Result result) { + setupUiForTaskState(false); + + onTaskResult(result); + + // Remove the task fragment after the task is finished + TaskFragment taskFragment = findTaskFragment(); + + if (taskFragment != null) { + getFragmentManager().beginTransaction() + .remove(taskFragment) + .commit(); + } + } + + private void checkBackgroundTask() { + TaskFragment taskFragment = findTaskFragment(); + + if (taskFragment != null) { + if (taskFragment.isCanceled()) { + // The task was canceled, so remove the task fragment + getFragmentManager().beginTransaction() + .remove(taskFragment) + .commit(); + + onReturnToCanceledTask(); + + setupUiForTaskState(false); + } else { + taskFragment.setCallback(this::handleTaskResult); + + setupUiForTaskState(true); + } + } else { + setupUiForTaskState(false); + } + } + + @Nullable + @SuppressWarnings("unchecked") + private TaskFragment findTaskFragment() { + Fragment fragment = getFragmentManager().findFragmentByTag(TAG_TASK_FRAGMENT); + + if (fragment instanceof TaskFragment) + return (TaskFragment) fragment; + else + return null; + } + + private class ProcessLifecycleObserver implements DefaultLifecycleObserver { + @Override + public void onStop(@NonNull LifecycleOwner owner) { + if (cancelTaskOnScreenOff()) + cancelBackgroundTask(); + } + } + + public static class TaskFragment extends Fragment { + private UiBasedBackgroundTask task; + + public TaskFragment() { + super(); + setRetainInstance(true); + } + + public void startTask(@NonNull UiBasedBackgroundTask task) { + this.task = task; + task.execute(); + } + + public void setCallback(@Nullable UiBasedBackgroundTask.UiCallback callback) { + if (this.task != null) + this.task.setCallback(callback); + } + + public boolean isCanceled() { + if (task != null) + return task.isCanceled(); + else + return true; + } + + public void cancelTask() { + if (task != null) + task.cancel(); + } + } +} diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Activities/BackupActivity.java b/app/src/main/java/org/shadowice/flocke/andotp/Activities/BackupActivity.java index c712227a..ae6aefc7 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Activities/BackupActivity.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Activities/BackupActivity.java @@ -23,7 +23,6 @@ package org.shadowice.flocke.andotp.Activities; import android.app.AlertDialog; -import android.app.Fragment; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -31,8 +30,6 @@ import android.content.IntentSender; import android.net.Uri; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; import android.text.TextUtils; @@ -56,10 +53,9 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection; import org.shadowice.flocke.andotp.Database.Entry; import org.shadowice.flocke.andotp.Dialogs.PasswordEntryDialog; import org.shadowice.flocke.andotp.R; +import org.shadowice.flocke.andotp.Tasks.BackupTaskResult; import org.shadowice.flocke.andotp.Tasks.EncryptedBackupTask; import org.shadowice.flocke.andotp.Tasks.EncryptedRestoreTask; -import org.shadowice.flocke.andotp.Tasks.GenericBackupTask; -import org.shadowice.flocke.andotp.Tasks.GenericRestoreTask; import org.shadowice.flocke.andotp.Tasks.PGPBackupTask; import org.shadowice.flocke.andotp.Tasks.PGPRestoreTask; import org.shadowice.flocke.andotp.Tasks.PlainTextBackupTask; @@ -79,11 +75,9 @@ import java.util.ArrayList; import javax.crypto.SecretKey; -public class BackupActivity extends BaseActivity { +public class BackupActivity extends BackgroundTaskActivity { private final static String TAG = BackupActivity.class.getSimpleName(); - private static final String TAG_TASK_FRAGMENT = "BackupActivity.TaskFragment"; - private Constants.BackupType backupType = Constants.BackupType.ENCRYPTED; private SecretKey encryptionKey = null; @@ -105,6 +99,8 @@ public class BackupActivity extends BaseActivity { private boolean reload = false; private boolean allowExit = true; + private BackupTaskResult.ResultType currentTask = BackupTaskResult.ResultType.NONE; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -276,29 +272,28 @@ public class BackupActivity extends BaseActivity { Toast.makeText(this, msgId, Toast.LENGTH_LONG).show(); } - private void handleBackupTaskResult(GenericBackupTask.BackupTaskResult result) { - showBackupProgress(false); + @Override + void onTaskResult(BackupTaskResult backupTaskResult) { + if (backupTaskResult.resultType == BackupTaskResult.ResultType.BACKUP) + handleBackupTaskResult(backupTaskResult); + else if (backupTaskResult.resultType == BackupTaskResult.ResultType.RESTORE) + handleRestoreTaskResult(backupTaskResult); + } + private void handleBackupTaskResult(BackupTaskResult result) { if (result.messageId != 0) notifyBackupState(result.messageId); else - if (!result.success) + if (result.success) + notifyBackupState(R.string.backup_toast_export_success); + else notifyBackupState(R.string.backup_toast_export_failed); - // Clean up the task fragment - TaskFragment taskFragment = findTaskFragment(); - - if (taskFragment != null && taskFragment.isEmpty()) { - getFragmentManager().beginTransaction() - .remove(taskFragment) - .commit(); - } - if (result.success) finishWithResult(); } - private void handleRestoreTaskResult(GenericRestoreTask.RestoreTaskResult result) { + private void handleRestoreTaskResult(BackupTaskResult result) { if (result.success) { if (result.isPGP) { InputStream is = new ByteArrayInputStream(result.payload.getBytes(StandardCharsets.UTF_8)); @@ -318,20 +313,23 @@ public class BackupActivity extends BaseActivity { notifyBackupState(R.string.backup_toast_import_failed); } - showRestoreProgress(false); - - // Clean up the task fragment - TaskFragment taskFragment = findTaskFragment(); - if (taskFragment != null && taskFragment.isEmpty()) { - getFragmentManager().beginTransaction() - .remove(taskFragment) - .commit(); - } - if (result.success && !result.isPGP) finishWithResult(); } + @Override + protected void setupUiForTaskState(boolean running) { + if (currentTask == BackupTaskResult.ResultType.BACKUP) + showBackupProgress(running); + else if (currentTask == BackupTaskResult.ResultType.RESTORE) + showRestoreProgress(running); + } + + protected void startBackgroundTask(UiBasedBackgroundTask task, BackupTaskResult.ResultType type) { + currentTask = type; + startBackgroundTask(task); + } + private void toggleInProgressMode(boolean running) { allowExit = !running; @@ -465,9 +463,7 @@ public class BackupActivity extends BaseActivity { private void doRestorePlain(Uri uri) { if (Tools.isExternalStorageReadable()) { PlainTextRestoreTask task = new PlainTextRestoreTask(this, uri); - task.setCallback(this::handleRestoreTaskResult); - - startRestoreTask(task); + startBackgroundTask(task, BackupTaskResult.ResultType.RESTORE); } else { Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show(); } @@ -478,9 +474,7 @@ public class BackupActivity extends BaseActivity { ArrayList entries = DatabaseHelper.loadDatabase(this, encryptionKey); PlainTextBackupTask task = new PlainTextBackupTask(this, entries, uri); - task.setCallback(this::handleBackupTaskResult); - - startBackupTask(task); + startBackgroundTask(task, BackupTaskResult.ResultType.BACKUP); } else { Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show(); } @@ -514,9 +508,7 @@ public class BackupActivity extends BaseActivity { private void doRestoreCryptWithPassword(Uri uri, String password, boolean old_format) { if (Tools.isExternalStorageReadable()) { EncryptedRestoreTask task = new EncryptedRestoreTask(this, uri, password, old_format); - task.setCallback(this::handleRestoreTaskResult); - - startRestoreTask(task); + startBackgroundTask(task, BackupTaskResult.ResultType.RESTORE); } else { Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show(); } @@ -538,9 +530,7 @@ public class BackupActivity extends BaseActivity { ArrayList entries = DatabaseHelper.loadDatabase(this, encryptionKey); EncryptedBackupTask task = new EncryptedBackupTask(this, entries, password, uri); - task.setCallback(this::handleBackupTaskResult); - - startBackupTask(task); + startBackgroundTask(task, BackupTaskResult.ResultType.BACKUP); } else { Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show(); } @@ -553,17 +543,13 @@ public class BackupActivity extends BaseActivity { decryptIntent = new Intent(OpenPgpApi.ACTION_DECRYPT_VERIFY); PGPRestoreTask task = new PGPRestoreTask(this, uri, decryptIntent); - task.setCallback(this::handleRestoreTaskResult); - - startRestoreTask(task); + startBackgroundTask(task, BackupTaskResult.ResultType.RESTORE); } private void doBackupEncrypted(Uri uri, String data) { if (Tools.isExternalStorageWritable()) { PGPBackupTask task = new PGPBackupTask(this, data, uri); - task.setCallback(this::handleBackupTaskResult); - - startBackupTask(task); + startBackgroundTask(task, BackupTaskResult.ResultType.BACKUP); } else { Toast.makeText(this, R.string.backup_toast_storage_not_accessible, Toast.LENGTH_LONG).show(); } @@ -645,127 +631,8 @@ public class BackupActivity extends BaseActivity { } } - @Nullable - private TaskFragment findTaskFragment() { - return (TaskFragment) getFragmentManager().findFragmentByTag(TAG_TASK_FRAGMENT); - } - - private void startBackupTask(GenericBackupTask task) { - TaskFragment taskFragment = findTaskFragment(); - - // Don't start a task if we already have an active task running (backup or restore). - if (taskFragment == null || taskFragment.isEmpty()) { - if (taskFragment == null) { - taskFragment = new TaskFragment(); - getFragmentManager() - .beginTransaction() - .add(taskFragment, TAG_TASK_FRAGMENT) - .commit(); - } - - taskFragment.startBackupTask(task); - showBackupProgress(true); - } - } - - private void startRestoreTask(GenericRestoreTask task) { - TaskFragment taskFragment = findTaskFragment(); - - // Don't start a task if we already have an active task running (backup or restore). - if (taskFragment == null || taskFragment.isEmpty()) { - if (taskFragment == null) { - taskFragment = new TaskFragment(); - getFragmentManager() - .beginTransaction() - .add(taskFragment, TAG_TASK_FRAGMENT) - .commit(); - } - - taskFragment.startRestoreTask(task); - showRestoreProgress(true); - } - } - - private void checkBackgroundTask() { - TaskFragment taskFragment = findTaskFragment(); - - if (taskFragment != null) { - if (taskFragment.isEmpty()) { - // The task was canceled or has finished, so remove the task fragment. - getFragmentManager().beginTransaction() - .remove(taskFragment) - .commit(); - } else { - if (taskFragment.isRestoreRunning()) - showRestoreProgress(true); - - if (taskFragment.isBackupRunning()) - showBackupProgress(true); - - taskFragment.setCallbacks(this::handleBackupTaskResult, this::handleRestoreTaskResult); - } - } - } - - @Override - protected void onPause() { - super.onPause(); - - // We don't want the task to callback to a dead activity and cause a memory leak, so null it here. - TaskFragment taskFragment = findTaskFragment(); - - if (taskFragment != null) - taskFragment.setCallbacks(null, null); - } - - @Override - public void onResume() { - super.onResume(); - checkBackgroundTask(); - } - @Override protected boolean shouldDestroyOnScreenOff() { return allowExit; // Don't destroy the backup activity as long as a backup task is running } - - public static class TaskFragment extends Fragment { - GenericBackupTask backupTask; - GenericRestoreTask restoreTask; - - public TaskFragment() { - super(); - setRetainInstance(true); - } - - public void setCallbacks(UiBasedBackgroundTask.UiCallback backupCallback, UiBasedBackgroundTask.UiCallback restoreCallback) { - if (backupTask != null && backupTask.isRunning()) - backupTask.setCallback(backupCallback); - - if (restoreTask != null && restoreTask.isRunning()) - restoreTask.setCallback(restoreCallback); - } - - public boolean isEmpty() { - return ((backupTask == null || !backupTask.isRunning()) && (restoreTask == null || !restoreTask.isRunning())); - } - - public boolean isRestoreRunning() { - return (restoreTask != null && restoreTask.isRunning()); - } - - public boolean isBackupRunning() { - return (backupTask != null && backupTask.isRunning()); - } - - public void startBackupTask(@NonNull GenericBackupTask task) { - this.backupTask = task; - this.backupTask.execute(); - } - - public void startRestoreTask(@NonNull GenericRestoreTask task) { - this.restoreTask = task; - this.restoreTask.execute(); - } - } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Activities/SettingsActivity.java b/app/src/main/java/org/shadowice/flocke/andotp/Activities/SettingsActivity.java index 3c70f637..42ae53e9 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Activities/SettingsActivity.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Activities/SettingsActivity.java @@ -22,7 +22,6 @@ package org.shadowice.flocke.andotp.Activities; -import android.app.Fragment; import android.app.backup.BackupManager; import android.app.AlertDialog; import android.content.Intent; @@ -39,13 +38,14 @@ import android.preference.PreferenceFragment; import android.preference.PreferenceManager; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; import android.provider.DocumentsContract; import android.util.Log; import android.view.ViewStub; -import android.widget.Toast; + +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; import org.openintents.openpgp.util.OpenPgpAppPreference; import org.openintents.openpgp.util.OpenPgpKeyPreference; @@ -67,18 +67,16 @@ import javax.crypto.SecretKey; import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod; import static org.shadowice.flocke.andotp.Utilities.Constants.EncryptionType; -public class SettingsActivity extends BaseActivity +public class SettingsActivity extends BackgroundTaskActivity implements SharedPreferences.OnSharedPreferenceChangeListener, EncryptionHelper.EncryptionChangeCallback { - private static final String TAG_TASK_FRAGMENT = "SettingsActivity.TaskFragmentTag"; - private SettingsFragment fragment; private SharedPreferences prefs; private SecretKey encryptionKey = null; private boolean encryptionChanged = false; - private Toast inProgress = null; + private Snackbar inProgress = null; private boolean canGoBack = true; @Override @@ -189,7 +187,7 @@ public class SettingsActivity extends BaseActivity (settings.getAndroidBackupServiceEnabled() ? "enabled" : "disabled")); int message = settings.getAndroidBackupServiceEnabled() ? R.string.settings_toast_android_sync_enabled : R.string.settings_toast_android_sync_disabled; - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + Snackbar.make(fragment.getView(), message, BaseTransientBottomBar.LENGTH_SHORT).show(); } fragment.updateAutoBackup(); @@ -218,106 +216,43 @@ public class SettingsActivity extends BaseActivity fragment.setEncryptionKey(newEncryptionKey); } - private void handleTaskResult(ChangeEncryptionTask.Result result) { - setupUiForTaskState(false); - + @Override + void onTaskResult(ChangeEncryptionTask.Result result) { switch (result.result) { case SUCCESS: onSuccessfulEncryptionChange(result.encryptionType, result.newEncryptionKey); - Toast.makeText(this, R.string.settings_toast_encryption_change_success, Toast.LENGTH_LONG).show(); + Snackbar.make(fragment.getView(), R.string.settings_toast_encryption_change_success, BaseTransientBottomBar.LENGTH_SHORT).show(); break; case BACKUP_FAILURE: - Toast.makeText(this, R.string.settings_toast_encryption_backup_failed, Toast.LENGTH_LONG).show(); + Snackbar.make(fragment.getView(), R.string.settings_toast_encryption_backup_failed, BaseTransientBottomBar.LENGTH_SHORT).show(); break; case CHANGE_FAILURE: - Toast.makeText(this, R.string.settings_toast_encryption_change_failed, Toast.LENGTH_LONG).show(); + Snackbar.make(fragment.getView(), R.string.settings_toast_encryption_change_failed, BaseTransientBottomBar.LENGTH_SHORT).show(); break; } - - // Remove the task fragment after the task is finished - TaskFragment taskFragment = findTaskFragment(); - if (taskFragment != null) { - getFragmentManager().beginTransaction() - .remove(taskFragment) - .commit(); - } - } - - @Nullable - private SettingsActivity.TaskFragment findTaskFragment() { - return (SettingsActivity.TaskFragment) getFragmentManager().findFragmentByTag(TAG_TASK_FRAGMENT); } private void startEncryptionChangeTask(EncryptionType newEnc, byte[] newKey) { - SettingsActivity.TaskFragment taskFragment = findTaskFragment(); - // Don't start a task if we already have an active task running. - if (taskFragment == null || taskFragment.task.isCanceled()) { - ChangeEncryptionTask task = new ChangeEncryptionTask(this, encryptionKey, newEnc, newKey); - task.setCallback(this::handleTaskResult); - - if (taskFragment == null) { - taskFragment = new SettingsActivity.TaskFragment(); - getFragmentManager() - .beginTransaction() - .add(taskFragment, TAG_TASK_FRAGMENT) - .commit(); - } - - taskFragment.startTask(task); - setupUiForTaskState(true); - } + ChangeEncryptionTask task = new ChangeEncryptionTask(this, encryptionKey, newEnc, newKey); + startBackgroundTask(task); } - private void checkBackgroundTask() { - SettingsActivity.TaskFragment taskFragment = findTaskFragment(); - - if (taskFragment != null) { - if (taskFragment.task.isCanceled()) { - // The task was canceled, so remove the task fragment - getFragmentManager().beginTransaction() - .remove(taskFragment) - .commit(); - setupUiForTaskState(false); - } else { - taskFragment.task.setCallback(this::handleTaskResult); - setupUiForTaskState(true); - } - } else { - setupUiForTaskState(false); - } - } - - private void setupUiForTaskState(boolean taskRunning) { + @Override + protected void setupUiForTaskState(boolean taskRunning) { if (taskRunning) { - canGoBack = false; - // TODO: Better in-progress notification - inProgress = Toast.makeText(this, R.string.settings_toast_encryption_changing, Toast.LENGTH_LONG); + inProgress = Snackbar.make(fragment.getView(), R.string.settings_toast_encryption_changing, BaseTransientBottomBar.LENGTH_INDEFINITE); inProgress.show(); + + canGoBack = false; } else { - canGoBack = true; - if (inProgress != null) - inProgress.cancel(); + inProgress.dismiss(); + + canGoBack = true; } } - @Override - public void onResume() { - super.onResume(); - checkBackgroundTask(); - } - - @Override - protected void onPause() { - super.onPause(); - // We don't want the task to callback to a dead activity and cause a memory leak, so null it here. - SettingsActivity.TaskFragment taskFragment = findTaskFragment(); - - if (taskFragment != null) - taskFragment.task.setCallback(null); - } - private void requestBackupAccess() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -344,10 +279,10 @@ public class SettingsActivity extends BaseActivity EncryptionType newEncType = EncryptionType.valueOf(newEnc); startEncryptionChangeTask(newEncType, authKey); } else { - Toast.makeText(this, R.string.settings_toast_encryption_no_key, Toast.LENGTH_LONG).show(); + Snackbar.make(fragment.getView(), R.string.settings_toast_encryption_no_key, BaseTransientBottomBar.LENGTH_SHORT).show(); } } else { - Toast.makeText(this, R.string.settings_toast_encryption_auth_failed, Toast.LENGTH_LONG).show(); + Snackbar.make(fragment.getView(), R.string.settings_toast_encryption_auth_failed, BaseTransientBottomBar.LENGTH_SHORT).show(); } } else if (requestCode == Constants.INTENT_SETTINGS_BACKUP_LOCATION && resultCode == RESULT_OK) { Uri treeUri = data.getData(); @@ -573,19 +508,4 @@ public class SettingsActivity extends BaseActivity } } } - - /** Retained instance fragment to hold a running {@link ChangeEncryptionTask} between configuration changes.*/ - public static class TaskFragment extends Fragment { - ChangeEncryptionTask task; - - public TaskFragment() { - super(); - setRetainInstance(true); - } - - public void startTask(@NonNull ChangeEncryptionTask task) { - this.task = task; - task.execute(); - } - } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Receivers/EncryptedBackupBroadcastReceiver.java b/app/src/main/java/org/shadowice/flocke/andotp/Receivers/EncryptedBackupBroadcastReceiver.java index b413aa86..4c71ed8c 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Receivers/EncryptedBackupBroadcastReceiver.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Receivers/EncryptedBackupBroadcastReceiver.java @@ -28,8 +28,8 @@ import android.content.Intent; import org.shadowice.flocke.andotp.Database.Entry; import org.shadowice.flocke.andotp.R; +import org.shadowice.flocke.andotp.Tasks.BackupTaskResult; import org.shadowice.flocke.andotp.Tasks.EncryptedBackupTask; -import org.shadowice.flocke.andotp.Tasks.GenericBackupTask; import org.shadowice.flocke.andotp.Utilities.Constants; import org.shadowice.flocke.andotp.Utilities.DatabaseHelper; import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper; @@ -81,9 +81,9 @@ public class EncryptedBackupBroadcastReceiver extends BackupBroadcastReceiver { task.execute(); } - private void handleTaskResult(GenericBackupTask.BackupTaskResult result) { + private void handleTaskResult(BackupTaskResult result) { if (result.success) { - NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_SUCCESS, R.string.backup_receiver_title_backup_success, result.fileName); + NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_SUCCESS, R.string.backup_receiver_title_backup_success, result.payload); } else { NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, result.messageId); } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Receivers/PlainTextBackupBroadcastReceiver.java b/app/src/main/java/org/shadowice/flocke/andotp/Receivers/PlainTextBackupBroadcastReceiver.java index bcb158f7..ecfc529a 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Receivers/PlainTextBackupBroadcastReceiver.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Receivers/PlainTextBackupBroadcastReceiver.java @@ -28,7 +28,7 @@ import android.content.Intent; import org.shadowice.flocke.andotp.Database.Entry; import org.shadowice.flocke.andotp.R; -import org.shadowice.flocke.andotp.Tasks.GenericBackupTask; +import org.shadowice.flocke.andotp.Tasks.BackupTaskResult; import org.shadowice.flocke.andotp.Tasks.PlainTextBackupTask; import org.shadowice.flocke.andotp.Utilities.Constants; import org.shadowice.flocke.andotp.Utilities.DatabaseHelper; @@ -74,9 +74,9 @@ public class PlainTextBackupBroadcastReceiver extends BackupBroadcastReceiver { task.execute(); } - private void handleTaskResult(GenericBackupTask.BackupTaskResult result) { + private void handleTaskResult(BackupTaskResult result) { if (result.success) { - NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_SUCCESS, R.string.backup_receiver_title_backup_success, result.fileName); + NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_SUCCESS, R.string.backup_receiver_title_backup_success, result.payload); } else { NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, result.messageId); } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Tasks/BackupTaskResult.java b/app/src/main/java/org/shadowice/flocke/andotp/Tasks/BackupTaskResult.java new file mode 100644 index 00000000..d2310d36 --- /dev/null +++ b/app/src/main/java/org/shadowice/flocke/andotp/Tasks/BackupTaskResult.java @@ -0,0 +1,47 @@ +package org.shadowice.flocke.andotp.Tasks; + +import android.content.Intent; +import android.net.Uri; + +import org.shadowice.flocke.andotp.R; + +public class BackupTaskResult { + public final boolean success; + public final String payload; + public final int messageId; + + public boolean isPGP = false; + public Intent decryptIntent = null; + public Uri uri = null; + + public final ResultType resultType; + + public enum ResultType { + NONE, BACKUP, RESTORE + } + + public BackupTaskResult(ResultType type, boolean success, String payload, int messageId) { + this.resultType = type; + this.success = success; + this.payload = payload; + this.messageId = messageId; + } + + public BackupTaskResult(ResultType type, boolean success, String payload, int messageId, boolean isPGP, Intent decryptIntent, Uri uri) { + this.resultType = type; + this.success = success; + this.payload = payload; + this.messageId = messageId; + this.isPGP = isPGP; + this.decryptIntent = decryptIntent; + this.uri = uri; + } + + public static BackupTaskResult success(ResultType type, String payload) { + return new BackupTaskResult(type, true, payload, R.string.backup_toast_export_success); + } + + public static BackupTaskResult failure(ResultType type, int messageId) { + return new BackupTaskResult(type, false, null, messageId); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Tasks/EncryptedRestoreTask.java b/app/src/main/java/org/shadowice/flocke/andotp/Tasks/EncryptedRestoreTask.java index bcd947e1..f356261f 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Tasks/EncryptedRestoreTask.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Tasks/EncryptedRestoreTask.java @@ -28,7 +28,7 @@ public class EncryptedRestoreTask extends GenericRestoreTask { @Override @NonNull - protected RestoreTaskResult doInBackground() { + protected BackupTaskResult doInBackground() { boolean success = true; String decryptedString = ""; @@ -58,9 +58,9 @@ public class EncryptedRestoreTask extends GenericRestoreTask { } if (success) { - return RestoreTaskResult.success(decryptedString); + return BackupTaskResult.success(BackupTaskResult.ResultType.RESTORE, decryptedString); } else { - return RestoreTaskResult.failure(R.string.backup_toast_import_decryption_failed); + return BackupTaskResult.failure(BackupTaskResult.ResultType.RESTORE, R.string.backup_toast_import_decryption_failed); } } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Tasks/GenericBackupTask.java b/app/src/main/java/org/shadowice/flocke/andotp/Tasks/GenericBackupTask.java index 59c9781b..a29a09f1 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Tasks/GenericBackupTask.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Tasks/GenericBackupTask.java @@ -12,14 +12,14 @@ import org.shadowice.flocke.andotp.Utilities.Constants; import org.shadowice.flocke.andotp.Utilities.Settings; import org.shadowice.flocke.andotp.Utilities.StorageAccessHelper; -public abstract class GenericBackupTask extends UiBasedBackgroundTask { +public abstract class GenericBackupTask extends UiBasedBackgroundTask { protected final Context applicationContext; protected final Settings settings; protected final Constants.BackupType type; protected Uri uri; public GenericBackupTask(Context context, @Nullable Uri uri) { - super(BackupTaskResult.failure()); + super(BackupTaskResult.failure(BackupTaskResult.ResultType.BACKUP, R.string.backup_toast_export_failed)); this.applicationContext = context.getApplicationContext(); this.settings = new Settings(applicationContext); @@ -37,7 +37,7 @@ public abstract class GenericBackupTask extends UiBasedBackgroundTask { +public abstract class GenericRestoreTask extends UiBasedBackgroundTask { protected final Context applicationContext; protected final Settings settings; protected Uri uri; public GenericRestoreTask(Context context, @Nullable Uri uri) { - super(GenericRestoreTask.RestoreTaskResult.failure(R.string.backup_toast_import_failed)); + super(BackupTaskResult.failure(BackupTaskResult.ResultType.RESTORE, R.string.backup_toast_import_failed)); this.applicationContext = context.getApplicationContext(); this.settings = new Settings(applicationContext); @@ -26,38 +25,5 @@ public abstract class GenericRestoreTask extends UiBasedBackgroundTask { private Result awaitedResult; private volatile boolean isCanceled = false; - private volatile boolean isRunning = false; /** @param failedResult The result to return if the task fails (throws an exception or returns null). */ public UiBasedBackgroundTask(@NonNull Result failedResult) { @@ -53,10 +52,7 @@ public abstract class UiBasedBackgroundTask { } private void emitResultOnMainThread(@NonNull UiCallback callback, @NonNull Result result) { - mainThreadHandler.post(() -> { - callback.onResult(result); - isRunning = false; - }); + mainThreadHandler.post(() -> callback.onResult(result)); this.callback = null; this.awaitedResult = null; } @@ -64,7 +60,6 @@ public abstract class UiBasedBackgroundTask { /** Executed the task on a background thread. Safe to call from the main thread. */ @AnyThread public void execute() { - isRunning = true; executor.execute(this::runTask); } @@ -101,15 +96,9 @@ public abstract class UiBasedBackgroundTask { return isCanceled; } - @AnyThread - public boolean isRunning() { - return isRunning; - } - @AnyThread public void cancel() { isCanceled = true; - isRunning = false; } @FunctionalInterface diff --git a/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java b/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java index 52ce4ff9..6afff91c 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/View/EntriesCardAdapter.java @@ -63,8 +63,8 @@ import org.shadowice.flocke.andotp.Database.Entry; import org.shadowice.flocke.andotp.Database.EntryList; import org.shadowice.flocke.andotp.Dialogs.ManualEntryDialog; import org.shadowice.flocke.andotp.R; +import org.shadowice.flocke.andotp.Tasks.BackupTaskResult; import org.shadowice.flocke.andotp.Tasks.EncryptedBackupTask; -import org.shadowice.flocke.andotp.Tasks.GenericBackupTask; import org.shadowice.flocke.andotp.Utilities.BackupHelper; import org.shadowice.flocke.andotp.Utilities.Constants; import org.shadowice.flocke.andotp.Utilities.DatabaseHelper; @@ -198,7 +198,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter } } - private void handleTaskResult(GenericBackupTask.BackupTaskResult result) { + private void handleTaskResult(BackupTaskResult result) { if (result.success) { Toast.makeText(context, R.string.backup_toast_export_success, Toast.LENGTH_LONG).show(); } else { @@ -270,9 +270,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter Constants.CardLayouts layout = settings.getCardLayout(); - if (layout == Constants.CardLayouts.DEFAULT) { - cardLayout = R.layout.component_card_default; - } else if (layout == Constants.CardLayouts.COMPACT) { + if (layout == Constants.CardLayouts.COMPACT) { cardLayout = R.layout.component_card_compact; } else if (layout == Constants.CardLayouts.FULL) { cardLayout = R.layout.component_card_full; @@ -371,7 +369,7 @@ public class EntriesCardAdapter extends RecyclerView.Adapter private void copyHandler(final int position, final String text, final boolean dropToBackground) { Tools.copyToClipboard(context, text); updateLastUsedAndFrequency(position, getRealIndex(position)); - if(context != null && dropToBackground) { + if (dropToBackground) { ((MainActivity)context).moveTaskToBack(true); } }