summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt162
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