aboutsummaryrefslogtreecommitdiff
path: root/input/autofill/AutofillFramework
diff options
context:
space:
mode:
authorFelipe Leme <felipeal@google.com>2018-03-19 18:07:35 -0700
committerFelipe Leme <felipeal@google.com>2018-03-19 18:13:12 -0700
commitf08f6f94246f0f5a8e8d26083cdc0652cca789b1 (patch)
treee80189c220d99e10524c46b0c6a400d0d8cc4b10 /input/autofill/AutofillFramework
parent9071e88670523a61735dcf2e78a1a25cac71b2b2 (diff)
downloadandroid-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/AutofillFramework')
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml9
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java177
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();
+ }
+}