Store the running ChangeEncryptionTask in a fragment

This way it survives when the calling activity goes into the background
This commit is contained in:
Jakob Nixdorf 2021-03-25 18:17:08 +01:00
parent d8a1e03806
commit d95dac1784
No known key found for this signature in database
GPG key ID: BE99BF86574A7DBC
3 changed files with 125 additions and 18 deletions

View file

@ -22,6 +22,7 @@
package org.shadowice.flocke.andotp.Activities;
import android.app.Fragment;
import android.app.backup.BackupManager;
import android.app.AlertDialog;
import android.content.Intent;
@ -38,10 +39,12 @@ 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.MenuItem;
import android.view.ViewStub;
import android.widget.Toast;
@ -68,6 +71,8 @@ import static org.shadowice.flocke.andotp.Utilities.Constants.EncryptionType;
public class SettingsActivity extends BaseActivity
implements SharedPreferences.OnSharedPreferenceChangeListener, EncryptionHelper.EncryptionChangeCallback {
private static final String TAG_TASK_FRAGMENT = "SettingsActivity.TaskFragmentTag";
private SettingsFragment fragment;
private SharedPreferences prefs;
@ -75,6 +80,7 @@ public class SettingsActivity extends BaseActivity
private boolean encryptionChanged = false;
private Toast inProgress = null;
private boolean canGoBack = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -115,6 +121,14 @@ public class SettingsActivity extends BaseActivity
prefs.registerOnSharedPreferenceChangeListener(this);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home && !canGoBack)
return true;
return super.onOptionsItemSelected(item);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
@ -142,8 +156,10 @@ public class SettingsActivity extends BaseActivity
@Override
public void onBackPressed() {
finishWithResult();
super.onBackPressed();
if (canGoBack) {
finishWithResult();
super.onBackPressed();
}
}
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
@ -211,7 +227,7 @@ public class SettingsActivity extends BaseActivity
}
private void handleTaskResult(ChangeEncryptionTask.Result result) {
inProgress.cancel();
setupUiForTaskState(false);
switch (result.result) {
case SUCCESS:
@ -225,17 +241,89 @@ public class SettingsActivity extends BaseActivity
Toast.makeText(this, R.string.settings_toast_encryption_change_failed, Toast.LENGTH_LONG).show();
break;
}
// Remove the task fragment after the task is finished
TaskFragment taskFragment = findTaskFragment();
if (taskFragment != null) {
getFragmentManager().beginTransaction()
.remove(taskFragment)
.commit();
}
}
private void tryEncryptionChange(EncryptionType newEnc, byte[] newKey) {
ChangeEncryptionTask task = new ChangeEncryptionTask(this, encryptionKey, newEnc, newKey);
task.setCallback(this::handleTaskResult);
@Nullable
private SettingsActivity.TaskFragment findTaskFragment() {
return (SettingsActivity.TaskFragment) getFragmentManager().findFragmentByTag(TAG_TASK_FRAGMENT);
}
// TODO: Better in-progress notification
inProgress = Toast.makeText(this, R.string.settings_toast_encryption_changing, Toast.LENGTH_LONG);
inProgress.show();
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);
task.execute();
if (taskFragment == null) {
taskFragment = new SettingsActivity.TaskFragment();
getFragmentManager()
.beginTransaction()
.add(taskFragment, TAG_TASK_FRAGMENT)
.commit();
}
taskFragment.startTask(task);
setupUiForTaskState(true);
}
}
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) {
if (taskRunning) {
canGoBack = false;
// TODO: Better in-progress notification
inProgress = Toast.makeText(this, R.string.settings_toast_encryption_changing, Toast.LENGTH_LONG);
inProgress.show();
} else {
canGoBack = true;
if (inProgress != null)
inProgress.cancel();
}
}
@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() {
@ -262,7 +350,7 @@ public class SettingsActivity extends BaseActivity
if (authKey != null && authKey.length > 0 && newEnc != null && !newEnc.isEmpty()) {
EncryptionType newEncType = EncryptionType.valueOf(newEnc);
tryEncryptionChange(newEncType, authKey);
startEncryptionChangeTask(newEncType, authKey);
} else {
Toast.makeText(this, R.string.settings_toast_encryption_no_key, Toast.LENGTH_LONG).show();
}
@ -317,7 +405,7 @@ public class SettingsActivity extends BaseActivity
if (encryptionType == EncryptionType.PASSWORD)
((SettingsActivity) getActivity()).tryEncryptionChangeWithAuth(encryptionType);
else if (encryptionType == EncryptionType.KEYSTORE)
((SettingsActivity) getActivity()).tryEncryptionChange(encryptionType, null);
((SettingsActivity) getActivity()).startEncryptionChangeTask(encryptionType, null);
})
.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> {
})
@ -493,4 +581,19 @@ 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();
}
}
}

View file

@ -40,14 +40,14 @@ public abstract class UiBasedBackgroundTask<Result> {
public void setCallback(@Nullable UiCallback<Result> callback) {
synchronized (callbackLock) {
// Don't bother doing anything if the task was canceled.
if (isCanceled()) {
if (isCanceled())
return;
}
this.callback = callback;
// If we have an awaited result and are setting a new callback, publish the result immediately.
if (awaitedResult != null && callback != null) {
if (awaitedResult != null && callback != null)
emitResultOnMainThread(callback, awaitedResult);
}
}
}
@ -73,9 +73,9 @@ public abstract class UiBasedBackgroundTask<Result> {
synchronized (callbackLock) {
// Don't bother issuing callback or storing result if this task is canceled.
if (isCanceled()) {
if (isCanceled())
return;
}
if (callback != null) {
emitResultOnMainThread(callback, result);
} else {

View file

@ -90,6 +90,10 @@ public class EncryptionHelper {
}
if (DatabaseHelper.saveDatabase(context, entries, newEncryptionKey)) {
// Persist new encryption here already so everything is fully setup when the task finishes
Settings settings = new Settings(context);
settings.setEncryption(newType);
if (changeCallback != null)
changeCallback.onSuccessfulEncryptionChange(newType, newEncryptionKey);