biometric: Add support for PresentationSession to AndroidX's CryptoObject.

Android 13 / T / API 33 introduced a new class in the Identity
Credential API in the Android Framework to present multiple documents
in a single session. This is similar to existing IdentityCredential
class except that it supports presentation of multiple documents in a
single session.

Bug: 197965513
Test: New unit tests.
Relnote: "CryptoObject support for PresentationSession."
Change-Id: Ife54b58e7e8dda493dd8aee8814bf8000c54d566
diff --git a/biometric/biometric/api/current.txt b/biometric/biometric/api/current.txt
index 6d8f18f..7e279b8 100644
--- a/biometric/biometric/api/current.txt
+++ b/biometric/biometric/api/current.txt
@@ -71,9 +71,11 @@
     ctor public BiometricPrompt.CryptoObject(javax.crypto.Cipher);
     ctor public BiometricPrompt.CryptoObject(javax.crypto.Mac);
     ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public BiometricPrompt.CryptoObject(android.security.identity.IdentityCredential);
+    ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public BiometricPrompt.CryptoObject(android.security.identity.PresentationSession);
     method public javax.crypto.Cipher? getCipher();
     method @RequiresApi(android.os.Build.VERSION_CODES.R) public android.security.identity.IdentityCredential? getIdentityCredential();
     method public javax.crypto.Mac? getMac();
+    method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public android.security.identity.PresentationSession? getPresentationSession();
     method public java.security.Signature? getSignature();
   }
 
diff --git a/biometric/biometric/api/public_plus_experimental_current.txt b/biometric/biometric/api/public_plus_experimental_current.txt
index 6d8f18f..7e279b8 100644
--- a/biometric/biometric/api/public_plus_experimental_current.txt
+++ b/biometric/biometric/api/public_plus_experimental_current.txt
@@ -71,9 +71,11 @@
     ctor public BiometricPrompt.CryptoObject(javax.crypto.Cipher);
     ctor public BiometricPrompt.CryptoObject(javax.crypto.Mac);
     ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public BiometricPrompt.CryptoObject(android.security.identity.IdentityCredential);
+    ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public BiometricPrompt.CryptoObject(android.security.identity.PresentationSession);
     method public javax.crypto.Cipher? getCipher();
     method @RequiresApi(android.os.Build.VERSION_CODES.R) public android.security.identity.IdentityCredential? getIdentityCredential();
     method public javax.crypto.Mac? getMac();
+    method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public android.security.identity.PresentationSession? getPresentationSession();
     method public java.security.Signature? getSignature();
   }
 
diff --git a/biometric/biometric/api/restricted_current.txt b/biometric/biometric/api/restricted_current.txt
index 6d8f18f..7e279b8 100644
--- a/biometric/biometric/api/restricted_current.txt
+++ b/biometric/biometric/api/restricted_current.txt
@@ -71,9 +71,11 @@
     ctor public BiometricPrompt.CryptoObject(javax.crypto.Cipher);
     ctor public BiometricPrompt.CryptoObject(javax.crypto.Mac);
     ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public BiometricPrompt.CryptoObject(android.security.identity.IdentityCredential);
+    ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public BiometricPrompt.CryptoObject(android.security.identity.PresentationSession);
     method public javax.crypto.Cipher? getCipher();
     method @RequiresApi(android.os.Build.VERSION_CODES.R) public android.security.identity.IdentityCredential? getIdentityCredential();
     method public javax.crypto.Mac? getMac();
+    method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public android.security.identity.PresentationSession? getPresentationSession();
     method public java.security.Signature? getSignature();
   }
 
diff --git a/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java b/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
index 2e3fb49..07442e6 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
@@ -230,6 +230,7 @@
         @Nullable private final Cipher mCipher;
         @Nullable private final Mac mMac;
         @Nullable private final android.security.identity.IdentityCredential mIdentityCredential;
+        @Nullable private final android.security.identity.PresentationSession mPresentationSession;
 
         /**
          * Creates a crypto object that wraps the given signature object.
@@ -241,6 +242,7 @@
             mCipher = null;
             mMac = null;
             mIdentityCredential = null;
+            mPresentationSession = null;
         }
 
         /**
@@ -253,6 +255,7 @@
             mCipher = cipher;
             mMac = null;
             mIdentityCredential = null;
+            mPresentationSession = null;
         }
 
         /**
@@ -265,6 +268,7 @@
             mCipher = null;
             mMac = mac;
             mIdentityCredential = null;
+            mPresentationSession = null;
         }
 
         /**
@@ -280,6 +284,23 @@
             mCipher = null;
             mMac = null;
             mIdentityCredential = identityCredential;
+            mPresentationSession = null;
+        }
+
+        /**
+         * Creates a crypto object that wraps the given presentation session object.
+         *
+         * @param presentationSession The presentation session to be associated with this crypto
+         *                           object.
+         */
+        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+        public CryptoObject(
+                @NonNull android.security.identity.PresentationSession presentationSession) {
+            mSignature = null;
+            mCipher = null;
+            mMac = null;
+            mIdentityCredential = null;
+            mPresentationSession = presentationSession;
         }
 
         /**
@@ -322,6 +343,17 @@
         public android.security.identity.IdentityCredential getIdentityCredential() {
             return mIdentityCredential;
         }
+
+        /**
+         * Gets the presentation session object associated with this crypto object.
+         *
+         * @return The presentation session, or {@code null} if none is associated with this object.
+         */
+        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+        @Nullable
+        public android.security.identity.PresentationSession getPresentationSession() {
+            return mPresentationSession;
+        }
     }
 
     /**
diff --git a/biometric/biometric/src/main/java/androidx/biometric/CryptoObjectUtils.java b/biometric/biometric/src/main/java/androidx/biometric/CryptoObjectUtils.java
index 2f28928..4cc0c30 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/CryptoObjectUtils.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/CryptoObjectUtils.java
@@ -103,6 +103,15 @@
             }
         }
 
+        // Presentation session is only supported on API 33 and above.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            final android.security.identity.PresentationSession presentationSession =
+                    Api33Impl.getPresentationSession(cryptoObject);
+            if (presentationSession != null) {
+                return new BiometricPrompt.CryptoObject(presentationSession);
+            }
+        }
+
         return null;
     }
 
@@ -146,6 +155,15 @@
             }
         }
 
+        // Presentation session is only supported on API 33 and above.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            final android.security.identity.PresentationSession presentationSession =
+                    cryptoObject.getPresentationSession();
+            if (presentationSession != null) {
+                return Api33Impl.create(presentationSession);
+            }
+        }
+
         return null;
     }
 
@@ -226,6 +244,12 @@
             return null;
         }
 
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                && cryptoObject.getPresentationSession() != null) {
+            Log.e(TAG, "Presentation session is not supported by FingerprintManager.");
+            return null;
+        }
+
         return null;
     }
 
@@ -274,7 +298,45 @@
     }
 
     /**
-     * Nested class to avoid verification errors for methods introduced in Android 9.0 (API 28).
+     * Nested class to avoid verification errors for methods introduced in Android 13.0 (API 33).
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    private static class Api33Impl {
+        // Prevent instantiation.
+        private Api33Impl() {}
+
+        /**
+         * Creates an instance of the framework class
+         * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} from the given
+         * presentation session.
+         *
+         * @param presentationSession The presentation session object to be wrapped.
+         * @return An instance of {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}.
+         */
+        @SuppressWarnings("deprecation")
+        @NonNull
+        static android.hardware.biometrics.BiometricPrompt.CryptoObject create(
+                @NonNull android.security.identity.PresentationSession presentationSession) {
+            return new android.hardware.biometrics.BiometricPrompt.CryptoObject(presentationSession);
+        }
+
+        /**
+         * Gets the presentation session associated with the given crypto object, if any.
+         *
+         * @param crypto An instance of
+         *               {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}.
+         * @return The wrapped presentation session object, or {@code null}.
+         */
+        @SuppressWarnings("deprecation")
+        @Nullable
+        static android.security.identity.PresentationSession getPresentationSession(
+                @NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject crypto) {
+            return crypto.getPresentationSession();
+        }
+    }
+
+    /**
+     * Nested class to avoid verification errors for methods introduced in Android 11.0 (API 30).
      */
     @RequiresApi(Build.VERSION_CODES.R)
     private static class Api30Impl {
diff --git a/biometric/biometric/src/test/java/androidx/biometric/CryptoObjectUtilsTest.java b/biometric/biometric/src/test/java/androidx/biometric/CryptoObjectUtilsTest.java
index 0d40707..2ef9fdf3 100644
--- a/biometric/biometric/src/test/java/androidx/biometric/CryptoObjectUtilsTest.java
+++ b/biometric/biometric/src/test/java/androidx/biometric/CryptoObjectUtilsTest.java
@@ -118,6 +118,25 @@
         assertThat(unwrappedCrypto.getIdentityCredential()).isEqualTo(identityCredential);
     }
 
+    @SuppressWarnings("deprecation")
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    public void testUnwrapFromBiometricPrompt_WithPresentationSessionCryptoObject() {
+        final android.security.identity.PresentationSession presentationSession =
+                mock(android.security.identity.PresentationSession.class);
+        final android.hardware.biometrics.BiometricPrompt.CryptoObject wrappedCrypto =
+                new android.hardware.biometrics.BiometricPrompt.CryptoObject(presentationSession);
+
+        final BiometricPrompt.CryptoObject unwrappedCrypto =
+                CryptoObjectUtils.unwrapFromBiometricPrompt(wrappedCrypto);
+
+        assertThat(unwrappedCrypto).isNotNull();
+        assertThat(unwrappedCrypto.getCipher()).isNull();
+        assertThat(unwrappedCrypto.getSignature()).isNull();
+        assertThat(unwrappedCrypto.getMac()).isNull();
+        assertThat(unwrappedCrypto.getPresentationSession()).isEqualTo(presentationSession);
+    }
+
     @Test
     @Config(minSdk = Build.VERSION_CODES.P)
     public void testWrapForBiometricPrompt_WithNullCryptoObject() {
@@ -187,6 +206,25 @@
         assertThat(wrappedCrypto.getIdentityCredential()).isEqualTo(identityCredential);
     }
 
+    @SuppressWarnings("deprecation")
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    public void testWrapForBiometricPrompt_WithPresentationSessionCryptoObject() {
+        final android.security.identity.PresentationSession presentationSession =
+                mock(android.security.identity.PresentationSession.class);
+        final BiometricPrompt.CryptoObject unwrappedCrypto =
+                new BiometricPrompt.CryptoObject(presentationSession);
+
+        final android.hardware.biometrics.BiometricPrompt.CryptoObject wrappedCrypto =
+                CryptoObjectUtils.wrapForBiometricPrompt(unwrappedCrypto);
+
+        assertThat(wrappedCrypto).isNotNull();
+        assertThat(wrappedCrypto.getCipher()).isNull();
+        assertThat(wrappedCrypto.getSignature()).isNull();
+        assertThat(wrappedCrypto.getMac()).isNull();
+        assertThat(wrappedCrypto.getPresentationSession()).isEqualTo(presentationSession);
+    }
+
     @Test
     public void testUnwrapFromFingerprintManager_WithNullCryptoObject() {
         assertThat(CryptoObjectUtils.unwrapFromFingerprintManager(null)).isNull();
@@ -299,4 +337,15 @@
 
         assertThat(CryptoObjectUtils.wrapForFingerprintManager(unwrappedCrypto)).isNull();
     }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    public void testWrapForFingerprintManager_WithPresentationSessionCryptoObject() {
+        final android.security.identity.PresentationSession presentationSession =
+                mock(android.security.identity.PresentationSession.class);
+        final BiometricPrompt.CryptoObject unwrappedCrypto =
+                new BiometricPrompt.CryptoObject(presentationSession);
+
+        assertThat(CryptoObjectUtils.wrapForFingerprintManager(unwrappedCrypto)).isNull();
+    }
 }