Add RemoteLockscreenValidationFragment to help retain remote lockscreen am: 2eb8ed2488

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/22390454

Change-Id: I58487aef2bc252448119e48c7a2f2bedc3a710b9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Brian Lee
2023-04-05 18:35:29 +00:00
committed by Automerger Merge Worker
6 changed files with 314 additions and 112 deletions

View File

@@ -17,6 +17,7 @@
// TODO (b/35202196): move this class out of the root of the package. // TODO (b/35202196): move this class out of the root of the package.
package com.android.settings.password; package com.android.settings.password;
import static android.app.Activity.RESULT_FIRST_USER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED; import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED;
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
@@ -24,7 +25,6 @@ import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Dialog; import android.app.Dialog;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.app.RemoteLockscreenValidationResult;
import android.app.RemoteLockscreenValidationSession; import android.app.RemoteLockscreenValidationSession;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.admin.ManagedSubscriptionsPolicy; import android.app.admin.ManagedSubscriptionsPolicy;
@@ -38,7 +38,6 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient; import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
import android.telecom.TelecomManager; import android.telecom.TelecomManager;
import android.text.TextUtils; import android.text.TextUtils;
@@ -55,14 +54,11 @@ import androidx.fragment.app.FragmentManager;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.LockscreenCredential;
import com.android.security.SecureBox;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.InstrumentedFragment;
import java.security.InvalidKeyException; import com.google.android.setupdesign.GlifLayout;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
/** /**
* Base fragment to be shared for PIN/Pattern/Password confirmation fragments. * Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
@@ -89,9 +85,13 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
/** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */ /** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */
protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000; protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000;
protected static final String FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION =
"remote_lockscreen_validation";
protected boolean mReturnCredentials = false; protected boolean mReturnCredentials = false;
protected boolean mReturnGatekeeperPassword = false; protected boolean mReturnGatekeeperPassword = false;
protected boolean mForceVerifyPath = false; protected boolean mForceVerifyPath = false;
protected GlifLayout mGlifLayout;
protected CheckBox mCheckBox; protected CheckBox mCheckBox;
protected Button mCancelButton; protected Button mCancelButton;
/** Button allowing managed profile password reset, null when is not shown. */ /** Button allowing managed profile password reset, null when is not shown. */
@@ -109,8 +109,8 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
protected BiometricManager mBiometricManager; protected BiometricManager mBiometricManager;
@Nullable protected RemoteLockscreenValidationSession mRemoteLockscreenValidationSession; @Nullable protected RemoteLockscreenValidationSession mRemoteLockscreenValidationSession;
/** Credential saved so the credential can be set for device if remote validation passes */ /** Credential saved so the credential can be set for device if remote validation passes */
@Nullable protected LockscreenCredential mDeviceCredentialGuess;
@Nullable protected RemoteLockscreenValidationClient mRemoteLockscreenValidationClient; @Nullable protected RemoteLockscreenValidationClient mRemoteLockscreenValidationClient;
protected RemoteLockscreenValidationFragment mRemoteLockscreenValidationFragment;
private boolean isInternalActivity() { private boolean isInternalActivity() {
return (getActivity() instanceof ConfirmLockPassword.InternalActivity) return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
@@ -136,8 +136,8 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION)) { FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION)) {
mRemoteValidation = true; mRemoteValidation = true;
} else { } else {
Log.e(TAG, "Remote device credential validation not enabled."); onRemoteLockscreenValidationFailure(
getActivity().finish(); "Remote lockscreen validation not enabled.");
} }
} }
if (mRemoteValidation) { if (mRemoteValidation) {
@@ -146,23 +146,31 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
RemoteLockscreenValidationSession.class); RemoteLockscreenValidationSession.class);
if (mRemoteLockscreenValidationSession == null if (mRemoteLockscreenValidationSession == null
|| mRemoteLockscreenValidationSession.getRemainingAttempts() == 0) { || mRemoteLockscreenValidationSession.getRemainingAttempts() == 0) {
Log.e(TAG, "RemoteLockscreenValidationSession is null or " onRemoteLockscreenValidationFailure("RemoteLockscreenValidationSession is null or "
+ "no more attempts for remote lockscreen validation."); + "no more attempts for remote lockscreen validation.");
getActivity().finish();
} }
ComponentName remoteLockscreenValidationServiceComponent = ComponentName remoteLockscreenValidationServiceComponent =
intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class); intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class);
if (remoteLockscreenValidationServiceComponent == null) { if (remoteLockscreenValidationServiceComponent == null) {
Log.e(TAG, "RemoteLockscreenValidationService ComponentName is null"); onRemoteLockscreenValidationFailure(
getActivity().finish(); "RemoteLockscreenValidationService ComponentName is null");
} }
mRemoteLockscreenValidationClient = RemoteLockscreenValidationClient mRemoteLockscreenValidationClient = RemoteLockscreenValidationClient
.create(getContext(), remoteLockscreenValidationServiceComponent); .create(getContext(), remoteLockscreenValidationServiceComponent);
if (!mRemoteLockscreenValidationClient.isServiceAvailable()) { if (!mRemoteLockscreenValidationClient.isServiceAvailable()) {
Log.e(TAG, String.format("RemoteLockscreenValidationService at %s is not available", onRemoteLockscreenValidationFailure(String.format(
"RemoteLockscreenValidationService at %s is not available",
remoteLockscreenValidationServiceComponent.getClassName())); remoteLockscreenValidationServiceComponent.getClassName()));
getActivity().finish(); }
mRemoteLockscreenValidationFragment =
(RemoteLockscreenValidationFragment) getFragmentManager()
.findFragmentByTag(FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION);
if (mRemoteLockscreenValidationFragment == null) {
mRemoteLockscreenValidationFragment = new RemoteLockscreenValidationFragment();
getFragmentManager().beginTransaction().add(mRemoteLockscreenValidationFragment,
FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION).commit();
} }
} }
@@ -194,8 +202,10 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
mCancelButton.setOnClickListener(v -> { mCancelButton.setOnClickListener(v -> {
if (hasAlternateButton) { if (hasAlternateButton) {
getActivity().setResult(KeyguardManager.RESULT_ALTERNATE); getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
getActivity().finish();
} else if (mRemoteValidation) {
onRemoteLockscreenValidationFailure("Forgot lockscreen credential button pressed.");
} }
getActivity().finish();
}); });
setupForgotButtonIfManagedProfile(view); setupForgotButtonIfManagedProfile(view);
@@ -299,17 +309,11 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
if (mRemoteLockscreenValidationClient != null) { if (mRemoteLockscreenValidationClient != null) {
mRemoteLockscreenValidationClient.disconnect(); mRemoteLockscreenValidationClient.disconnect();
} }
if (mDeviceCredentialGuess != null) {
mDeviceCredentialGuess.zeroize();
}
super.onDestroy(); super.onDestroy();
} }
protected abstract void authenticationSucceeded(); protected abstract void authenticationSucceeded();
protected abstract void onRemoteDeviceCredentialValidationResult(
RemoteLockscreenValidationResult result);
public void prepareEnterAnimation() { public void prepareEnterAnimation() {
} }
@@ -411,43 +415,33 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
} }
protected void validateGuess(LockscreenCredential credentialGuess) { protected void validateGuess(LockscreenCredential credentialGuess) {
if (mCheckBox.isChecked()) { mRemoteLockscreenValidationFragment.validateLockscreenGuess(
// Keep credential in memory since user wants to set guess as screen lock. mRemoteLockscreenValidationClient, credentialGuess,
mDeviceCredentialGuess = credentialGuess; mRemoteLockscreenValidationSession.getSourcePublicKey(), mCheckBox.isChecked());
} else if (mDeviceCredentialGuess != null) {
mDeviceCredentialGuess.zeroize();
}
mRemoteLockscreenValidationClient.validateLockscreenGuess(
encryptDeviceCredentialGuess(credentialGuess.getCredential()),
new IRemoteLockscreenValidationCallback.Stub() {
@Override
public void onSuccess(RemoteLockscreenValidationResult result) {
mHandler.post(()->onRemoteDeviceCredentialValidationResult(result));
}
@Override
public void onFailure(String message) {
Log.e(TAG, "A failure occurred while trying "
+ "to validate lockscreen guess: " + message);
mHandler.post(()->getActivity().finish());
}
});
} }
private byte[] encryptDeviceCredentialGuess(byte[] guess) { protected void updateRemoteLockscreenValidationViews() {
try { if (!mRemoteValidation || mRemoteLockscreenValidationFragment == null) {
byte[] encodedPublicKey = mRemoteLockscreenValidationSession.getSourcePublicKey(); return;
PublicKey publicKey = SecureBox.decodePublicKey(encodedPublicKey);
return SecureBox.encrypt(
publicKey,
/* sharedSecret= */ null,
LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER,
guess);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
Log.w(TAG, "Error encrypting device credential guess. Returning empty byte[].", e);
return new byte[0];
} }
boolean enable = mRemoteLockscreenValidationFragment.isRemoteValidationInProgress();
mGlifLayout.setProgressBarShown(enable);
mCheckBox.setEnabled(!enable);
mCancelButton.setEnabled(!enable);
}
/**
* Finishes the activity with result code {@link android.app.Activity#RESULT_FIRST_USER}
* after logging the error message.
* @param message Optional message to log.
*/
public void onRemoteLockscreenValidationFailure(String message) {
if (!TextUtils.isEmpty(message)) {
Log.w(TAG, message);
}
getActivity().setResult(RESULT_FIRST_USER);
getActivity().finish();
} }
protected abstract void onShowError(); protected abstract void onShowError();

View File

@@ -71,8 +71,6 @@ import com.android.settings.R;
import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils;
import com.google.android.setupdesign.GlifLayout;
import java.util.ArrayList; import java.util.ArrayList;
public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity { public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
@@ -127,7 +125,8 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
implements OnClickListener, OnEditorActionListener, implements OnClickListener, OnEditorActionListener,
CredentialCheckResultTracker.Listener, SaveChosenLockWorkerBase.Listener { CredentialCheckResultTracker.Listener, SaveChosenLockWorkerBase.Listener,
RemoteLockscreenValidationFragment.Listener {
private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result"; private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
private ImeAwareEditText mPasswordEntry; private ImeAwareEditText mPasswordEntry;
private TextViewInputDisabler mPasswordEntryInputDisabler; private TextViewInputDisabler mPasswordEntryInputDisabler;
@@ -140,7 +139,6 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
private AppearAnimationUtils mAppearAnimationUtils; private AppearAnimationUtils mAppearAnimationUtils;
private DisappearAnimationUtils mDisappearAnimationUtils; private DisappearAnimationUtils mDisappearAnimationUtils;
private boolean mIsManagedProfile; private boolean mIsManagedProfile;
private GlifLayout mGlifLayout;
private CharSequence mCheckBoxLabel; private CharSequence mCheckBoxLabel;
// required constructor for fragments // required constructor for fragments
@@ -255,6 +253,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
? R.string.lockpassword_forgot_password ? R.string.lockpassword_forgot_password
: R.string.lockpassword_forgot_pin); : R.string.lockpassword_forgot_pin);
} }
updateRemoteLockscreenValidationViews();
} }
if (mForgotButton != null) { if (mForgotButton != null) {
@@ -405,6 +404,9 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mCountdownTimer = null; mCountdownTimer = null;
} }
mCredentialCheckResultTracker.setListener(null); mCredentialCheckResultTracker.setListener(null);
if (mRemoteLockscreenValidationFragment != null) {
mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
}
} }
@Override @Override
@@ -426,6 +428,9 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
} }
mCredentialCheckResultTracker.setListener(this); mCredentialCheckResultTracker.setListener(this);
if (mRemoteLockscreenValidationFragment != null) {
mRemoteLockscreenValidationFragment.setListener(this, mHandler);
}
} }
@Override @Override
@@ -436,13 +441,17 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
private void updatePasswordEntry() { private void updatePasswordEntry() {
final boolean isLockedOut = final boolean isLockedOut =
mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0; mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0;
mPasswordEntry.setEnabled(!isLockedOut); final boolean isRemoteLockscreenValidationInProgress =
mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut); mRemoteLockscreenValidationFragment != null
if (isLockedOut) { && mRemoteLockscreenValidationFragment.isRemoteValidationInProgress();
mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/); boolean shouldEnableInput = !isLockedOut && !isRemoteLockscreenValidationInProgress;
} else { mPasswordEntry.setEnabled(shouldEnableInput);
mPasswordEntryInputDisabler.setInputEnabled(shouldEnableInput);
if (shouldEnableInput) {
mPasswordEntry.scheduleShowSoftInput(); mPasswordEntry.scheduleShowSoftInput();
mPasswordEntry.requestFocus(); mPasswordEntry.requestFocus();
} else {
mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), /* flags= */0);
} }
} }
@@ -472,7 +481,8 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
if (mRemoteValidation) { if (mRemoteValidation) {
validateGuess(credential); validateGuess(credential);
mGlifLayout.setProgressBarShown(true); updateRemoteLockscreenValidationViews();
updatePasswordEntry();
return; return;
} }
@@ -604,14 +614,15 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
} }
@Override @Override
protected void onRemoteDeviceCredentialValidationResult( public void onRemoteLockscreenValidationResult(
RemoteLockscreenValidationResult result) { RemoteLockscreenValidationResult result) {
switch (result.getResultCode()) { switch (result.getResultCode()) {
case RemoteLockscreenValidationResult.RESULT_GUESS_VALID: case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
if (mCheckBox.isChecked()) { if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
.getLockscreenCredential() != null) {
Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
ChooseLockPassword.SaveAndFinishWorker saveAndFinishWorker = ChooseLockPassword.SaveAndFinishWorker saveAndFinishWorker =
new ChooseLockPassword.SaveAndFinishWorker(); new ChooseLockPassword.SaveAndFinishWorker();
Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
getFragmentManager().beginTransaction().add(saveAndFinishWorker, null) getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
.commit(); .commit();
getFragmentManager().executePendingTransactions(); getFragmentManager().executePendingTransactions();
@@ -619,14 +630,14 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
saveAndFinishWorker.start( saveAndFinishWorker.start(
mLockPatternUtils, mLockPatternUtils,
/* requestGatekeeperPassword= */ true, /* requestGatekeeperPassword= */ true,
mDeviceCredentialGuess, mRemoteLockscreenValidationFragment.getLockscreenCredential(),
/* currentCredential= */ null, /* currentCredential= */ null,
mEffectiveUserId); mEffectiveUserId);
return; } else {
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
} }
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(), return;
/* timeoutMs= */ 0, mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID: case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(), mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId); /* timeoutMs= */ 0, mEffectiveUserId);
@@ -636,12 +647,15 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
(int) result.getTimeoutMillis(), mEffectiveUserId); (int) result.getTimeoutMillis(), mEffectiveUserId);
break; break;
case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS: case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
getActivity().finish();
break;
case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED: case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED:
getActivity().finish(); onRemoteLockscreenValidationFailure(String.format(
"Cannot continue remote lockscreen validation. ResultCode=%d",
result.getResultCode()));
break;
} }
mGlifLayout.setProgressBarShown(false); updateRemoteLockscreenValidationViews();
updatePasswordEntry();
mRemoteLockscreenValidationFragment.clearLockscreenCredential();
} }
@Override @Override
@@ -701,21 +715,18 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
} }
/** /**
* Callback for when the device credential guess used for remote validation was set as the * Callback for when the current device's lockscreen was set to the guess used for
* current device's device credential. * remote lockscreen validation.
*/ */
@Override @Override
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
if (mDeviceCredentialGuess != null) { Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
mDeviceCredentialGuess.zeroize(); mRemoteLockscreenValidationFragment.clearLockscreenCredential();
}
Intent result = new Intent(); Intent result = new Intent();
if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) { if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData)); result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
} }
mGlifLayout.setProgressBarShown(false);
mCredentialCheckResultTracker.setResult(/* matched= */ true, result, mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
/* timeoutMs= */ 0, mEffectiveUserId); /* timeoutMs= */ 0, mEffectiveUserId);
} }

View File

@@ -59,8 +59,6 @@ import com.android.settingslib.animation.AppearAnimationCreator;
import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils;
import com.google.android.setupdesign.GlifLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -97,7 +95,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener, implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener,
SaveChosenLockWorkerBase.Listener { SaveChosenLockWorkerBase.Listener, RemoteLockscreenValidationFragment.Listener {
private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result"; private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
@@ -107,7 +105,6 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
private boolean mDisappearing = false; private boolean mDisappearing = false;
private CountDownTimer mCountdownTimer; private CountDownTimer mCountdownTimer;
private GlifLayout mGlifLayout;
private View mSudContent; private View mSudContent;
// caller-supplied text for various prompts // caller-supplied text for various prompts
@@ -239,6 +236,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
if (mCancelButton != null && TextUtils.isEmpty(mAlternateButtonText)) { if (mCancelButton != null && TextUtils.isEmpty(mAlternateButtonText)) {
mCancelButton.setText(R.string.lockpassword_forgot_pattern); mCancelButton.setText(R.string.lockpassword_forgot_pattern);
} }
updateRemoteLockscreenValidationViews();
} }
if (mForgotButton != null) { if (mForgotButton != null) {
@@ -259,6 +257,9 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
mCountdownTimer.cancel(); mCountdownTimer.cancel();
} }
mCredentialCheckResultTracker.setListener(null); mCredentialCheckResultTracker.setListener(null);
if (mRemoteLockscreenValidationFragment != null) {
mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
}
} }
@Override @Override
@@ -281,6 +282,12 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
updateStage(Stage.NeedToUnlock); updateStage(Stage.NeedToUnlock);
} }
mCredentialCheckResultTracker.setListener(this); mCredentialCheckResultTracker.setListener(this);
if (mRemoteLockscreenValidationFragment != null) {
mRemoteLockscreenValidationFragment.setListener(this, mHandler);
if (mRemoteLockscreenValidationFragment.isRemoteValidationInProgress()) {
mLockPatternView.setEnabled(false);
}
}
} }
@Override @Override
@@ -502,7 +509,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
if (mRemoteValidation) { if (mRemoteValidation) {
validateGuess(credential); validateGuess(credential);
mGlifLayout.setProgressBarShown(true); updateRemoteLockscreenValidationViews();
return; return;
} }
@@ -617,11 +624,12 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
} }
@Override @Override
protected void onRemoteDeviceCredentialValidationResult( public void onRemoteLockscreenValidationResult(
RemoteLockscreenValidationResult result) { RemoteLockscreenValidationResult result) {
switch (result.getResultCode()) { switch (result.getResultCode()) {
case RemoteLockscreenValidationResult.RESULT_GUESS_VALID: case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
if (mCheckBox.isChecked()) { if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
.getLockscreenCredential() != null) {
Log.i(TAG, "Setting device screen lock to the other device's screen lock."); Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
ChooseLockPattern.SaveAndFinishWorker saveAndFinishWorker = ChooseLockPattern.SaveAndFinishWorker saveAndFinishWorker =
new ChooseLockPattern.SaveAndFinishWorker(); new ChooseLockPattern.SaveAndFinishWorker();
@@ -632,14 +640,14 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
saveAndFinishWorker.start( saveAndFinishWorker.start(
mLockPatternUtils, mLockPatternUtils,
/* requestGatekeeperPassword= */ true, /* requestGatekeeperPassword= */ true,
mDeviceCredentialGuess, mRemoteLockscreenValidationFragment.getLockscreenCredential(),
/* currentCredential= */ null, /* currentCredential= */ null,
mEffectiveUserId); mEffectiveUserId);
return; } else {
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
} }
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(), return;
/* timeoutMs= */ 0, mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID: case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(), mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId); /* timeoutMs= */ 0, mEffectiveUserId);
@@ -649,12 +657,14 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
(int) result.getTimeoutMillis(), mEffectiveUserId); (int) result.getTimeoutMillis(), mEffectiveUserId);
break; break;
case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS: case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
getActivity().finish();
break;
case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED: case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED:
getActivity().finish(); onRemoteLockscreenValidationFailure(String.format(
"Cannot continue remote lockscreen validation. ResultCode=%d",
result.getResultCode()));
break;
} }
mGlifLayout.setProgressBarShown(false); updateRemoteLockscreenValidationViews();
mRemoteLockscreenValidationFragment.clearLockscreenCredential();
} }
@Override @Override
@@ -728,21 +738,18 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
} }
/** /**
* Callback for when the device credential guess used for remote validation was set as the * Callback for when the current device's lockscreen to the guess used for
* current device's device credential. * remote lockscreen validation.
*/ */
@Override @Override
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
if (mDeviceCredentialGuess != null) { Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
mDeviceCredentialGuess.zeroize(); mRemoteLockscreenValidationFragment.clearLockscreenCredential();
}
Intent result = new Intent(); Intent result = new Intent();
if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) { if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData)); result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
} }
mGlifLayout.setProgressBarShown(false);
mCredentialCheckResultTracker.setResult(/* matched= */ true, result, mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
/* timeoutMs= */ 0, mEffectiveUserId); /* timeoutMs= */ 0, mEffectiveUserId);
} }

View File

@@ -0,0 +1,190 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.password;
import android.app.RemoteLockscreenValidationResult;
import android.os.Bundle;
import android.os.Handler;
import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
import android.util.Log;
import androidx.fragment.app.Fragment;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.security.SecureBox;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
/**
* A fragment used to hold state for remote lockscreen validation.
* If the original listener is ever re-created, the new listener must be set again using
* {@link #setListener} so that the validation result does not get handled by the old listener.
*/
public class RemoteLockscreenValidationFragment extends Fragment {
private static final String TAG = RemoteLockscreenValidationFragment.class.getSimpleName();
private Listener mListener;
private Handler mHandler;
private boolean mIsInProgress;
private RemoteLockscreenValidationResult mResult;
private String mErrorMessage;
private LockscreenCredential mLockscreenCredential;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onDestroy() {
clearLockscreenCredential();
if (mResult != null && mErrorMessage != null) {
Log.w(TAG, "Unprocessed remote lockscreen validation result");
}
super.onDestroy();
}
/**
* @return {@code true} if remote lockscreen guess validation has started or
* the validation result has not yet been handled.
*/
public boolean isRemoteValidationInProgress() {
return mIsInProgress;
}
/**
* Sets the listener and handler that will handle the result of remote lockscreen validation.
* Unprocessed results or failures will be handled after the listener is set.
*/
public void setListener(Listener listener, Handler handler) {
if (mListener == listener) {
return;
}
mListener = listener;
mHandler = handler;
if (mResult != null) {
handleResult();
} else if (mErrorMessage != null) {
handleFailure();
}
}
/**
* @return {@link LockscreenCredential} if it was cached in {@link #validateLockscreenGuess}.
*/
public LockscreenCredential getLockscreenCredential() {
return mLockscreenCredential;
}
/**
* Clears the {@link LockscreenCredential} if it was cached in {@link #validateLockscreenGuess}.
*/
public void clearLockscreenCredential() {
if (mLockscreenCredential != null) {
mLockscreenCredential.zeroize();
mLockscreenCredential = null;
}
}
/**
* Validates the lockscreen guess on the remote device.
* @param remoteLockscreenValidationClient the client that should be used to send the guess to
* for validation
* @param guess the {@link LockscreenCredential} guess that the user entered
* @param encryptionKey the key that should be used to encrypt the guess before validation
* @param shouldCacheGuess whether to cache to guess so it can be used to set the current
* device's lockscreen after validation succeeds.
*/
public void validateLockscreenGuess(
RemoteLockscreenValidationClient remoteLockscreenValidationClient,
LockscreenCredential guess, byte[] encryptionKey, boolean shouldCacheGuess) {
if (shouldCacheGuess) {
mLockscreenCredential = guess;
}
remoteLockscreenValidationClient.validateLockscreenGuess(
encryptDeviceCredentialGuess(guess.getCredential(), encryptionKey),
new IRemoteLockscreenValidationCallback.Stub() {
@Override
public void onSuccess(RemoteLockscreenValidationResult result) {
mResult = result;
handleResult();
}
@Override
public void onFailure(String message) {
mErrorMessage = message;
handleFailure();
}
});
mIsInProgress = true;
}
private byte[] encryptDeviceCredentialGuess(byte[] guess, byte[] encryptionKey) {
try {
PublicKey publicKey = SecureBox.decodePublicKey(encryptionKey);
return SecureBox.encrypt(
publicKey,
/* sharedSecret= */ null,
LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER,
guess);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
Log.w(TAG, "Error encrypting device credential guess. Returning empty byte[].", e);
return new byte[0];
}
}
private void handleResult() {
if (mHandler != null) {
mHandler.post(()-> {
if (mListener == null || mResult == null) {
return;
}
mIsInProgress = false;
mListener.onRemoteLockscreenValidationResult(mResult);
mResult = null;
});
}
}
private void handleFailure() {
if (mHandler != null) {
mHandler.post(()-> {
if (mListener == null || mErrorMessage == null) {
return;
}
mIsInProgress = false;
mListener.onRemoteLockscreenValidationFailure(
String.format("Remote lockscreen validation failed: %s", mErrorMessage));
mErrorMessage = null;
});
}
}
interface Listener {
void onRemoteLockscreenValidationResult(RemoteLockscreenValidationResult result);
void onRemoteLockscreenValidationFailure(String message);
}
}

View File

@@ -197,7 +197,7 @@ public class ConfirmLockPasswordTest {
verify(mCredentialCheckResultTracker).setResult( verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId)); eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue(); assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue();
assertThat(fragment.mDeviceCredentialGuess).isNotNull(); assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
} }
@Test @Test
@@ -223,7 +223,7 @@ public class ConfirmLockPasswordTest {
verify(mCredentialCheckResultTracker).setResult( verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId)); eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse(); assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
assertThat(fragment.mDeviceCredentialGuess).isNull(); assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
} }
@Test @Test

View File

@@ -177,7 +177,7 @@ public class ConfirmLockPatternTest {
verify(mCredentialCheckResultTracker).setResult( verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId)); eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue(); assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue();
assertThat(fragment.mDeviceCredentialGuess).isNotNull(); assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
} }
@Test @Test
@@ -203,7 +203,7 @@ public class ConfirmLockPatternTest {
verify(mCredentialCheckResultTracker).setResult( verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId)); eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse(); assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
assertThat(fragment.mDeviceCredentialGuess).isNull(); assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
} }
@Test @Test