#635 - Make the AuthenticationTask a UiBackedBackgroundTask, and make sure that AuthenticateActivity can receive results across configuration changes
Begin storing the task in a retained fragment so that if the task is begun, we will be able to get a callback when the activity is destroyed/recreated.
This commit is contained in:
parent
f4a5f1ec5d
commit
b52accb2aa
2 changed files with 75 additions and 52 deletions
|
@ -22,16 +22,18 @@
|
|||
|
||||
package org.shadowice.flocke.andotp.Activities;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.text.InputType;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewStub;
|
||||
|
@ -48,28 +50,30 @@ import org.shadowice.flocke.andotp.Tasks.AuthenticationTask;
|
|||
import org.shadowice.flocke.andotp.Tasks.AuthenticationTask.Result;
|
||||
import org.shadowice.flocke.andotp.Utilities.Constants;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod;
|
||||
|
||||
public class AuthenticateActivity extends ThemedActivity
|
||||
implements EditText.OnEditorActionListener, View.OnClickListener {
|
||||
|
||||
private static final String TAG_TASK_FRAGMENT = "AuthenticateActivity.TaskFragmentTag";
|
||||
|
||||
private AuthMethod authMethod;
|
||||
private String newEncryption = "";
|
||||
private String existingAuthCredentials;
|
||||
private boolean isAuthUpgrade = false;
|
||||
|
||||
private boolean isTaskRunning = false;
|
||||
|
||||
private TextInputLayout passwordLayout;
|
||||
private TextInputEditText passwordInput;
|
||||
private Button unlockButton;
|
||||
private ProgressBar unlockProgress;
|
||||
|
||||
private AuthenticationTask activeTask;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
checkBackgroundTask();
|
||||
|
||||
if (! settings.getScreenshotsEnabled())
|
||||
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
||||
|
||||
|
@ -97,10 +101,28 @@ public class AuthenticateActivity extends ThemedActivity
|
|||
setContentView(R.layout.activity_container);
|
||||
initToolbar();
|
||||
initPasswordViews();
|
||||
if (isTaskRunning) {
|
||||
setupUiForRunningTask();
|
||||
}
|
||||
|
||||
getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
}
|
||||
|
||||
private void checkBackgroundTask() {
|
||||
TaskFragment taskFragment = findTaskFragment();
|
||||
if (taskFragment != null) {
|
||||
// If we have the task fragment in our fragment manager, we know that we started the
|
||||
// task before a configuration change and need to set a new callback.
|
||||
isTaskRunning = true;
|
||||
taskFragment.task.setCallback(this::handleResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TaskFragment findTaskFragment() {
|
||||
return (TaskFragment) getFragmentManager().findFragmentByTag(TAG_TASK_FRAGMENT);
|
||||
}
|
||||
|
||||
private void initToolbar() {
|
||||
Toolbar toolbar = findViewById(R.id.container_toolbar);
|
||||
toolbar.setNavigationIcon(null);
|
||||
|
@ -152,6 +174,15 @@ public class AuthenticateActivity extends ThemedActivity
|
|||
unlockProgress = v.findViewById(R.id.unlockProgress);
|
||||
}
|
||||
|
||||
private void setupUiForRunningTask() {
|
||||
isTaskRunning = true;
|
||||
passwordLayout.setEnabled(false);
|
||||
passwordInput.setEnabled(false);
|
||||
unlockButton.setEnabled(false);
|
||||
unlockButton.setVisibility(View.INVISIBLE);
|
||||
unlockProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startAuthTask(passwordInput.getText().toString());
|
||||
|
@ -167,25 +198,23 @@ public class AuthenticateActivity extends ThemedActivity
|
|||
}
|
||||
|
||||
private void startAuthTask(String plainPassword) {
|
||||
TaskFragment taskFragment = findTaskFragment();
|
||||
// Don't start another task if this was already started.
|
||||
if (activeTask != null) {
|
||||
return;
|
||||
if (taskFragment == null) {
|
||||
// Add a retained task fragment so that we will be able to know if a task was running on a configuration change.
|
||||
AuthenticationTask task = new AuthenticationTask(this, isAuthUpgrade, existingAuthCredentials, plainPassword);
|
||||
task.setCallback(this::handleResult);
|
||||
taskFragment = new TaskFragment();
|
||||
taskFragment.startTask(task);
|
||||
getFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(taskFragment, TAG_TASK_FRAGMENT)
|
||||
.commit();
|
||||
setupUiForRunningTask();
|
||||
}
|
||||
displayUnlockProgress();
|
||||
activeTask = new AuthenticationTask(this, this::handleResult, isAuthUpgrade, existingAuthCredentials, plainPassword);
|
||||
activeTask.execute();
|
||||
}
|
||||
|
||||
private void displayUnlockProgress() {
|
||||
passwordLayout.setEnabled(false);
|
||||
passwordInput.setEnabled(false);
|
||||
unlockButton.setEnabled(false);
|
||||
unlockButton.setVisibility(View.INVISIBLE);
|
||||
unlockProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void handleResult(Result result) {
|
||||
activeTask = null;
|
||||
if (result.authUpgradeFailed) {
|
||||
Toast.makeText(this, R.string.settings_toast_auth_upgrade_failed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
@ -206,29 +235,34 @@ public class AuthenticateActivity extends ThemedActivity
|
|||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
completeTaskIfActive();
|
||||
}
|
||||
|
||||
/** @return true if the task was active and was completed, false otherwise. */
|
||||
private boolean completeTaskIfActive() {
|
||||
try {
|
||||
// This will cause the main thread to lock, but ensures that our Activity result will be set.
|
||||
// This task shouldn't take more than 1-2 seconds to complete from starting.
|
||||
if (activeTask != null) {
|
||||
handleResult(activeTask.get());
|
||||
return true;
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e("AuthenticateActivity", "Could not finish active authentication task", e);
|
||||
// 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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!completeTaskIfActive()) {
|
||||
finishWithResult(false, null);
|
||||
}
|
||||
finishWithResult(false, null);
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
/** Retained instance fragment to hold a running @link{{@link AuthenticationTask}} between configuration changes.*/
|
||||
public static class TaskFragment extends Fragment {
|
||||
|
||||
AuthenticationTask task;
|
||||
|
||||
public TaskFragment() {
|
||||
super();
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
public void startTask(@NonNull AuthenticationTask task) {
|
||||
if (this.task == null) {
|
||||
this.task = task;
|
||||
task.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,19 +20,18 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class AuthenticationTask extends AsyncTask<Void, Void, Result> {
|
||||
public class AuthenticationTask extends UiBasedBackgroundTask<Result> {
|
||||
|
||||
private final Settings settings;
|
||||
private final Callback callback;
|
||||
|
||||
private final boolean isAuthUpgrade;
|
||||
private final String existingAuthCredentials;
|
||||
private final String plainPassword;
|
||||
|
||||
public AuthenticationTask(Context context, Callback callback, boolean isAuthUpgrade, String existingAuthCredentials, String plainPassword) {
|
||||
public AuthenticationTask(Context context, boolean isAuthUpgrade, String existingAuthCredentials, String plainPassword) {
|
||||
super(Result.failure());
|
||||
Context applicationContext = context.getApplicationContext();
|
||||
this.settings = new Settings(applicationContext);
|
||||
this.callback = callback;
|
||||
|
||||
this.isAuthUpgrade = isAuthUpgrade;
|
||||
this.existingAuthCredentials = existingAuthCredentials;
|
||||
|
@ -41,7 +40,7 @@ public class AuthenticationTask extends AsyncTask<Void, Void, Result> {
|
|||
|
||||
@Override
|
||||
@NonNull
|
||||
protected Result doInBackground(Void... ignore) {
|
||||
protected Result doInBackground() {
|
||||
if (isAuthUpgrade) {
|
||||
return upgradeAuthentication();
|
||||
} else {
|
||||
|
@ -85,16 +84,6 @@ public class AuthenticationTask extends AsyncTask<Void, Void, Result> {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Result result) {
|
||||
callback.onComplete(result);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Callback {
|
||||
void onComplete(Result result);
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
@Nullable
|
||||
public final byte[] encryptionKey;
|
||||
|
|
Loading…
Reference in a new issue