diff options
| author | 2023-09-26 17:45:41 +0000 | |
|---|---|---|
| committer | 2023-09-26 17:45:41 +0000 | |
| commit | 68922d30b6f54ec1b8b8a9123b68e0895ca53e3c (patch) | |
| tree | 2ae088ff25c8154ea5411d48885286fb3165a9e2 | |
| parent | d62cfd94edb9c017ac9f6cb175e818e07b7c8661 (diff) | |
| parent | c2a7e80630bc02c9db3727e92b54dfe37d180e58 (diff) | |
Merge "Call credman after parsing requests from hints" into main
| -rw-r--r-- | packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt | 162 |
1 files changed, 161 insertions, 1 deletions
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 943c2b4b3a5b..ba88484518aa 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -16,23 +16,183 @@ package com.android.credentialmanager.autofill +import android.app.assist.AssistStructure +import android.content.Context +import android.credentials.GetCredentialRequest +import android.credentials.CredentialManager +import android.credentials.GetCandidateCredentialsResponse +import android.credentials.CredentialOption +import android.credentials.GetCandidateCredentialsException +import android.os.Bundle import android.os.CancellationSignal +import android.os.OutcomeReceiver +import android.service.autofill.FillRequest import android.service.autofill.AutofillService +import android.service.autofill.FillResponse import android.service.autofill.FillCallback -import android.service.autofill.FillRequest import android.service.autofill.SaveRequest import android.service.autofill.SaveCallback +import android.util.Log +import org.json.JSONObject +import java.util.concurrent.Executors class CredentialAutofillService : AutofillService() { + + companion object { + private const val TAG = "CredAutofill" + + private const val CRED_HINT_PREFIX = "credential=" + private const val REQUEST_DATA_KEY = "requestData" + private const val CANDIDATE_DATA_KEY = "candidateQueryData" + private const val SYS_PROVIDER_REQ_KEY = "isSystemProviderRequired" + private const val CRED_OPTIONS_KEY = "credentialOptions" + private const val TYPE_KEY = "type" + } + + private val credentialManager: CredentialManager = + getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager + override fun onFillRequest( request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback ) { + val context = request.fillContexts + val structure = context[context.size - 1].structure + val callingPackage = structure.activityComponent.packageName + Log.i(TAG, "onFillRequest called for $callingPackage") + + val getCredRequest: GetCredentialRequest? = getCredManRequest(structure) + if (getCredRequest == null) { + callback.onFailure("No credential manager request found") + return + } + + val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse, + GetCandidateCredentialsException> { + override fun onResult(result: GetCandidateCredentialsResponse) { + Log.i(TAG, "getCandidateCredentials onResponse") + val fillResponse: FillResponse? = convertToFillResponse(result, request) + callback.onSuccess(fillResponse) + } + + override fun onError(error: GetCandidateCredentialsException) { + Log.i(TAG, "getCandidateCredentials onError") + callback.onFailure("error received from credential manager ${error.message}") + } + } + + credentialManager.getCandidateCredentials( + getCredRequest, + callingPackage, + CancellationSignal(), + Executors.newSingleThreadExecutor(), + outcome + ) + } + + private fun convertToFillResponse( + getCredResponse: GetCandidateCredentialsResponse, + filLRequest: FillRequest + ): FillResponse? { TODO("Not yet implemented") } override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { TODO("Not yet implemented") } + + private fun getCredManRequest(structure: AssistStructure): GetCredentialRequest? { + val credentialOptions: MutableList<CredentialOption> = mutableListOf() + traverseStructure(structure, credentialOptions) + + if (credentialOptions.isNotEmpty()) { + return GetCredentialRequest.Builder(Bundle.EMPTY) + .setCredentialOptions(credentialOptions) + .build() + } + return null + } + + private fun traverseStructure( + structure: AssistStructure, + cmRequests: MutableList<CredentialOption> + ) { + val windowNodes: List<AssistStructure.WindowNode> = + structure.run { + (0 until windowNodeCount).map { getWindowNodeAt(it) } + } + + windowNodes.forEach { windowNode: AssistStructure.WindowNode -> + traverseNode(windowNode.rootViewNode, cmRequests) + } + } + + private fun traverseNode( + viewNode: AssistStructure.ViewNode?, + cmRequests: MutableList<CredentialOption> + ) { + val options = getCredentialOptionsFromViewNode(viewNode) + cmRequests.addAll(options) + + val children: List<AssistStructure.ViewNode>? = + viewNode?.run { + (0 until childCount).map { getChildAt(it) } + } + + children?.forEach { childNode: AssistStructure.ViewNode -> + traverseNode(childNode, cmRequests) + } + } + + private fun getCredentialOptionsFromViewNode(viewNode: AssistStructure.ViewNode?): + List<CredentialOption> { + // TODO(b/293945193) Replace with isCredential check from viewNode + val credentialHints: MutableList<String> = mutableListOf() + if (viewNode != null && viewNode.autofillHints != null) { + for (hint in viewNode.autofillHints!!) { + if (hint.startsWith(CRED_HINT_PREFIX)) { + credentialHints.add(hint.substringAfter(CRED_HINT_PREFIX)) + } + } + } + + val credentialOptions: MutableList<CredentialOption> = mutableListOf() + for (credentialHint in credentialHints) { + convertJsonToCredentialOption(credentialHint).let { credentialOptions.addAll(it) } + } + return credentialOptions + } + + private fun convertJsonToCredentialOption(jsonString: String): List<CredentialOption> { + // TODO(b/302000646) Move this logic to jetpack so that is consistent + // with building the json + val credentialOptions: MutableList<CredentialOption> = mutableListOf() + + val json = JSONObject(jsonString) + val options = json.getJSONArray(CRED_OPTIONS_KEY) + for (i in 0 until options.length()) { + val option = options.getJSONObject(i) + + credentialOptions.add(CredentialOption( + option.getString(TYPE_KEY), + convertJsonToBundle(option.getJSONObject(REQUEST_DATA_KEY)), + convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY)), + option.getBoolean(SYS_PROVIDER_REQ_KEY), + )) + } + return credentialOptions + } + + private fun convertJsonToBundle(json: JSONObject): Bundle { + val result = Bundle() + json.keys().forEach { + val v = json.get(it) + when (v) { + is String -> result.putString(it, v) + is Boolean -> result.putBoolean(it, v) + } + } + return result + } }
\ No newline at end of file |