diff --git a/CredentialProvider/MyVault/app/build.gradle.kts b/CredentialProvider/MyVault/app/build.gradle.kts
index 8f8456d4..e8ca419b 100644
--- a/CredentialProvider/MyVault/app/build.gradle.kts
+++ b/CredentialProvider/MyVault/app/build.gradle.kts
@@ -17,6 +17,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.devtools.ksp)
+ alias(libs.plugins.compose.compiler)
}
android {
@@ -75,7 +76,11 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
+
implementation(libs.androidx.credential.manager)
+ implementation(libs.provider.events)
+ implementation(libs.provider.events.ps)
+
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.room.runtime)
ksp(libs.androidx.room.compiler)
diff --git a/CredentialProvider/MyVault/app/src/main/AndroidManifest.xml b/CredentialProvider/MyVault/app/src/main/AndroidManifest.xml
index 907bc0f4..de73600a 100644
--- a/CredentialProvider/MyVault/app/src/main/AndroidManifest.xml
+++ b/CredentialProvider/MyVault/app/src/main/AndroidManifest.xml
@@ -18,6 +18,7 @@
xmlns:tools="http://schemas.android.com/tools">
+
+
+
+
+
+
+
+
diff --git a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/AppDependencies.kt b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/AppDependencies.kt
index 0e745863..1fb29c5f 100644
--- a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/AppDependencies.kt
+++ b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/AppDependencies.kt
@@ -23,6 +23,9 @@ import com.example.android.authentication.myvault.data.CredentialsDataSource
import com.example.android.authentication.myvault.data.CredentialsRepository
import com.example.android.authentication.myvault.data.RPIconDataSource
import com.example.android.authentication.myvault.data.room.MyVaultDatabase
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
/**
* This class is an application-level singleton object which is providing dependencies required for the app to function.
@@ -42,6 +45,8 @@ object AppDependencies {
lateinit var rpIconDataSource: RPIconDataSource
+ lateinit var coroutineScope: CoroutineScope
+
/**
* Initializes the core components required for the application's data storage and icon handling.
* This includes:
@@ -72,5 +77,7 @@ object AppDependencies {
credentialsDataSource,
context,
)
+
+ coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
}
}
diff --git a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/Constants.kt b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/Constants.kt
new file mode 100644
index 00000000..2394a96b
--- /dev/null
+++ b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/Constants.kt
@@ -0,0 +1,9 @@
+package com.example.android.authentication.myvault
+
+const val NOTIFICATION_CHANNEL_ID = "channel_id"
+const val NOTIFICATION_ID = 135
+const val CREDENTIAL_ID = "credentialId"
+const val USER_ID = "userId"
+const val ACCEPTED_CREDENTIAL_IDS = "allAcceptedCredentialIds"
+const val NAME = "name"
+const val DISPLAY_NAME = "displayName"
diff --git a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/NotificationUtils.kt b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/NotificationUtils.kt
new file mode 100644
index 00000000..de1536eb
--- /dev/null
+++ b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/NotificationUtils.kt
@@ -0,0 +1,74 @@
+package com.example.android.authentication.myvault
+
+import android.Manifest
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import androidx.core.app.ActivityCompat
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import com.example.android.authentication.myvault.ui.MainActivity
+
+/**
+ * Creates and registers a notification channel with the system.
+ *
+ * This is a utility extension function that creates a Notification Channel with
+ * [NotificationManager.IMPORTANCE_HIGH] to show pop-up notification on receiving
+ * signals from the RP apps
+ *
+ * @param channelName The user-visible name of the channel.
+ * This is displayed in the system's notification settings.
+ * @param channelDescription The user-visible description of the channel.
+ * This is displayed in the system's notification settings.
+ */
+fun Context.createNotificationChannel(
+ channelName: String,
+ channelDescription: String,
+) {
+ val channel = NotificationChannel(
+ NOTIFICATION_CHANNEL_ID,
+ channelName,
+ NotificationManager.IMPORTANCE_HIGH
+ ).apply {
+ description = channelDescription
+ }
+
+ val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.createNotificationChannel(channel)
+}
+
+/**
+ * Utility extension function that displays a system notification with the given title and content.
+ *
+ * @param title The title of the notification.
+ * @param content The main content text of the notification.
+ */
+fun Context.showNotification(
+ title: String,
+ content: String,
+) {
+ val intent = Intent(this, MainActivity::class.java)
+ val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+
+ val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.android_secure)
+ .setContentTitle(title)
+ .setContentText(content)
+ .setPriority(NotificationCompat.PRIORITY_MAX)
+ .setContentIntent(pendingIntent)
+ .setAutoCancel(true)
+
+ with(NotificationManagerCompat.from(this)) {
+ if (ActivityCompat.checkSelfPermission(
+ this@showNotification,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED) {
+ return@with
+ }
+ notify(NOTIFICATION_ID, builder.build())
+ }
+}
diff --git a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialProviderService.kt b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialProviderService.kt
new file mode 100644
index 00000000..42e7bee9
--- /dev/null
+++ b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialProviderService.kt
@@ -0,0 +1,225 @@
+package com.example.android.authentication.myvault.data
+
+import android.annotation.SuppressLint
+import android.util.Log
+import androidx.credentials.SignalAllAcceptedCredentialIdsRequest
+import androidx.credentials.SignalCurrentUserDetailsRequest
+import androidx.credentials.SignalUnknownCredentialRequest
+import androidx.credentials.providerevents.service.CredentialProviderEventsService
+import androidx.credentials.providerevents.signal.ProviderSignalCredentialStateCallback
+import androidx.credentials.providerevents.signal.ProviderSignalCredentialStateRequest
+import com.example.android.authentication.myvault.ACCEPTED_CREDENTIAL_IDS
+import com.example.android.authentication.myvault.AppDependencies
+import com.example.android.authentication.myvault.CREDENTIAL_ID
+import com.example.android.authentication.myvault.DISPLAY_NAME
+import com.example.android.authentication.myvault.NAME
+import com.example.android.authentication.myvault.R
+import com.example.android.authentication.myvault.USER_ID
+import com.example.android.authentication.myvault.showNotification
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.json.JSONArray
+import org.json.JSONObject
+
+/**
+ * A service that listens to credential provider events triggered by the relying parties
+ *
+ * This service is responsible for handling signals related to credential state changes in the RPs,
+ * such as when a credential is no longer valid, when a list of accepted credentials is accepted,
+ * or when current user details change for credentials
+ */
+class CredentialProviderService: CredentialProviderEventsService() {
+ private val dataSource = AppDependencies.credentialsDataSource
+ private val coroutineScope = AppDependencies.coroutineScope
+
+ /**
+ * Called when the system or another credential provider signals a change in credential state.
+ *
+ * This method inspects the type of [ProviderSignalCredentialStateRequest] and delegates
+ * to the appropriate handler function to update the local data store and show a notification.
+ * After processing the signal, {@link ProviderSignalCredentialStateCallback#onSignalConsumed()}
+ * is called to acknowledge receipt of the signal.
+ *
+ * The {@link SuppressLint("RestrictedApi")} annotation is used because this method
+ * interacts with APIs from the {@code androidx.credentials} library that might be
+ * marked as restricted for extension by library developers.
+ *
+ * @param request The request containing details about the credential state signal.
+ * @param callback The callback to be invoked after the signal has been processed.
+ */
+ @SuppressLint("RestrictedApi")
+ override fun onSignalCredentialStateRequest(
+ request: ProviderSignalCredentialStateRequest,
+ callback: ProviderSignalCredentialStateCallback,
+ ) {
+ when (request.callingRequest) {
+ is SignalUnknownCredentialRequest -> {
+ updateDataOnSignalAndShowNotification(
+ handleRequest = ::handleUnknownCredentialRequest,
+ requestJson = request.callingRequest.requestJson,
+ notificationTitle = getString(R.string.credential_deletion),
+ notificationContent = getString(R.string.unknown_signal_message)
+ )
+ }
+
+ is SignalAllAcceptedCredentialIdsRequest -> {
+ updateDataOnSignalAndShowNotification(
+ handleRequest = ::handleAcceptedCredentialsRequest,
+ requestJson = request.callingRequest.requestJson,
+ notificationTitle = getString(R.string.credentials_list_updation),
+ notificationContent = getString(R.string.all_accepted_signal_message)
+ )
+ }
+
+ is SignalCurrentUserDetailsRequest -> {
+ updateDataOnSignalAndShowNotification(
+ handleRequest = ::handleCurrentUserDetailRequest,
+ requestJson = request.callingRequest.requestJson,
+ notificationTitle = getString(R.string.user_details_updation),
+ notificationContent = getString(R.string.current_user_signal_message)
+ )
+ }
+
+ else -> { }
+ }
+
+ callback.onSignalConsumed()
+ }
+
+ /**
+ * A helper function to asynchronously handle a credential state update request,
+ * update the data source, and then show a system notification on the main thread.
+ *
+ * @param handleRequest A suspend function that takes the request JSON string and processes it.
+ * This function is responsible for interacting with the data source.
+ * @param requestJson The JSON string payload from the original credential signal request.
+ * @param notificationTitle The title to be used for the system notification.
+ * @param notificationContent The content text for the system notification.
+ */
+ private fun updateDataOnSignalAndShowNotification(
+ handleRequest: suspend (String) -> Boolean,
+ requestJson: String,
+ notificationTitle: String,
+ notificationContent: String,
+ ) {
+ coroutineScope.launch {
+ val success = handleRequest(requestJson)
+ withContext(Dispatchers.Main) {
+ if (success) {
+ showNotification(
+ title = notificationTitle,
+ content = notificationContent,
+ )
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles a [SignalUnknownCredentialRequest] by parsing the credential ID
+ * from the request JSON and attempting to hide the corresponding passkey in the data source.
+ *
+ * "Hiding" a passkey typically means marking it as inactive or not to be suggested
+ * for autofill, often because the system has indicated it's no longer valid
+ * (e.g., deleted from the authenticator).
+ *
+ * @param requestJson The JSON string payload from the [SignalUnknownCredentialRequest].
+ * Expected to contain a {@code CREDENTIAL_ID}.
+ */
+ private suspend fun handleUnknownCredentialRequest(requestJson: String): Boolean {
+ try {
+ val credentialId = JSONObject(requestJson).getString(CREDENTIAL_ID)
+ dataSource.getPasskey(credentialId)?.let {
+ // Currently hiding the passkey on UnknownSignal for testing purpose
+ // If the business logc requires deletion, please add deletion code instead
+ dataSource.hidePasskey(it)
+ }
+ return true
+ } catch (e: Exception) {
+ Log.e(getString(R.string.failed_to_handle_unknowncredentialrequest), e.toString())
+ return false
+ }
+ }
+
+ /**
+ * Handles a {@link SignalAllAcceptedCredentialIdsRequest} by synchronizing the visibility
+ * state of passkeys for a specific user.
+ *
+ * It retrieves all current passkeys for the user from the data source. Then, it compares
+ * this list against the list of accepted credential IDs provided in the signal.
+ * Passkeys whose IDs are in the accepted list are unhidden (made active).
+ * Passkeys whose IDs are not in the accepted list are hidden (made inactive).
+ *
+ * This is useful for scenarios where the system provides an authoritative list of
+ * credentials that are currently valid or preferred for a user.
+ *
+ * @param requestJson The JSON string payload from the {@link SignalAllAcceptedCredentialIdsRequest}.
+ * Expected to contain a {@code USER_ID} and {@code ACCEPTED_CREDENTIAL_IDS}
+ * (which can be a string or a JSON array of strings).
+ */
+ private suspend fun handleAcceptedCredentialsRequest(requestJson: String): Boolean {
+ try {
+ val request = JSONObject(requestJson)
+ val userId = request.getString(USER_ID)
+ val listCurrentPasskeysForUser = dataSource.getAllPasskeysForUser(userId) ?: emptyList()
+ val listAllAcceptedCredIds = mutableListOf()
+ when (val value = request.get(ACCEPTED_CREDENTIAL_IDS)) {
+ is String -> listAllAcceptedCredIds.add(value)
+ is JSONArray -> {
+ for (i in 0 until value.length()) {
+ val item = value.get(i)
+ if (item is String) {
+ listAllAcceptedCredIds.add(item)
+ }
+ }
+ }
+
+ else -> { /*do nothing*/ }
+ }
+
+ for (key in listCurrentPasskeysForUser) {
+ if (listAllAcceptedCredIds.contains(key.credId)) {
+ dataSource.unhidePasskey(key)
+ } else {
+ dataSource.hidePasskey(key)
+ }
+ }
+ return true
+ } catch (e: Exception) {
+ Log.e(getString(R.string.failed_to_handle_acceptedcredentialsrequest), e.toString())
+ return false
+ }
+ }
+
+ /**
+ * Handles a {@link SignalCurrentUserDetailsRequest} by updating the username and display name
+ * for all passkeys associated with a given user ID.
+ *
+ * This is useful when the user's profile information (like name or display name)
+ * changes elsewhere, and the credential provider needs to reflect these changes
+ * in its stored passkey data.
+ *
+ * @param requestJson The JSON string payload from the {@link SignalCurrentUserDetailsRequest}.
+ * Expected to contain {@code USER_ID}, {@code NAME}, and {@code DISPLAY_NAME}.
+ */
+ private suspend fun handleCurrentUserDetailRequest(requestJson: String): Boolean {
+ try {
+ val request = JSONObject(requestJson)
+ val userId = request.getString(USER_ID)
+ val updatedName = request.getString(NAME)
+ val updatedDisplayName = request.getString(DISPLAY_NAME)
+ val listPasskeys = dataSource.getAllPasskeysForUser(userId) ?: emptyList()
+ // Update user details for each passkey
+ for (key in listPasskeys) {
+ val newPasskeyItem =
+ key.copy(username = updatedName, displayName = updatedDisplayName)
+ dataSource.updatePasskey(newPasskeyItem)
+ }
+ return true
+ } catch (e: Exception) {
+ Log.e(getString(R.string.failed_to_handle_currentuserdetailrequest), e.toString())
+ return false
+ }
+ }
+}
diff --git a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialsDataSource.kt b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialsDataSource.kt
index 324132c9..5e37d591 100644
--- a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialsDataSource.kt
+++ b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialsDataSource.kt
@@ -126,6 +126,18 @@ class CredentialsDataSource(
fun getPasskey(credId: String): PasskeyItem? {
return myVaultDao.getPasskey(credId)
}
+
+ suspend fun getAllPasskeysForUser(userId: String): List? {
+ return myVaultDao.getAllPasskeysForUser(userId)
+ }
+
+ suspend fun hidePasskey(passkey: PasskeyItem) {
+ myVaultDao.updatePasskey(passkey.copy(hidden = true))
+ }
+
+ suspend fun unhidePasskey(passkey: PasskeyItem) {
+ myVaultDao.updatePasskey(passkey.copy(hidden = false))
+ }
}
data class PasswordMetaData(
diff --git a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialsRepository.kt b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialsRepository.kt
index a2720f73..94cfc2b2 100644
--- a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialsRepository.kt
+++ b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/CredentialsRepository.kt
@@ -228,36 +228,38 @@ class CredentialsRepository(
val credentials = credentialsDataSource.credentialsForSite(request.rpId) ?: return false
val passkeys = credentials.passkeys
- for (passkey in passkeys) {
- val data = Bundle()
- data.putString("requestJson", option.requestJson)
- data.putString("credId", passkey.credId)
-
- // Create a PendingIntent to launch the activity that will handle the passkey retrieval
- val pendingIntent = createNewPendingIntent(
- "",
- GET_PASSKEY_INTENT,
- data,
- )
+ passkeys
+ .filter { !it.hidden }
+ .forEach { passkey ->
+ val data = Bundle()
+ data.putString("requestJson", option.requestJson)
+ data.putString("credId", passkey.credId)
+
+ // Create a PendingIntent to launch the activity that will handle the passkey retrieval
+ val pendingIntent = createNewPendingIntent(
+ "",
+ GET_PASSKEY_INTENT,
+ data,
+ )
- // Create a PublicKeyCredentialEntry object to represent the passkey
- val entryBuilder =
- configurePublicKeyCredentialEntryBuilder(passkey, pendingIntent, option)
+ // Create a PublicKeyCredentialEntry object to represent the passkey
+ val entryBuilder =
+ configurePublicKeyCredentialEntryBuilder(passkey, pendingIntent, option)
+
+ // Configure biometric prompt data
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ entryBuilder.setBiometricPromptData(
+ BiometricPromptData(
+ cryptoObject = null,
+ allowedAuthenticators = allowedAuthenticator,
+ ),
+ )
+ }
- // Configure biometric prompt data
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
- entryBuilder.setBiometricPromptData(
- BiometricPromptData(
- cryptoObject = null,
- allowedAuthenticators = allowedAuthenticator,
- ),
- )
+ val entry = entryBuilder.build()
+ // Add the entry to the response builder.
+ responseBuilder.addCredentialEntry(entry)
}
-
- val entry = entryBuilder.build()
- // Add the entry to the response builder.
- responseBuilder.addCredentialEntry(entry)
- }
} catch (e: IOException) {
return false
}
diff --git a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/PasskeyItem.kt b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/PasskeyItem.kt
index fe355daf..8ce7bbfd 100644
--- a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/PasskeyItem.kt
+++ b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/PasskeyItem.kt
@@ -31,6 +31,7 @@ import androidx.room.PrimaryKey
* @property credPrivateKey The private key
* @property siteId The ID of the site
* @property lastUsedTimeMs The last time the passkey item was used
+ * @property hidden Whether a passkey is hidden from the end user or not
*/
@Entity(
tableName = "passkeys",
@@ -47,4 +48,5 @@ data class PasskeyItem(
@ColumnInfo(name = "credPrivateKey") val credPrivateKey: String,
@ColumnInfo(name = "siteId") val siteId: Long,
@ColumnInfo(name = "lastUsedTimeMs") val lastUsedTimeMs: Long,
+ @ColumnInfo(name = "hidden") val hidden: Boolean = false,
)
diff --git a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/room/MyVaultDatabase.kt b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/room/MyVaultDatabase.kt
index bccf7e02..c32818af 100644
--- a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/room/MyVaultDatabase.kt
+++ b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/data/room/MyVaultDatabase.kt
@@ -34,7 +34,7 @@ import kotlinx.coroutines.flow.Flow
PasswordItem::class,
PasskeyItem::class,
],
- version = 7,
+ version = 8,
)
abstract class MyVaultDatabase : RoomDatabase() {
abstract fun myVaultDao(): MyVaultDao
@@ -88,4 +88,7 @@ interface MyVaultDao {
@Query("SELECT * from passkeys WHERE credId = :credId")
fun getPasskey(credId: String): PasskeyItem?
+
+ @Query("SELECT * from passkeys WHERE uid = :userId")
+ suspend fun getAllPasskeysForUser(userId: String): List?
}
diff --git a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/ui/MainActivity.kt b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/ui/MainActivity.kt
index 59e8dc8c..f95f7799 100644
--- a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/ui/MainActivity.kt
+++ b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/ui/MainActivity.kt
@@ -20,12 +20,19 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.core.view.WindowCompat
+import com.example.android.authentication.myvault.R
+import com.example.android.authentication.myvault.createNotificationChannel
import com.example.android.authentication.myvault.ui.theme.MyVaultTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
+ createNotificationChannel(
+ getString(R.string.notification_channel_name),
+ getString(R.string.notification_channel_description),
+ )
+
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
MyVaultTheme {
diff --git a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/ui/home/ShowCredentialsScreen.kt b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/ui/home/ShowCredentialsScreen.kt
index 9be1596f..eb633694 100644
--- a/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/ui/home/ShowCredentialsScreen.kt
+++ b/CredentialProvider/MyVault/app/src/main/java/com/example/android/authentication/myvault/ui/home/ShowCredentialsScreen.kt
@@ -191,7 +191,7 @@ private fun CredentialsEntry(
horizontalAlignment = Alignment.CenterHorizontally,
) {
- items(site.passkeys) {
+ items(site.passkeys.filter { !it.hidden }) {
PasskeyEntry(
passkey = it,
onPasskeyDelete = onPasskeyDelete,
diff --git a/CredentialProvider/MyVault/app/src/main/res/values/strings.xml b/CredentialProvider/MyVault/app/src/main/res/values/strings.xml
index 31e718dc..8fb460d7 100644
--- a/CredentialProvider/MyVault/app/src/main/res/values/strings.xml
+++ b/CredentialProvider/MyVault/app/src/main/res/values/strings.xml
@@ -69,4 +69,15 @@
Unexpected create request found in intent
Unknown Failure
Could not retrieve GPM allowlist
+ Credential deleted successfully
+ The provider received the SignalUnknown request and thus deleted the credential
+ Credentials List updated successfully
+ The provider received the AcceptedCredentialIds request and thus updated the list of user credentials
+ User details updated successfully
+ The provider received the CurrentUserDetails request and thus updated the user details in the credential
+ Notification channel used for testing Signal APIs. Apps pushes a notification if a Signal from RP is received
+ Signal API notification channel
+ Failed to handle UnknownCredentialRequest
+ Failed to handle AcceptedCredentialsRequest
+ Failed to handle CurrentUserDetailRequest
diff --git a/CredentialProvider/MyVault/app/src/main/res/xml/provider.xml b/CredentialProvider/MyVault/app/src/main/res/xml/provider.xml
index b68190f5..8925b681 100644
--- a/CredentialProvider/MyVault/app/src/main/res/xml/provider.xml
+++ b/CredentialProvider/MyVault/app/src/main/res/xml/provider.xml
@@ -19,5 +19,7 @@
+
+
diff --git a/CredentialProvider/MyVault/build.gradle.kts b/CredentialProvider/MyVault/build.gradle.kts
index 0646386f..0c18ab07 100644
--- a/CredentialProvider/MyVault/build.gradle.kts
+++ b/CredentialProvider/MyVault/build.gradle.kts
@@ -3,4 +3,5 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.devtools.ksp) apply false
+ alias(libs.plugins.compose.compiler) apply false
}
diff --git a/CredentialProvider/MyVault/gradle/libs.versions.toml b/CredentialProvider/MyVault/gradle/libs.versions.toml
index 06da1d2a..2628a0f4 100644
--- a/CredentialProvider/MyVault/gradle/libs.versions.toml
+++ b/CredentialProvider/MyVault/gradle/libs.versions.toml
@@ -1,7 +1,7 @@
[versions]
agp = "8.8.1"
-ksp = "1.9.22-1.0.17"
-kotlin = "1.9.22"
+ksp = "2.1.20-2.0.1"
+kotlin = "2.1.20"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
@@ -9,11 +9,12 @@ espressoCore = "3.6.1"
lifecycleRuntime = "2.8.7"
activityCompose = "1.10.0"
composeBom = "2025.02.00"
-credentials = "1.5.0-rc01"
-room = "2.6.1"
+credentials = "1.6.0-alpha05"
+room = "2.7.2"
biometrics = "1.2.0-alpha05"
accompanist = "0.28.0"
navigation = "2.8.7"
+providerevents = "1.0.0-alpha02"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -38,6 +39,8 @@ androidx-room-compiler = { group = "androidx.room", name = "room-compiler", vers
androidx-biometrics = { group = "androidx.biometric", name = "biometric", version.ref = "biometrics" }
androidx-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
google-accompanist = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" }
+provider-events = { group = "androidx.credentials.providerevents", name = "providerevents", version.ref = "providerevents" }
+provider-events-ps = { group = "androidx.credentials.providerevents", name = "providerevents-play-services", version.ref = "providerevents" }
@@ -45,6 +48,7 @@ google-accompanist = { group = "com.google.accompanist", name = "accompanist-sys
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
diff --git a/CredentialProvider/MyVault/settings.gradle.kts b/CredentialProvider/MyVault/settings.gradle.kts
index 2db4150d..1cab8b7c 100644
--- a/CredentialProvider/MyVault/settings.gradle.kts
+++ b/CredentialProvider/MyVault/settings.gradle.kts
@@ -21,4 +21,3 @@ dependencyResolutionManagement {
rootProject.name = "MyVault"
include(":app")
-
\ No newline at end of file