Add a method to write password data

This method will save the current password data to the repair mode
file. This can be used to replace VERIFY_FLAG_WRITE_REPAIR_MODE_PW when
verification is not needed.

Flag: EXEMPT refactor
Bug: 325666121
Test: atest LockscreenRepairModeTest
Test: atest LockPatternUtilsTest
Change-Id: Ibdf9b5c87ea822abe7bd7ff1c3de770665388d98
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 8236783..511c680 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -109,4 +109,5 @@
     boolean isWeakEscrowTokenActive(long handle, int userId);
     boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId);
     void unlockUserKeyIfUnsecured(int userId);
+    boolean writeRepairModeCredential(int userId);
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index e46b8d7..f4ad487 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -449,6 +449,21 @@
     }
 
     /**
+     * Save the current password data to the repair mode file.
+     *
+     * @return true if success or false otherwise.
+     */
+    public boolean writeRepairModeCredential(int userId) {
+        throwIfCalledOnMainThread();
+        try {
+            return getLockSettings().writeRepairModeCredential(userId);
+        } catch (RemoteException re) {
+            Log.e(TAG, "Failed to write repair mode credential", re);
+            return false;
+        }
+    }
+
+    /**
      * Check to see if a credential matches the saved one.
      * If credential matches, return an opaque attestation that the challenge was verified.
      *
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index b90480a..92a7d8e 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -68,6 +68,7 @@
 
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -316,6 +317,40 @@
         assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
     }
 
+    @Test
+    public void testWriteRepairModeCredential_mainThread() {
+        createTestLockSettings();
+        var context = InstrumentationRegistry.getTargetContext();
+
+        var future = new CompletableFuture<Exception>();
+        context.getMainThreadHandler().post(() -> {
+            try {
+                mLockPatternUtils.writeRepairModeCredential(USER_ID);
+                future.complete(null);
+            } catch (Exception e) {
+                future.complete(e);
+            }
+        });
+
+        var e = future.join();
+        assertThat(e).isNotNull();
+        assertThat(e.getMessage()).contains("should not be called from the main thread");
+    }
+
+    @Test
+    public void testWriteRepairModeCredential() throws Exception {
+        var ils = createTestLockSettings();
+
+        when(ils.writeRepairModeCredential(USER_ID)).thenReturn(false);
+        assertThat(mLockPatternUtils.writeRepairModeCredential(USER_ID)).isFalse();
+
+        when(ils.writeRepairModeCredential(USER_ID)).thenReturn(true);
+        assertThat(mLockPatternUtils.writeRepairModeCredential(USER_ID)).isTrue();
+
+        when(ils.writeRepairModeCredential(USER_ID)).thenThrow(new RemoteException());
+        assertThat(mLockPatternUtils.writeRepairModeCredential(USER_ID)).isFalse();
+    }
+
     private TestStrongAuthTracker createStrongAuthTracker() {
         final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
         return new TestStrongAuthTracker(context, Looper.getMainLooper());
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 7157646..7aae91b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1773,6 +1773,20 @@
         }
     }
 
+    @Override
+    public boolean writeRepairModeCredential(int userId) {
+        checkWritePermission();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSpManager) {
+                long protectorId = getCurrentLskfBasedProtectorId(userId);
+                return mSpManager.writeRepairModeCredentialLocked(protectorId, userId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     /**
      * @param savedCredential if the user is a profile with
      * {@link UserManager#isCredentialSharableWithParent()} with unified challenge and
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
index 70150c5..4396c67 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
@@ -19,6 +19,8 @@
 import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
 import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 
@@ -199,6 +201,27 @@
                         .getResponseCode());
     }
 
+    @Test
+    public void writeRepairModeCredential_noLock() {
+        assertThat(mService.writeRepairModeCredential(PRIMARY_USER_ID)).isFalse();
+    }
+
+    @Test
+    public void writeRepairModeCredential_hasLock() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertThat(mService.writeRepairModeCredential(PRIMARY_USER_ID)).isTrue();
+    }
+
+    @Test
+    public void writeRepairModeCredential_verifyRepairModeUser() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        mService.writeRepairModeCredential(PRIMARY_USER_ID);
+        setRepairModeActive(true);
+
+        var response = mService.verifyCredential(newPin("1234"), USER_REPAIR_MODE, 0);
+        assertThat(response.isMatched()).isTrue();
+    }
+
     private void setRepairModeActive(boolean active) {
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.REPAIR_MODE_ACTIVE, active ? 1 : 0);