Add RemoteLockscreenValidationFragment to help retain remote lockscreen
validation state.

Currently, if ConfirmDeviceCredentialBaseFragment is ever re-created due
to orientation change, screen getting turned off, etc., relevant state
gets lost. This led to the old ConfirmDeviceCredentialBaseFragment
handling results which led to issues such as lockscreen not getting set.
By addiing a retained RemoteLockscreenValidationFragment,
we're able to update the new ConfirmDeviceCredentialBaseFragment
that will handle results. We can also retain other important state like
the device credential guess to be set after successful validation.

Some smaller changes include:
* If the activity is finished for any reason other than "Back" getting
  pressed, RESULT_FIRST_USER is returned instead of RESULT_CANCELED.
* CheckBox, "Forgot [LSKF]?" button, and EditText/LockPatternView
  gets disabled during validation.
* The above also stay disabled if ConfirmDeviceCredentialBaseFragment
  gets re-created and remote lockscreen validation is still in progress.

Test: m RunSettingsRoboTests -j
ROBOTEST_FILTER=com.android.settings.password
Test: Manual
Bug: 274983372
Bug: 274991889
Bug: 274792310
Bug: 270395807

Change-Id: Ib6d47430e233a43e6985ab83abae45713c49771f
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
index 1bb6df0..f4cfabc 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
@@ -17,6 +17,7 @@
 // TODO (b/35202196): move this class out of the root of the package.
 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 com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
@@ -24,7 +25,6 @@
 import android.annotation.Nullable;
 import android.app.Dialog;
 import android.app.KeyguardManager;
-import android.app.RemoteLockscreenValidationResult;
 import android.app.RemoteLockscreenValidationSession;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.ManagedSubscriptionsPolicy;
@@ -38,7 +38,6 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
 import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
@@ -55,14 +54,11 @@
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
-import com.android.security.SecureBox;
 import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settings.core.InstrumentedFragment;
 
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
+import com.google.android.setupdesign.GlifLayout;
 
 /**
  * Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
@@ -89,9 +85,13 @@
     /** 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 String FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION =
+            "remote_lockscreen_validation";
+
     protected boolean mReturnCredentials = false;
     protected boolean mReturnGatekeeperPassword = false;
     protected boolean mForceVerifyPath = false;
+    protected GlifLayout mGlifLayout;
     protected CheckBox mCheckBox;
     protected Button mCancelButton;
     /** Button allowing managed profile password reset, null when is not shown. */
@@ -109,8 +109,8 @@
     protected BiometricManager mBiometricManager;
     @Nullable protected RemoteLockscreenValidationSession mRemoteLockscreenValidationSession;
     /** Credential saved so the credential can be set for device if remote validation passes */
-    @Nullable protected LockscreenCredential mDeviceCredentialGuess;
     @Nullable protected RemoteLockscreenValidationClient mRemoteLockscreenValidationClient;
+    protected RemoteLockscreenValidationFragment mRemoteLockscreenValidationFragment;
 
     private boolean isInternalActivity() {
         return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
@@ -136,8 +136,8 @@
                     FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION)) {
                 mRemoteValidation = true;
             } else {
-                Log.e(TAG, "Remote device credential validation not enabled.");
-                getActivity().finish();
+                onRemoteLockscreenValidationFailure(
+                        "Remote lockscreen validation not enabled.");
             }
         }
         if (mRemoteValidation) {
@@ -146,23 +146,31 @@
                     RemoteLockscreenValidationSession.class);
             if (mRemoteLockscreenValidationSession == null
                     || mRemoteLockscreenValidationSession.getRemainingAttempts() == 0) {
-                Log.e(TAG, "RemoteLockscreenValidationSession is null or "
+                onRemoteLockscreenValidationFailure("RemoteLockscreenValidationSession is null or "
                         + "no more attempts for remote lockscreen validation.");
-                getActivity().finish();
             }
 
             ComponentName remoteLockscreenValidationServiceComponent =
                     intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class);
             if (remoteLockscreenValidationServiceComponent == null) {
-                Log.e(TAG, "RemoteLockscreenValidationService ComponentName is null");
-                getActivity().finish();
+                onRemoteLockscreenValidationFailure(
+                        "RemoteLockscreenValidationService ComponentName is null");
             }
             mRemoteLockscreenValidationClient = RemoteLockscreenValidationClient
                     .create(getContext(), remoteLockscreenValidationServiceComponent);
             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()));
-                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 @@
         mCancelButton.setOnClickListener(v -> {
             if (hasAlternateButton) {
                 getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
+                getActivity().finish();
+            } else if (mRemoteValidation) {
+                onRemoteLockscreenValidationFailure("Forgot lockscreen credential button pressed.");
             }
-            getActivity().finish();
         });
         setupForgotButtonIfManagedProfile(view);
 
@@ -299,17 +309,11 @@
         if (mRemoteLockscreenValidationClient != null) {
             mRemoteLockscreenValidationClient.disconnect();
         }
-        if (mDeviceCredentialGuess != null) {
-            mDeviceCredentialGuess.zeroize();
-        }
         super.onDestroy();
     }
 
     protected abstract void authenticationSucceeded();
 
-    protected abstract void onRemoteDeviceCredentialValidationResult(
-            RemoteLockscreenValidationResult result);
-
     public void prepareEnterAnimation() {
     }
 
@@ -411,43 +415,33 @@
     }
 
     protected void validateGuess(LockscreenCredential credentialGuess) {
-        if (mCheckBox.isChecked()) {
-            // Keep credential in memory since user wants to set guess as screen lock.
-            mDeviceCredentialGuess = credentialGuess;
-        } 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());
-                    }
-                });
+        mRemoteLockscreenValidationFragment.validateLockscreenGuess(
+                mRemoteLockscreenValidationClient, credentialGuess,
+                mRemoteLockscreenValidationSession.getSourcePublicKey(), mCheckBox.isChecked());
     }
 
-    private byte[] encryptDeviceCredentialGuess(byte[] guess) {
-        try {
-            byte[] encodedPublicKey = mRemoteLockscreenValidationSession.getSourcePublicKey();
-            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];
+    protected void updateRemoteLockscreenValidationViews() {
+        if (!mRemoteValidation || mRemoteLockscreenValidationFragment == null) {
+            return;
         }
+
+        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();
diff --git a/src/com/android/settings/password/ConfirmLockPassword.java b/src/com/android/settings/password/ConfirmLockPassword.java
index 81bd8c2..03b89f2 100644
--- a/src/com/android/settings/password/ConfirmLockPassword.java
+++ b/src/com/android/settings/password/ConfirmLockPassword.java
@@ -71,8 +71,6 @@
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
 
-import com.google.android.setupdesign.GlifLayout;
-
 import java.util.ArrayList;
 
 public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
@@ -127,7 +125,8 @@
 
     public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
             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 ImeAwareEditText mPasswordEntry;
         private TextViewInputDisabler mPasswordEntryInputDisabler;
@@ -140,7 +139,6 @@
         private AppearAnimationUtils mAppearAnimationUtils;
         private DisappearAnimationUtils mDisappearAnimationUtils;
         private boolean mIsManagedProfile;
-        private GlifLayout mGlifLayout;
         private CharSequence mCheckBoxLabel;
 
         // required constructor for fragments
@@ -255,6 +253,7 @@
                             ? R.string.lockpassword_forgot_password
                             : R.string.lockpassword_forgot_pin);
                 }
+                updateRemoteLockscreenValidationViews();
             }
 
             if (mForgotButton != null) {
@@ -405,6 +404,9 @@
                 mCountdownTimer = null;
             }
             mCredentialCheckResultTracker.setListener(null);
+            if (mRemoteLockscreenValidationFragment != null) {
+                mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
+            }
         }
 
         @Override
@@ -426,6 +428,9 @@
                         mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
             }
             mCredentialCheckResultTracker.setListener(this);
+            if (mRemoteLockscreenValidationFragment != null) {
+                mRemoteLockscreenValidationFragment.setListener(this, mHandler);
+            }
         }
 
         @Override
@@ -436,13 +441,17 @@
         private void updatePasswordEntry() {
             final boolean isLockedOut =
                     mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0;
-            mPasswordEntry.setEnabled(!isLockedOut);
-            mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut);
-            if (isLockedOut) {
-                mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/);
-            } else {
+            final boolean isRemoteLockscreenValidationInProgress =
+                    mRemoteLockscreenValidationFragment != null
+                    && mRemoteLockscreenValidationFragment.isRemoteValidationInProgress();
+            boolean shouldEnableInput = !isLockedOut && !isRemoteLockscreenValidationInProgress;
+            mPasswordEntry.setEnabled(shouldEnableInput);
+            mPasswordEntryInputDisabler.setInputEnabled(shouldEnableInput);
+            if (shouldEnableInput) {
                 mPasswordEntry.scheduleShowSoftInput();
                 mPasswordEntry.requestFocus();
+            } else {
+                mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), /* flags= */0);
             }
         }
 
@@ -472,7 +481,8 @@
 
             if (mRemoteValidation) {
                 validateGuess(credential);
-                mGlifLayout.setProgressBarShown(true);
+                updateRemoteLockscreenValidationViews();
+                updatePasswordEntry();
                 return;
             }
 
@@ -604,14 +614,15 @@
         }
 
         @Override
-        protected void onRemoteDeviceCredentialValidationResult(
+        public void onRemoteLockscreenValidationResult(
                 RemoteLockscreenValidationResult result) {
             switch (result.getResultCode()) {
                 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 =
                                 new ChooseLockPassword.SaveAndFinishWorker();
-                        Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
                         getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
                                 .commit();
                         getFragmentManager().executePendingTransactions();
@@ -619,14 +630,14 @@
                         saveAndFinishWorker.start(
                                 mLockPatternUtils,
                                 /* requestGatekeeperPassword= */ true,
-                                mDeviceCredentialGuess,
+                                mRemoteLockscreenValidationFragment.getLockscreenCredential(),
                                 /* currentCredential= */ null,
                                 mEffectiveUserId);
-                        return;
+                    } else {
+                        mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
+                                /* timeoutMs= */ 0, mEffectiveUserId);
                     }
-                    mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
-                            /* timeoutMs= */ 0, mEffectiveUserId);
-                    break;
+                    return;
                 case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
                     mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
                             /* timeoutMs= */ 0, mEffectiveUserId);
@@ -636,12 +647,15 @@
                             (int) result.getTimeoutMillis(), mEffectiveUserId);
                     break;
                 case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
-                    getActivity().finish();
-                    break;
                 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
@@ -701,21 +715,18 @@
         }
 
         /**
-         * Callback for when the device credential guess used for remote validation was set as the
-         * current device's device credential.
+         * Callback for when the current device's lockscreen was set to the guess used for
+         * remote lockscreen validation.
          */
         @Override
         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
-            if (mDeviceCredentialGuess != null) {
-                mDeviceCredentialGuess.zeroize();
-            }
+            Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
+            mRemoteLockscreenValidationFragment.clearLockscreenCredential();
 
             Intent result = new Intent();
             if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
                 result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
             }
-
-            mGlifLayout.setProgressBarShown(false);
             mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
                     /* timeoutMs= */ 0, mEffectiveUserId);
         }
diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java
index 7c21739..7db25fd 100644
--- a/src/com/android/settings/password/ConfirmLockPattern.java
+++ b/src/com/android/settings/password/ConfirmLockPattern.java
@@ -59,8 +59,6 @@
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
 
-import com.google.android.setupdesign.GlifLayout;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -97,7 +95,7 @@
 
     public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
             implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener,
-            SaveChosenLockWorkerBase.Listener {
+            SaveChosenLockWorkerBase.Listener, RemoteLockscreenValidationFragment.Listener {
 
         private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
 
@@ -107,7 +105,6 @@
         private boolean mDisappearing = false;
         private CountDownTimer mCountdownTimer;
 
-        private GlifLayout mGlifLayout;
         private View mSudContent;
 
         // caller-supplied text for various prompts
@@ -239,6 +236,7 @@
                 if (mCancelButton != null && TextUtils.isEmpty(mAlternateButtonText)) {
                     mCancelButton.setText(R.string.lockpassword_forgot_pattern);
                 }
+                updateRemoteLockscreenValidationViews();
             }
 
             if (mForgotButton != null) {
@@ -259,6 +257,9 @@
                 mCountdownTimer.cancel();
             }
             mCredentialCheckResultTracker.setListener(null);
+            if (mRemoteLockscreenValidationFragment != null) {
+                mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
+            }
         }
 
         @Override
@@ -281,6 +282,12 @@
                 updateStage(Stage.NeedToUnlock);
             }
             mCredentialCheckResultTracker.setListener(this);
+            if (mRemoteLockscreenValidationFragment != null) {
+                mRemoteLockscreenValidationFragment.setListener(this, mHandler);
+                if (mRemoteLockscreenValidationFragment.isRemoteValidationInProgress()) {
+                    mLockPatternView.setEnabled(false);
+                }
+            }
         }
 
         @Override
@@ -502,7 +509,7 @@
 
                 if (mRemoteValidation) {
                     validateGuess(credential);
-                    mGlifLayout.setProgressBarShown(true);
+                    updateRemoteLockscreenValidationViews();
                     return;
                 }
 
@@ -617,11 +624,12 @@
         }
 
         @Override
-        protected void onRemoteDeviceCredentialValidationResult(
+        public void onRemoteLockscreenValidationResult(
                 RemoteLockscreenValidationResult result) {
             switch (result.getResultCode()) {
                 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.");
                         ChooseLockPattern.SaveAndFinishWorker saveAndFinishWorker =
                                 new ChooseLockPattern.SaveAndFinishWorker();
@@ -632,14 +640,14 @@
                         saveAndFinishWorker.start(
                                 mLockPatternUtils,
                                 /* requestGatekeeperPassword= */ true,
-                                mDeviceCredentialGuess,
+                                mRemoteLockscreenValidationFragment.getLockscreenCredential(),
                                 /* currentCredential= */ null,
                                 mEffectiveUserId);
-                        return;
+                    } else {
+                        mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
+                                /* timeoutMs= */ 0, mEffectiveUserId);
                     }
-                    mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
-                            /* timeoutMs= */ 0, mEffectiveUserId);
-                    break;
+                    return;
                 case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
                     mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
                             /* timeoutMs= */ 0, mEffectiveUserId);
@@ -649,12 +657,14 @@
                             (int) result.getTimeoutMillis(), mEffectiveUserId);
                     break;
                 case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
-                    getActivity().finish();
-                    break;
                 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
@@ -728,21 +738,18 @@
         }
 
         /**
-         * Callback for when the device credential guess used for remote validation was set as the
-         * current device's device credential.
+         * Callback for when the current device's lockscreen to the guess used for
+         * remote lockscreen validation.
          */
         @Override
         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
-            if (mDeviceCredentialGuess != null) {
-                mDeviceCredentialGuess.zeroize();
-            }
+            Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
+            mRemoteLockscreenValidationFragment.clearLockscreenCredential();
 
             Intent result = new Intent();
             if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
                 result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
             }
-
-            mGlifLayout.setProgressBarShown(false);
             mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
                     /* timeoutMs= */ 0, mEffectiveUserId);
         }
diff --git a/src/com/android/settings/password/RemoteLockscreenValidationFragment.java b/src/com/android/settings/password/RemoteLockscreenValidationFragment.java
new file mode 100644
index 0000000..5819376
--- /dev/null
+++ b/src/com/android/settings/password/RemoteLockscreenValidationFragment.java
@@ -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);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java
index 77a013d..d26c33b 100644
--- a/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java
+++ b/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java
@@ -197,7 +197,7 @@
         verify(mCredentialCheckResultTracker).setResult(
                 eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
         assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue();
-        assertThat(fragment.mDeviceCredentialGuess).isNotNull();
+        assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
     }
 
     @Test
@@ -223,7 +223,7 @@
         verify(mCredentialCheckResultTracker).setResult(
                 eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
         assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
-        assertThat(fragment.mDeviceCredentialGuess).isNull();
+        assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java b/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java
index 4374e38..2ed7988 100644
--- a/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java
+++ b/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java
@@ -177,7 +177,7 @@
         verify(mCredentialCheckResultTracker).setResult(
                 eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
         assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue();
-        assertThat(fragment.mDeviceCredentialGuess).isNotNull();
+        assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
     }
 
     @Test
@@ -203,7 +203,7 @@
         verify(mCredentialCheckResultTracker).setResult(
                 eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
         assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
-        assertThat(fragment.mDeviceCredentialGuess).isNull();
+        assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
     }
 
     @Test