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