diff options
author | Douglas Sigelbaum <sigelbaum@google.com> | 2017-05-31 00:30:47 -0700 |
---|---|---|
committer | Douglas Sigelbaum <sigelbaum@google.com> | 2017-05-31 11:45:41 -0700 |
commit | 389ba153ecf015cbff36764e9c07f12ac3a0353d (patch) | |
tree | 284aa0a3355b15801ab5a1e171ff541e1ee850ba /input/autofill/AutofillFramework/kotlinApp | |
parent | da2273924314181bb5e7e50fc52effc2f00aff1b (diff) | |
download | android-389ba153ecf015cbff36764e9c07f12ac3a0353d.tar.gz |
Changes to get Kotlin and Java autofill samples in sync.
Also fixed a crash when using datasetAuth. Impacted both samples.
Bug: 38182790
Test: manual
Change-Id: I1bfb00e3a22708d7bcbaade2aff3b00789499fe7
Diffstat (limited to 'input/autofill/AutofillFramework/kotlinApp')
20 files changed, 159 insertions, 353 deletions
diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/AndroidManifest.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/AndroidManifest.xml index 28d9c0b5..eb1f43c2 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/AndroidManifest.xml +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/AndroidManifest.xml @@ -32,7 +32,6 @@ android:taskAffinity=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> @@ -40,32 +39,17 @@ android:name=".app.LoginActivity" android:label="AF StandardLogin" android:taskAffinity=".LoginActivity"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> </activity> <activity android:name=".app.VirtualLoginActivity" android:label="AF VirtualLogin" android:taskAffinity=".VirtualLoginActivity"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> </activity> <activity android:name=".app.WelcomeActivity" /> <activity android:name=".app.CreditCardActivity" android:label="AF CreditCard" android:taskAffinity=".CreditCardActivity"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> </activity> <!-- Including launcher icon for Autofill Settings to convenience. @@ -78,7 +62,6 @@ android:taskAffinity=".SettingsActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.kt index 86dc9b25..bef16afe 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.kt +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.kt @@ -70,20 +70,21 @@ class CustomVirtualView(context: Context, attrs: AttributeSet) : View(context, a } override fun autofill(values: SparseArray<AutofillValue>) { - // User has just selected a Dataset from the list of Autofill suggestions and the Dataset's - // AutofillValue gets passed into this method. + // User has just selected a Dataset from the list of autofill suggestions. + // The Dataset is comprised of a list of AutofillValues, with each AutofillValue meant + // to fill a specific autofillable view. Now we have to update the UI based on the + // AutofillValues in the list. Log.d(TAG, "autofill(): " + values) for (i in 0..values.size() - 1) { val id = values.keyAt(i) val value = values.valueAt(i) - - mItems[id]?.let { - if (!it.editable) { - Log.w(TAG, "Item for autofillId $id is not editable: $it") - return@autofill + mItems[id]?.let { item -> + if (item.editable) { + // Set the item's text to the text wrapped in the AutofillValue. + item.text = value.textValue + } else { + Log.w(TAG, "Item for autofillId $id is not editable: $item") } - // Set the item's text to the text wrapped in the AutofillValue. - it.text = value.textValue } } postInvalidate() diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/VirtualLoginActivity.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/VirtualLoginActivity.kt index c6eb721f..52081afd 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/VirtualLoginActivity.kt +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/app/VirtualLoginActivity.kt @@ -51,6 +51,7 @@ class VirtualLoginActivity : AppCompatActivity() { if (valid) { val intent = WelcomeActivity.getStartActivityIntent(this@VirtualLoginActivity) startActivity(intent) + finish() } else { Toast.makeText(this, "Authentication failed.", Toast.LENGTH_SHORT).show() } diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/AuthActivity.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/AuthActivity.kt index 12be45bb..d102a7e9 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/AuthActivity.kt +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/AuthActivity.kt @@ -97,8 +97,8 @@ class AuthActivity : Activity() { } else { val datasetName = intent.getStringExtra(EXTRA_DATASET_NAME) clientFormDataMap?.let { - it[datasetName]?.let { - AutofillHelper.newDataset(this, autofillFields, it)?.let(this::setDatasetIntent) + it[datasetName]?.let { clientFormData -> + AutofillHelper.newDataset(this, autofillFields, clientFormData, false)?.let(this::setDatasetIntent) } } } @@ -115,7 +115,7 @@ class AuthActivity : Activity() { companion object { // Unique autofillId for dataset intents. - private var sDatasetPendingIntentId = 0 + private var datasetPendingIntentId = 0 internal fun getAuthIntentSenderForResponse(context: Context): IntentSender { val intent = Intent(context, AuthActivity::class.java) @@ -127,7 +127,7 @@ class AuthActivity : Activity() { val intent = Intent(context, AuthActivity::class.java) intent.putExtra(EXTRA_DATASET_NAME, datasetName) intent.putExtra(EXTRA_FOR_RESPONSE, false) - return PendingIntent.getActivity(context, ++sDatasetPendingIntentId, intent, + return PendingIntent.getActivity(context, ++datasetPendingIntentId, intent, PendingIntent.FLAG_CANCEL_CURRENT).intentSender } } diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/AutofillHelper.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/AutofillHelper.kt index b7470d7b..58666ef4 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/AutofillHelper.kt +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/AutofillHelper.kt @@ -37,10 +37,14 @@ object AutofillHelper { * client View. */ fun newDataset(context: Context, autofillFields: AutofillFieldsCollection, - clientFormData: ClientFormData): Dataset? { + clientFormData: ClientFormData, datasetAuth: Boolean): Dataset? { clientFormData.datasetName?.let { datasetName -> val datasetBuilder = Dataset.Builder(newRemoteViews(context.packageName, datasetName)) val setValueAtLeastOnce = clientFormData.applyToFields(autofillFields, datasetBuilder) + if (datasetAuth) { + val sender = AuthActivity.getAuthIntentSenderForDataset(context, datasetName) + datasetBuilder.setAuthentication(sender) + } if (setValueAtLeastOnce) { return datasetBuilder.build() } @@ -65,18 +69,8 @@ object AutofillHelper { clientFormDataMap?.keys?.let { datasetNames -> for (datasetName in datasetNames) { clientFormDataMap[datasetName]?.let { clientFormData -> - if (datasetAuth) { - clientFormData.datasetName?.let { - val datasetBuilder = Dataset.Builder(newRemoteViews(context.packageName, it)) - val sender = AuthActivity - .getAuthIntentSenderForDataset(context, it) - datasetBuilder.setAuthentication(sender) - responseBuilder.addDataset(datasetBuilder.build()) - } - } else { - val dataset = newDataset(context, autofillFields, clientFormData) - dataset?.let(responseBuilder::addDataset) - } + val dataset = newDataset(context, autofillFields, clientFormData, datasetAuth) + dataset?.let(responseBuilder::addDataset) } } } diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/MyAutofillService.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/MyAutofillService.kt index 9bc2a8c2..41712bd1 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/MyAutofillService.kt +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/MyAutofillService.kt @@ -76,7 +76,8 @@ class MyAutofillService : AutofillService() { callback.onSuccess(responseBuilder.build()) } else { val datasetAuth = MyPreferences.isDatasetAuth(this) - val clientFormDataMap = SharedPrefsAutofillRepository.getClientFormData(this, autofillFields.focusedAutofillHints, autofillFields.allAutofillHints) + val clientFormDataMap = SharedPrefsAutofillRepository.getClientFormData(this, + autofillFields.focusedAutofillHints, autofillFields.allAutofillHints) val response = AutofillHelper.newResponse(this, datasetAuth, autofillFields, clientFormDataMap) callback.onSuccess(response) } diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/StructureParser.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/StructureParser.kt index e05a2d51..2cb37eea 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/StructureParser.kt +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/StructureParser.kt @@ -22,12 +22,12 @@ import com.example.android.autofillframework.CommonUtil.TAG import com.example.android.autofillframework.service.model.AutofillField import com.example.android.autofillframework.service.model.AutofillFieldsCollection import com.example.android.autofillframework.service.model.ClientFormData -import com.example.android.autofillframework.service.model.MutableAutofillValue +import com.example.android.autofillframework.service.model.SavableAutofillData /** * Parser for an AssistStructure object. This is invoked when the Autofill Service receives an - * AssistStructure from the client Activity, representing its View hierarchy. In this - * sample, it parses the hierarchy and records + * AssistStructure from the client Activity, representing its View hierarchy. In this sample, it + * parses the hierarchy and collects autofill metadata from {@link ViewNode}s along the way. */ internal class StructureParser(private val mStructure: AssistStructure) { val autofillFields = AutofillFieldsCollection() @@ -64,7 +64,7 @@ internal class StructureParser(private val mStructure: AssistStructure) { autofillFields.add(AutofillField(viewNode)) } else { clientFormData.setAutofillValuesForHints(viewNode.autofillHints, - MutableAutofillValue(viewNode)) + SavableAutofillData(viewNode)) } } } diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/datasource/SharedPrefsAutofillRepository.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/datasource/SharedPrefsAutofillRepository.kt index a7eadc96..e2fb8708 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/datasource/SharedPrefsAutofillRepository.kt +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/datasource/SharedPrefsAutofillRepository.kt @@ -24,9 +24,9 @@ import com.google.gson.reflect.TypeToken /** - * Singleton autofill data repository, that stores autofill fields to SharedPreferences. - * DISCLAIMER, you should not store sensitive fields like user data unencrypted. This is only done - * here for simplicity and learning purposes. + * Singleton autofill data repository that stores autofill fields to SharedPreferences. + * Disclaimer: you should not store sensitive fields like user data unencrypted. This is done + * here only for simplicity and learning purposes. */ object SharedPrefsAutofillRepository : AutofillRepository { private val SHARED_PREF_KEY = "com.example.android.autofillframework.service" @@ -46,9 +46,13 @@ object SharedPrefsAutofillRepository : AutofillRepository { val type = object : TypeToken<ClientFormData>() {}.type Gson().fromJson<ClientFormData>(clientFormDataString, type)?.let { clientFormData -> if (clientFormData.helpsWithHints(focusedAutofillHints)) { + // Saved data has data relevant to at least 1 of the hints associated with the + // View in focus. hasDataForFocusedAutofillHints = true clientFormData.datasetName?.let { datasetName -> if (clientFormData.helpsWithHints(allAutofillHints)) { + // Saved data has data relevant to at least 1 of these hints associated with any + // of the Views in the hierarchy. clientFormDataMap.put(datasetName, clientFormData) } } diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/model/ClientFormData.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/model/ClientFormData.kt index 78980aaa..2dc844ae 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/model/ClientFormData.kt +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/model/ClientFormData.kt @@ -28,16 +28,16 @@ import java.util.HashMap * dataset name associated with it. */ class ClientFormData constructor(var datasetName: String? = null, - private val hintMap: HashMap<String, MutableAutofillValue> = HashMap<String, MutableAutofillValue>()) { + private val hintMap: HashMap<String, SavableAutofillData> = HashMap<String, SavableAutofillData>()) { private val TAG = "ClientFormData" /** * Sets values for a list of autofillHints. */ - fun setAutofillValuesForHints(autofillHints: Array<String>, autofillValue: MutableAutofillValue) { + fun setAutofillValuesForHints(autofillHints: Array<String>, autofillData: SavableAutofillData) { autofillHints.forEach { hint -> - hintMap[hint] = autofillValue + hintMap[hint] = autofillData } } @@ -86,6 +86,10 @@ class ClientFormData constructor(var datasetName: String? = null, return setValueAtLeastOnce } + /** + * Returns whether this model contains autofill data that is relevant to any of the + * autofillHints that are passed in. + */ fun helpsWithHints(autofillHints: List<String>): Boolean { for (autofillHint in autofillHints) { hintMap[autofillHint]?.let { savedAutofillValue -> diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/model/MutableAutofillValue.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/model/SavableAutofillData.kt index 702a1358..44baebfa 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/model/MutableAutofillValue.kt +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/service/model/SavableAutofillData.kt @@ -19,9 +19,9 @@ import android.app.assist.AssistStructure import android.view.autofill.AutofillValue /** - * Mutable, JSON serializable data class containing the same data as an [AutofillValue]. + * JSON serializable data class containing the same data as an [AutofillValue]. */ -class MutableAutofillValue(viewNode: AssistStructure.ViewNode) { +class SavableAutofillData(viewNode: AssistStructure.ViewNode) { var textValue: String? = null var dateValue: Long? = null var toggleValue: Boolean? = null diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-sw600dp/template-dimens.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-sw600dp/template-dimens.xml deleted file mode 100644 index 22074a2b..00000000 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-sw600dp/template-dimens.xml +++ /dev/null @@ -1,24 +0,0 @@ -<!-- - Copyright 2013 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 - - http://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. - --> - -<resources> - - <!-- Semantic definitions --> - - <dimen name="horizontal_page_margin">@dimen/margin_huge</dimen> - <dimen name="vertical_page_margin">@dimen/margin_medium</dimen> - -</resources> diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-sw600dp/template-styles.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-sw600dp/template-styles.xml deleted file mode 100644 index 03d19741..00000000 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-sw600dp/template-styles.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - Copyright 2013 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 - - http://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. - --> - -<resources> - - <style name="Widget.SampleMessage"> - <item name="android:textAppearance">?android:textAppearanceLarge</item> - <item name="android:lineSpacingMultiplier">1.2</item> - <item name="android:shadowDy">-6.5</item> - </style> - -</resources> diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-v11/template-styles.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-v11/template-styles.xml deleted file mode 100644 index 8c1ea66f..00000000 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-v11/template-styles.xml +++ /dev/null @@ -1,22 +0,0 @@ -<!-- - Copyright 2013 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 - - http://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. - --> - -<resources> - - <!-- Activity themes --> - <style name="Theme.Base" parent="android:Theme.Holo.Light" /> - -</resources> diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-v21/base-colors.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-v21/base-colors.xml deleted file mode 100644 index 8b6ec3f8..00000000 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-v21/base-colors.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - Copyright 2013 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 - - http://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. ---> - -<resources> - - -</resources> diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-v21/base-template-styles.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-v21/base-template-styles.xml deleted file mode 100644 index c778e4f9..00000000 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values-v21/base-template-styles.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - Copyright 2013 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 - - http://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. ---> - -<resources> - - <!-- Activity themes --> - <style name="Theme.Base" parent="android:Theme.Material.Light"> - </style> - -</resources> diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/base-strings.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/base-strings.xml deleted file mode 100644 index ddf8b07d..00000000 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/base-strings.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - Copyright 2013 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 - - http://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. ---> - -<resources> - <string name="app_name">AutofillFramework</string> - <string name="intro_message"> - <![CDATA[ - - - This sample app demos the Autofill feature introduced in Android O. - - - ]]> - </string> -</resources> diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/strings.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/strings.xml index b5611ae1..f9448a74 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/strings.xml +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ --> <resources> + <string name="app_name">Autofill Sample</string> <string name="settings_name">Autofill Settings</string> <string name="username_label">Username</string> <string name="password_label">Password</string> diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/template-dimens.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/template-dimens.xml deleted file mode 100644 index 39e710b5..00000000 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/template-dimens.xml +++ /dev/null @@ -1,32 +0,0 @@ -<!-- - Copyright 2013 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 - - http://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. - --> - -<resources> - - <!-- Define standard dimensions to comply with Holo-style grids and rhythm. --> - - <dimen name="margin_tiny">4dp</dimen> - <dimen name="margin_small">8dp</dimen> - <dimen name="margin_medium">16dp</dimen> - <dimen name="margin_large">32dp</dimen> - <dimen name="margin_huge">64dp</dimen> - - <!-- Semantic definitions --> - - <dimen name="horizontal_page_margin">@dimen/margin_medium</dimen> - <dimen name="vertical_page_margin">@dimen/margin_medium</dimen> - -</resources> diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/template-styles.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/template-styles.xml deleted file mode 100644 index 6e7d593d..00000000 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/template-styles.xml +++ /dev/null @@ -1,42 +0,0 @@ -<!-- - Copyright 2013 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 - - http://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. - --> - -<resources> - - <!-- Activity themes --> - - <style name="Theme.Base" parent="android:Theme.Light" /> - - <style name="Theme.Sample" parent="Theme.Base" /> - - <style name="AppTheme" parent="Theme.Sample" /> - <!-- Widget styling --> - - <style name="Widget" /> - - <style name="Widget.SampleMessage"> - <item name="android:textAppearance">?android:textAppearanceMedium</item> - <item name="android:lineSpacingMultiplier">1.1</item> - </style> - - <style name="Widget.SampleMessageTile"> - <item name="android:background">@drawable/tile</item> - <item name="android:shadowColor">#7F000000</item> - <item name="android:shadowDy">-3.5</item> - <item name="android:shadowRadius">2</item> - </style> - -</resources> diff --git a/input/autofill/AutofillFramework/kotlinApp/README.md b/input/autofill/AutofillFramework/kotlinApp/README.md index 53e4a7f0..9b060561 100644 --- a/input/autofill/AutofillFramework/kotlinApp/README.md +++ b/input/autofill/AutofillFramework/kotlinApp/README.md @@ -1,63 +1,98 @@ -Android AutofillFramework Sample (Kotlin) + +Android AutofillFramework Sample =================================== This sample demonstrates the use of the Autofill Framework. It includes implementations of client -Activities that want to be autofilled, and a Service that can provide autofill data to client -Activities. For simplicity, this sample's service uses mock data to autofill what it thinks are -username and password text fields. +Activities with views that should be autofilled, and a Service that can provide autofill data to +client Activities. Introduction ------------ This sample demonstrates the use of the Autofill framework from the service side and the client -side. In practice, only a small handful of apps will develop Autofill services because a device will -only have one service as default at a time. However, all apps targeting O with any autofillable -fields should follow the necessary steps to ensure their Views can be autofilled. Most of the time, -there is little to no extra code involved, but the use of custom views and views with virtual child -views requires more work. - -The sample's Autofill service is implemented to parse the client's view hierarchy in search of text -fields that it has data for. If such text fields exist in the hierarchy, the service sends data -suggestions to the client to fill in those text fields. In this basic sample, it will only look for -views whose resource IDs are "usernameField" and "passwordField" and will send mock credential data -accordingly. A real Autofill service would attempt to autofill more than just login credentials and -would be able to fill in other view types in addition to text fields (e.g. spinners, checkboxes, -etc.). It would also use more advanced heuristics to determine what data to send to which views. - -To set the device's default Autofill service to the one in the sample, edit -**Settings** > **Apps & Notifications** > **Default Apps** > **Auto-fill app** and select the -sample app. To edit the service's settings, open the **Autofill Settings** launcher icon. Here, you -can set whether you want to enable authentication on the entire Autofill Response or just on -individual datasets. You can also set the number of mock datasets that are sent to the client app. - -The client side of the app has two Activities that each have a username field and a password field. -One of the Activities uses standard views and the other Activity uses a custom view with virtual -children. The standard views do not require any extra code to allow autofill. The following code -example shows the `View` method you have to override in order to provide view hierarchy data to the -Autofill service. This is triggered when the `View` goes into focus and Android invokes an Autofill -request. - -```java -/* -This method is responsible for building the ViewStructure that gets passed to the AutoFillService -by the framework when it is time to find Autofill suggestions. To do this, it should traverse -through its view hierarchy and add views to the ViewStructure on the way. -*/ -@Override -public void onProvideAutoFillVirtualStructure(ViewStructure structure, int flags) { - structure.setClassName(getClass().getName()); - int childrenSize = mItems.size(); - int index = structure.addChildCount(childrenSize); - for (int i = 0; i < childrenSize; i++) { - Item item = mItems.valueAt(i); - ViewStructure child = structure.newChild(index, item.id, flags); - child.setSanitized(item.sanitized); - child.setText(item.text); - child.setAutoFillValue(AutoFillValue.forText(item.text)); - child.setFocused(item.line.focused); - child.setId(item.id, getContext().getPackageName(), null, item.line.idEntry); - index++; - } +side. In practice, only a small handful of apps will develop Autofill services because a device +will only have one service as default at a time, and there is just a small number of 3rd-party apps +providing these services (typically password managers). However, all apps targeting O with any +autofillable fields should follow the necessary steps to 1) ensure their views can be autofilled +and 2) optimize their autofill performance. Most of the time, there is little to no extra code +involved, but the use of custom views and views with virtual child views requires more work. + +The sample's Autofill service is implemented to parse the client's view hierarchy in search of +autofillable fields that it has data for. If such fields exist in the hierarchy, the service sends +data suggestions to the client to autofill those fields. The client uses the following attributes +to specify autofill properties: `importantForAutofill`, `autofillHints`, and `autofillType`. +`importantForAutofill` specifies whether the view is autofillable. `autofillHints` is a list of +strings that hint to the service **what** data to fill the view with. This sample service only +supports the hints listed [here](https://developer.android.com/reference/android/view/View.html#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE) +with the prefix AUTOFILL_HINT_*. `autofillType` tells the service the type of data it expects to +receive (i.e. a list index, a date, or a string). Specifying `autofillType` is only necessary +when implementing a custom view since all of the provided widgets in the UI toolkit do this for you. + +To set the device's default Autofill service to the one in the sample, edit **Settings** > +**System** > **Languages & Input** > **Advanced** > **Auto-fill service** and select the sample +app. To edit the service's settings, tap the settings icon next to the **Auto-fill service** list +item or open the **Autofill Settings** launcher icon.. Here, you can set whether you want to enable +authentication on the entire autofill Response or just on individual autofill datasets. You should +also set the master password to “unlock” authenticated autofill data with. + +**Note:** This sample service stores all autofill data in SharedPreferences and thus is not secure. +Be careful about what you store when experimenting with the sample because anyone with root access +to your device will be able to view your autofill data. + +The client side of the app has three Activities that each have autofillable fields. The first +Activity uses standard views to comprise a login form. Very little needs to be done by the client +app to ensure the views get autofilled properly. The second Activity uses a custom view with +virtual children, meaning some autofillable child views are not known to the View hierarchy to be +child views. Supporting autofill on these child views is a little more involved. + +The following code snippet shows how to signal to the autofill service that a specific +autofillable virtual view has come into focus: + +```kotlin +class CustomVirtualView(context: Context, attrs: AttributeSet) : View(context, attrs) { +... + // Cache AutofillManager system service + private val autofillManager: AutofillManager = context.getSystemService(AutofillManager::class.java) +... + // Notify service which virtual view has come into focus. + autofillManager.notifyViewEntered(this@CustomVirtualView, id, absBounds) +... + // Notify service that a virtual view has left focus. + autofillManager.notifyViewExited(this@CustomVirtualView, id) +} +``` + +Now that the autofillable view has signaled to the service that it has been autofilled, it needs +to provide the virtual view hierarchy to the Autofill service. This is done out of the box for +views part of the UI toolkit, but you need to implement this yourself if you have the view has +virtual child views. The following code example shows the `View` method you have to override in +order to provide this view hierarchy data to the Autofill service. + +```kotlin +override fun onProvideAutofillVirtualStructure(structure: ViewStructure, flags: Int) { + // Build a ViewStructure to pack in AutoFillService requests. + structure.setClassName(javaClass.name) + val childrenSize = mItems.size() + Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags + ", items = " + + childrenSize + ", extras: " + bundleToString(structure.extras)) + var index = structure.addChildCount(childrenSize) + // Traverse through the view hierarchy, including virtual child views. For each view, we + // need to set the relevant autofill metadata and add it to the ViewStructure. + for (i in 0..childrenSize - 1) { + val item = mItems.valueAt(i) + Log.d(TAG, "Adding new child at index $index: $item") + val child = structure.newChild(index) + child.setAutofillId(structure, item.id) + child.setAutofillHints(item.hints) + child.setAutofillType(item.type) + child.setDataIsSensitive(!item.sanitized) + child.text = item.text + child.setAutofillValue(AutofillValue.forText(item.text)) + child.setFocused(item.focused) + child.setId(item.id, context.packageName, null, item.line.idEntry) + child.setClassName(item.className) + index++ + } } ``` @@ -65,30 +100,29 @@ After the service processes the Autofill request and sends back a series of Auto (wrapped in a `Response` object), the user can pick which `Dataset` they want to autofill their views with. When a `Dataset` is selected, this method is invoked for all of the views that were associated with that `Dataset` by the service. For example, the `Dataset` might contain Autofill -values for username, password, birthday, and address. This method would then be invoked on all four -of those fields. The following code example shows how the sample app implements the method to -deliver a UI update to the appropriate child view after the user makes their selection. - -```java -/* -User has just selected a Dataset from the list of Autofill suggestions and the Dataset's -AutoFillValue gets passed into this method. This method updates the UI based on the data -in the AutoFillValue. -*/ -@Override -public void autoFillVirtual(int id, AutoFillValue value) { - Item item = mItems.get(id); - if (item == null) { - // ID not recognized so no-op. - return; - } - if (!item.editable) { - // Component is not editable so no-op. - return; - } - // Set the virtual child view's text to the text wrapped in the AutoFillValue. - item.text = value.getTextValue(); - postInvalidate(); +values for username, password, birthday, and address. This method would then be invoked on all +four of those fields. The following code example shows how the sample app implements the method +to deliver a UI update to the appropriate child view after the user makes their selection. + +```kotlin +override fun autofill(values: SparseArray<AutofillValue>) { + // User has just selected a Dataset from the list of autofill suggestions. + // The Dataset is comprised of a list of AutofillValues, with each AutofillValue meant + // to fill a specific autofillable view. Now we have to update the UI based on the + // AutofillValues in the list. + for (i in 0..values.size() - 1) { + val id = values.keyAt(i) + val value = values.valueAt(i) + mItems[id]?.let { item -> + if (item.editable) { + // Set the item's text to the text wrapped in the AutofillValue. + item.text = value.textValue + } else { + // Component not editable, so no-op. + } + } + } + postInvalidate() } ``` @@ -96,8 +130,10 @@ Pre-requisites -------------- - Android SDK Preview O -- Android Build Tools v25.0.3 -- Android Support Repository +- Android Studio 3.0+ +- Android Build Tools v26+ +- Android Support Repository v26+ +- Gradle v3.0+ Screenshots ------------- |