diff --git a/identity/credentialmanager/src/main/AndroidManifest.xml b/identity/credentialmanager/src/main/AndroidManifest.xml index 09a3c8397..70391952b 100644 --- a/identity/credentialmanager/src/main/AndroidManifest.xml +++ b/identity/credentialmanager/src/main/AndroidManifest.xml @@ -1,4 +1,20 @@ + + + + + \ No newline at end of file diff --git a/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/CredentialManagerFunctions.kt b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/CredentialManagerFunctions.kt new file mode 100644 index 000000000..e85f61639 --- /dev/null +++ b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/CredentialManagerFunctions.kt @@ -0,0 +1,181 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.example.identity.credentialmanager + +import android.content.Context +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.credentials.CredentialManager +import androidx.credentials.CustomCredential +import androidx.credentials.GetCredentialRequest +import androidx.credentials.GetCredentialResponse +import androidx.credentials.GetPasswordOption +import androidx.credentials.GetPublicKeyCredentialOption +import androidx.credentials.PasswordCredential +import androidx.credentials.PublicKeyCredential +import androidx.credentials.exceptions.GetCredentialException +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.runBlocking +import org.json.JSONObject + +class CredentialManagerFunctions ( + context: Context, +) { + // [START android_identity_initialize_credman] + // Use your app or activity context to instantiate a client instance of + // CredentialManager. + private val credentialManager = CredentialManager.create(context) + // [END android_identity_initialize_credman] + + // Placeholder for TAG log value. + val TAG = "" + /** + * Retrieves a passkey from the credential manager. + * + * @param creationResult The result of the passkey creation operation. + * @param context The activity context from the Composable, to be used in Credential Manager APIs + * @return The [GetCredentialResponse] object containing the passkey, or null if an error occurred. + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun signInFlow( + creationResult: JSONObject, + activityContext: Context, + ) { + val requestJson = creationResult.toString() + // [START android_identity_get_password_passkey_options] + // Retrieves the user's saved password for your app from their + // password provider. + val getPasswordOption = GetPasswordOption() + + // Get passkey from the user's public key credential provider. + val getPublicKeyCredentialOption = GetPublicKeyCredentialOption( + requestJson = requestJson + ) + // [END android_identity_get_password_passkey_options] + var result: GetCredentialResponse + // [START android_identity_get_credential_request] + val credentialRequest = GetCredentialRequest( + listOf(getPasswordOption, getPublicKeyCredentialOption), + ) + // [END android_identity_get_credential_request] + runBlocking { + // getPrepareCredential request + // [START android_identity_prepare_get_credential] + coroutineScope { + val response = credentialManager.prepareGetCredential( + GetCredentialRequest( + listOf( + getPublicKeyCredentialOption, + getPasswordOption + ) + ) + ) + } + // [END android_identity_prepare_get_credential] + // getCredential request without handling exception. + // [START android_identity_launch_sign_in_flow_1] + coroutineScope { + try { + result = credentialManager.getCredential( + // Use an activity-based context to avoid undefined system UI + // launching behavior. + context = activityContext, + request = credentialRequest + ) + handleSignIn(result) + } catch (e: GetCredentialException) { + // Handle failure + } + } + // [END android_identity_launch_sign_in_flow_1] + // getCredential request adding some exception handling. + // [START android_identity_handle_exceptions_no_credential] + coroutineScope { + try { + result = credentialManager.getCredential( + context = activityContext, + request = credentialRequest + ) + } catch (e: GetCredentialException) { + Log.e("CredentialManager", "No credential available", e) + } + } + // [END android_identity_handle_exceptions_no_credential] + } + } + + // [START android_identity_launch_sign_in_flow_2] + fun handleSignIn(result: GetCredentialResponse) { + // Handle the successfully returned credential. + val credential = result.credential + + when (credential) { + is PublicKeyCredential -> { + val responseJson = credential.authenticationResponseJson + // Share responseJson i.e. a GetCredentialResponse on your server to + // validate and authenticate + } + + is PasswordCredential -> { + val username = credential.id + val password = credential.password + // Use id and password to send to your server to validate + // and authenticate + } + + is CustomCredential -> { + // If you are also using any external sign-in libraries, parse them + // here with the utility functions provided. + if (credential.type == ExampleCustomCredential.TYPE) { + try { + val ExampleCustomCredential = + ExampleCustomCredential.createFrom(credential.data) + // Extract the required credentials and complete the authentication as per + // the federated sign in or any external sign in library flow + } catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) { + // Unlikely to happen. If it does, you likely need to update the dependency + // version of your external sign-in library. + Log.e(TAG, "Failed to parse an ExampleCustomCredential", e) + } + } else { + // Catch any unrecognized custom credential type here. + Log.e(TAG, "Unexpected type of credential") + } + } + else -> { + // Catch any unrecognized credential type here. + Log.e(TAG, "Unexpected type of credential") + } + } + } + // [END android_identity_launch_sign_in_flow_2] +} + +sealed class ExampleCustomCredential { + class ExampleCustomCredentialParsingException : Throwable() {} + + companion object { + fun createFrom(data: Bundle): PublicKeyCredential { + return PublicKeyCredential("") + } + + const val TYPE: String = "" + } +} \ No newline at end of file diff --git a/identity/credentialmanager/src/main/java/com/example/identity/MainActivity.kt b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/MainActivity.kt similarity index 100% rename from identity/credentialmanager/src/main/java/com/example/identity/MainActivity.kt rename to identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/MainActivity.kt diff --git a/identity/credentialmanager/src/main/java/com/example/identity/ui/theme/Color.kt b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/ui/theme/Color.kt similarity index 100% rename from identity/credentialmanager/src/main/java/com/example/identity/ui/theme/Color.kt rename to identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/ui/theme/Color.kt diff --git a/identity/credentialmanager/src/main/java/com/example/identity/ui/theme/Theme.kt b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/ui/theme/Theme.kt similarity index 100% rename from identity/credentialmanager/src/main/java/com/example/identity/ui/theme/Theme.kt rename to identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/ui/theme/Theme.kt diff --git a/identity/credentialmanager/src/main/java/com/example/identity/ui/theme/Type.kt b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/ui/theme/Type.kt similarity index 100% rename from identity/credentialmanager/src/main/java/com/example/identity/ui/theme/Type.kt rename to identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/ui/theme/Type.kt diff --git a/identity/credentialmanager/src/main/jsonSnippets.json b/identity/credentialmanager/src/main/jsonSnippets.json new file mode 100644 index 000000000..1ba353add --- /dev/null +++ b/identity/credentialmanager/src/main/jsonSnippets.json @@ -0,0 +1,66 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "snippets": [ + { + "DigitalAssetLinking": + // Digital asset linking + // [START android_identity_assetlinks_json] + [ + { + "relation": [ + "delegate_permission/common.handle_all_urls", + "delegate_permission/common.get_login_creds" + ], + "target": { + "namespace": "android_app", + "package_name": "com.example.android", + "sha256_cert_fingerprints": [ + SHA_HEX_VALUE + ] + } + } + ] + // [END android_identity_assetlinks_json] + }, + + // JSON request and response formats + // [START android_identity_format_json_request_passkey] + { + "challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo", + "allowCredentials": [], + "timeout": 1800000, + "userVerification": "required", + "rpId": "credential-manager-app-test.glitch.me" + }, + // [END android_identity_format_json_request_passkey] + + // [START android_identity_format_json_response_passkey] + { + "id": "KEDetxZcUfinhVi6Za5nZQ", + "type": "public-key", + "rawId": "KEDetxZcUfinhVi6Za5nZQ", + "response": { + "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9", + "authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA", + "signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ", + "userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0" + } + } + // [END android_identity_format_json_response_passkey] + ] +} \ No newline at end of file diff --git a/identity/credentialmanager/src/main/res/layout-v34/xmlsnippets.xml b/identity/credentialmanager/src/main/res/layout-v34/xmlsnippets.xml new file mode 100644 index 000000000..9be5cf21f --- /dev/null +++ b/identity/credentialmanager/src/main/res/layout-v34/xmlsnippets.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/identity/credentialmanager/src/main/res/values/strings.xml b/identity/credentialmanager/src/main/res/values/strings.xml index 3178959b9..8f5fb8e80 100644 --- a/identity/credentialmanager/src/main/res/values/strings.xml +++ b/identity/credentialmanager/src/main/res/values/strings.xml @@ -1,3 +1,10 @@ credentialmanager + // [START android_identity_assetlinks_app_association] + + [{ + \"include\": \"https://signin.example.com/.well-known/assetlinks.json\" + }] + + // [END android_identity_assetlinks_app_association] \ No newline at end of file