FRP: Explicitly throw UnsupportedOperation when FRP is not supported

Changes KeyguardManager.createConfirmFactoryResetCredentialIntent such that
it throws UnsupportedOperationException when credential-based FRP is not
available on the device (either because storage was corrupted, or the device
simply does not support it).

This allows GMScore to distinguish this case from when the previous owner
simply didn't set up security.

Also fixes a few places where we previously unintentionally crashed with an
NPE.

Bug: 72368658
Test: atest LockSettingsStorageTests
Change-Id: I92fe1d4b06834e76a4bfffad41276ca28c68ce70
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index b0325cb..237091d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -29,8 +29,12 @@
 import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.Log.TerribleFailure;
+import android.util.Log.TerribleFailureHandler;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
 
@@ -52,7 +56,7 @@
 
     public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 33};
 
-    LockSettingsStorage mStorage;
+    LockSettingsStorageTestable mStorage;
     File mStorageDir;
 
     private File mDb;
@@ -346,6 +350,39 @@
         assertEquals(null, mStorage.readSyntheticPasswordState(10, 1234L, "state"));
     }
 
+    public void testPersistentDataBlock_unavailable() {
+        mStorage.mPersistentDataBlock = null;
+
+        assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
+    }
+
+    public void testPersistentDataBlock_empty() {
+        mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
+
+        assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
+    }
+
+    public void testPersistentDataBlock_withData() {
+        mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
+        when(mStorage.mPersistentDataBlock.getFrpCredentialHandle())
+                .thenReturn(PersistentData.toBytes(PersistentData.TYPE_SP_WEAVER, SOME_USER_ID,
+                        DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD));
+
+        PersistentData data = mStorage.readPersistentDataBlock();
+
+        assertEquals(PersistentData.TYPE_SP_WEAVER, data.type);
+        assertEquals(SOME_USER_ID, data.userId);
+        assertEquals(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, data.qualityForUi);
+        assertArrayEquals(PAYLOAD, data.payload);
+    }
+
+    public void testPersistentDataBlock_exception() {
+        mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
+        when(mStorage.mPersistentDataBlock.getFrpCredentialHandle())
+                .thenThrow(new IllegalStateException("oops"));
+        assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
+    }
+
     public void testPersistentData_serializeUnserialize() {
         byte[] serialized = PersistentData.toBytes(PersistentData.TYPE_SP, SOME_USER_ID,
                 DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD);
@@ -366,6 +403,13 @@
         assertSame(PersistentData.NONE, deserialized);
     }
 
+    public void testPersistentData_unserializeInvalid() {
+        assertNotNull(suppressAndReturnWtf(() -> {
+            PersistentData deserialized = PersistentData.fromBytes(new byte[]{5});
+            assertSame(PersistentData.NONE, deserialized);
+        }));
+    }
+
     public void testPersistentData_unserialize_version1() {
         // This test ensures that we can read serialized VERSION_1 PersistentData even if we change
         // the wire format in the future.
@@ -450,4 +494,19 @@
         assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN, cred.type);
         assertArrayEquals(pattern, cred.hash);
     }
+
+    /**
+     * Suppresses reporting of the WTF to system_server, so we don't pollute the dropbox with
+     * intentionally caused WTFs.
+     */
+    private TerribleFailure suppressAndReturnWtf(Runnable r) {
+        TerribleFailure[] captured = new TerribleFailure[1];
+        TerribleFailureHandler prevWtfHandler = Log.setWtfHandler((t, w, s) -> captured[0] = w);
+        try {
+            r.run();
+        } finally {
+            Log.setWtfHandler(prevWtfHandler);
+        }
+        return captured[0];
+    }
 }