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();
+ }
}