diff options
author | Felipe Leme <felipeal@google.com> | 2018-03-19 18:07:35 -0700 |
---|---|---|
committer | Felipe Leme <felipeal@google.com> | 2018-03-19 18:13:12 -0700 |
commit | f08f6f94246f0f5a8e8d26083cdc0652cca789b1 (patch) | |
tree | e80189c220d99e10524c46b0c6a400d0d8cc4b10 /input/autofill | |
parent | 9071e88670523a61735dcf2e78a1a25cac71b2b2 (diff) | |
download | android-f08f6f94246f0f5a8e8d26083cdc0652cca789b1.tar.gz |
Created a simple service (A.K.A. ASIOF - Autofill Service In One File)
Bug: 75285224
Test: ./gradlew :afservice:installDebug
Change-Id: I67fb162b4f18593b73a10becaead189559ab21f5
Diffstat (limited to 'input/autofill')
2 files changed, 186 insertions, 0 deletions
diff --git a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml index f5d9a6d1..83ac4ee7 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml +++ b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml @@ -26,6 +26,15 @@ </intent-filter> </service> + <service + android:name=".simple.BasicService" + android:label="Basic Service" + android:permission="android.permission.BIND_AUTOFILL_SERVICE"> + <intent-filter> + <action android:name="android.service.autofill.AutofillService" /> + </intent-filter> + </service> + <activity android:name=".AuthActivity" android:taskAffinity=".AuthActivity" diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java new file mode 100644 index 00000000..af8a9afa --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2018 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. + */ +package com.example.android.autofill.service.simple; + +import android.app.assist.AssistStructure; +import android.app.assist.AssistStructure.ViewNode; +import android.os.CancellationSignal; +import android.service.autofill.AutofillService; +import android.service.autofill.Dataset; +import android.service.autofill.FillCallback; +import android.service.autofill.FillContext; +import android.service.autofill.FillRequest; +import android.service.autofill.FillResponse; +import android.service.autofill.SaveCallback; +import android.service.autofill.SaveInfo; +import android.service.autofill.SaveRequest; +import android.support.annotation.NonNull; +import android.support.v4.util.ArrayMap; +import android.util.Log; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; +import android.widget.RemoteViews; +import android.widget.Toast; + +import com.example.android.autofill.service.R; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * A very basic {@link AutofillService} implementation that only shows dynamic-generated datasets + * and don't persist the saved data. + * + * <p>This class has 2 goals: + * <ul> + * <li>Provide a simple service that app developers can use to see how their apps behave with + * autofill. + * <li>Explain the basic autofill workflow / provide a service that can be easily modified. + * </ul> + */ +/* + * TODO list: + * - improve documentation above, explaining + * - no-goals, security limitations, etc.. + * - toast usage + * - how dynamic datasets are created + * - how save works + * - use strings instead of hardcoded values + * - use different icons for different services + */ +public class BasicService extends AutofillService { + + private static final String TAG = "BasicService"; + + private static final int NUMBER_DATASETS = 4; + + @Override + public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, + FillCallback callback) { + Log.d(TAG, "onFillRequest()"); + + // Find autofillable fields + AssistStructure structure = getLatestAssistStructure(request); + Map<String, AutofillId> fields = getAutofillableFields(structure); + Log.d(TAG, "autofillable fields:" + fields); + + if (fields.isEmpty()) { + toast("No autofill hints found"); + callback.onSuccess(null); + return; + } + Log.d(TAG, "autofill hints: " + fields); + + // Create the base response + FillResponse.Builder response = new FillResponse.Builder(); + + // 1.Add the dynamic datasets + String packageName = getApplicationContext().getPackageName(); + for (int i = 1; i <= NUMBER_DATASETS; i++) { + Dataset.Builder dataset = new Dataset.Builder(); + for (Entry<String, AutofillId> field : fields.entrySet()) { + String hint = field.getKey(); + AutofillId id = field.getValue(); + String value = hint + i; + String displayValue = hint.contains("password") ? "password for #" + i : value; + RemoteViews presentation = newDatasetPresentation(packageName, displayValue); + dataset.setValue(id, AutofillValue.forText(value), presentation); + } + response.addDataset(dataset.build()); + } + + // 2.Add save info + Collection<AutofillId> ids = fields.values(); + AutofillId[] requiredIds = new AutofillId[ids.size()]; + ids.toArray(requiredIds); + response.setSaveInfo( + // We're simple, so we're generic + new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build()); + + // 3.Profit! + callback.onSuccess(response.build()); + } + + @Override + public void onSaveRequest(SaveRequest request, SaveCallback callback) { + Log.d(TAG, "onSaveRequest()"); + toast("Save not supported"); + callback.onSuccess(); + } + + // TODO: document + @NonNull + private Map<String, AutofillId> getAutofillableFields(@NonNull AssistStructure structure) { + Map<String, AutofillId> fields = new ArrayMap<>(); + int nodes = structure.getWindowNodeCount(); + for (int i = 0; i < nodes; i++) { + ViewNode node = structure.getWindowNodeAt(i).getRootViewNode(); + addAutofillableFields(fields, node); + } + return fields; + } + + // TODO: document + private void addAutofillableFields(@NonNull Map<String, AutofillId> fields, + @NonNull ViewNode node) { + String[] hints = node.getAutofillHints(); + if (hints != null) { + AutofillId id = node.getAutofillId(); + // We're simple, we only care about the first hint + String hint = hints[0].toLowerCase(); + Log.v(TAG, "Found hint " + hint + " on " + id); + fields.put(hint, id); + } + int childrenSize = node.getChildCount(); + for (int i = 0; i < childrenSize; i++) { + addAutofillableFields(fields, node.getChildAt(i)); + } + } + + // TODO: move to common code + @NonNull + private static AssistStructure getLatestAssistStructure(@NonNull FillRequest request) { + List<FillContext> fillContexts = request.getFillContexts(); + return fillContexts.get(fillContexts.size() - 1).getStructure(); + } + + // TODO: move to common code + @NonNull + private static RemoteViews newDatasetPresentation(@NonNull String packageName, + @NonNull CharSequence text) { + RemoteViews presentation = + new RemoteViews(packageName, R.layout.multidataset_service_list_item); + presentation.setTextViewText(R.id.text, text); + presentation.setImageViewResource(R.id.icon, R.mipmap.ic_launcher); + return presentation; + } + + // TODO: move to common code + private void toast(@NonNull CharSequence message) { + Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); + } +} |