aboutsummaryrefslogtreecommitdiff
path: root/input
diff options
context:
space:
mode:
authorFelipe Leme <felipeal@google.com>2018-09-25 15:28:58 -0700
committerFelipe Leme <felipeal@google.com>2018-09-25 16:15:30 -0700
commitf74de0e61e9b9f0bf13a5b0e2860e7fbdb5307be (patch)
tree3c1f494ea351aa11ee7ae1324245402107ea7a7c /input
parent0c6c93dc1003eeecb7321862a71913523ef8be52 (diff)
downloadandroid-f74de0e61e9b9f0bf13a5b0e2860e7fbdb5307be.tar.gz
Changes on Autofill Service project:
- Renamed BasicHeuristicsService to HeuristicsService - Split HeuristicsService from BasicService - Added compat-mode support to Chromium - Supported authentication on HeuristicsService - Changed value to "N-hint" to make it easier to filter out datasets - Added settings to HeuristicService - Made BasicService final, as it's kind of the "smallest" service possible Bug: 114236837 Test: ./gradlew :afservice:installDebug Change-Id: Ie36a8009d5ef39b9f82117f47a86ba11a9a4a9c1
Diffstat (limited to 'input')
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml11
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java109
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicService.java42
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java309
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java117
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/res/layout/simple_service_auth_activity.xml47
-rw-r--r--input/autofill/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml (renamed from input/autofill/AutofillFramework/afservice/src/main/res/xml/basic_heuristics_service.xml)10
7 files changed, 503 insertions, 142 deletions
diff --git a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
index 6ece581e..8c0b7750 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
+++ b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
@@ -36,12 +36,12 @@
</service>
<service
- android:name=".simple.BasicHeuristicsService"
- android:label="Basic Heuristics Autofill Service"
+ android:name=".simple.HeuristicsService"
+ android:label="Heuristics Autofill Service"
android:permission="android.permission.BIND_AUTOFILL_SERVICE">
<meta-data
android:name="android.autofill"
- android:resource="@xml/basic_heuristics_service"/>
+ android:resource="@xml/heuristics_service"/>
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>
@@ -53,6 +53,11 @@
android:label="@string/authentication_name" />
<activity
+ android:name=".simple.SimpleAuthActivity"
+ android:taskAffinity=".simple.SimpleAuthActivity"
+ android:label="@string/authentication_name" />
+
+ <activity
android:name=".ManualActivity"
android:taskAffinity=".ManualActivity"
android:label="@string/manual_name" />
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java
deleted file mode 100644
index 81aac2b6..00000000
--- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.ViewNode;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-
-import com.example.android.autofill.service.MyAutofillService;
-
-/**
- * A basic service that uses some rudimentary heuristics to identify fields that are not explicitly
- * marked with autofill hints.
- *
- * <p>The goal of this class is to provide a simple autofill service implementation that is easy
- * to understand and extend, but it should <strong>not</strong> be used as-is on real apps because
- * it lacks fundamental security requirements such as data partitioning and package verification
- * &mdashthese requirements are fullfilled by {@link MyAutofillService}. *
- */
-public class BasicHeuristicsService extends BasicService {
-
- private static final String TAG = "BasicHeuristicsService";
-
- @Override
- @Nullable
- protected String getHint(@NonNull ViewNode node) {
-
- // First try the explicit autofill hints...
-
- String hint = super.getHint(node);
- if (hint != null) return hint;
-
- // Then try some rudimentary heuristics based on other node properties
-
- String viewHint = node.getHint();
- hint = inferHint(viewHint);
- if (hint != null) {
- Log.d(TAG, "Found hint using view hint(" + viewHint + "): " + hint);
- return hint;
- } else if (!TextUtils.isEmpty(viewHint)) {
- Log.v(TAG, "No hint using view hint: " + viewHint);
- }
-
- String resourceId = node.getIdEntry();
- hint = inferHint(resourceId);
- if (hint != null) {
- Log.d(TAG, "Found hint using resourceId(" + resourceId + "): " + hint);
- return hint;
- } else if (!TextUtils.isEmpty(resourceId)) {
- Log.v(TAG, "No hint using resourceId: " + resourceId);
- }
-
- CharSequence text = node.getText();
- CharSequence className = node.getClassName();
- if (text != null && className != null && className.toString().contains("EditText")) {
- hint = inferHint(text.toString());
- if (hint != null) {
- // NODE: text should not be logged, as it could contain PII
- Log.d(TAG, "Found hint using text(" + text + "): " + hint);
- return hint;
- }
- } else if (!TextUtils.isEmpty(text)) {
- // NODE: text should not be logged, as it could contain PII
- Log.v(TAG, "No hint using text: " + text + " and class " + className);
- }
- return null;
- }
-
- /**
- * Uses heuristics to infer an autofill hint from a {@code string}.
- *
- * @return standard autofill hint, or {@code null} when it could not be inferred.
- */
- @Nullable
- protected String inferHint(@Nullable String string) {
- if (string == null) return null;
-
- string = string.toLowerCase();
- if (string.contains("label")) {
- Log.v(TAG, "Ignroing 'label' hint: " + string);
- return null;
- }
- if (string.contains("password")) return View.AUTOFILL_HINT_PASSWORD;
- if (string.contains("username")
- || (string.contains("login") && string.contains("id")))
- return View.AUTOFILL_HINT_USERNAME;
- if (string.contains("email")) return View.AUTOFILL_HINT_EMAIL_ADDRESS;
- if (string.contains("name")) return View.AUTOFILL_HINT_NAME;
- if (string.contains("phone")) return View.AUTOFILL_HINT_PHONE;
-
- return null;
- }
-}
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
index 73210beb..16937e74 100644
--- 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
@@ -53,7 +53,7 @@ import java.util.Map.Entry;
* it lacks fundamental security requirements such as data partitioning and package verification
* &mdashthese requirements are fullfilled by {@link MyAutofillService}.
*/
-public class BasicService extends AutofillService {
+public final class BasicService extends AutofillService {
private static final String TAG = "BasicService";
@@ -88,9 +88,9 @@ public class BasicService extends AutofillService {
for (Entry<String, AutofillId> field : fields.entrySet()) {
String hint = field.getKey();
AutofillId id = field.getValue();
- String value = hint + i;
- // We're simple - our dataset values are hardcoded as "hintN" (for example,
- // "username1", "username2") and they're displayed as such, except if they're a
+ String value = i + "-" + hint;
+ // We're simple - our dataset values are hardcoded as "N-hint" (for example,
+ // "1-username", "2-username") and they're displayed as such, except if they're a
// password
String displayValue = hint.contains("password") ? "password for #" + i : value;
RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
@@ -141,15 +141,19 @@ public class BasicService extends AutofillService {
*/
private void addAutofillableFields(@NonNull Map<String, AutofillId> fields,
@NonNull ViewNode node) {
- int type = node.getAutofillType();
- String hint = getHint(node);
+ String[] hints = node.getAutofillHints();
+ if (hints == null) return;
+
+ // We're simple, we only care about the first hint
+ String hint = hints[0].toLowerCase();
+
if (hint != null) {
AutofillId id = node.getAutofillId();
if (!fields.containsKey(hint)) {
- Log.v(TAG, "Setting hint " + hint + " on " + id);
+ Log.v(TAG, "Setting hint '" + hint + "' on " + id);
fields.put(hint, id);
} else {
- Log.v(TAG, "Ignoring hint " + hint + " on " + id
+ Log.v(TAG, "Ignoring hint '" + hint + "' on " + id
+ " because it was already set");
}
}
@@ -160,29 +164,11 @@ public class BasicService extends AutofillService {
}
/**
- * Gets the autofill hint associated with the given node.
- *
- * <p>By default it just return the first entry on the node's
- * {@link ViewNode#getAutofillHints() autofillHints} (when available), but subclasses could
- * extend it to use heuristics when the app developer didn't explicitly provide these hints.
- *
- */
- @Nullable
- protected String getHint(@NonNull ViewNode node) {
- String[] hints = node.getAutofillHints();
- if (hints == null) return null;
-
- // We're simple, we only care about the first hint
- String hint = hints[0].toLowerCase();
- return hint;
- }
-
- /**
* Helper method to get the {@link AssistStructure} associated with the latest request
* in an autofill context.
*/
@NonNull
- private static AssistStructure getLatestAssistStructure(@NonNull FillRequest request) {
+ static AssistStructure getLatestAssistStructure(@NonNull FillRequest request) {
List<FillContext> fillContexts = request.getFillContexts();
return fillContexts.get(fillContexts.size() - 1).getStructure();
}
@@ -191,7 +177,7 @@ public class BasicService extends AutofillService {
* Helper method to create a dataset presentation with the given text.
*/
@NonNull
- private static RemoteViews newDatasetPresentation(@NonNull String packageName,
+ static RemoteViews newDatasetPresentation(@NonNull String packageName,
@NonNull CharSequence text) {
RemoteViews presentation =
new RemoteViews(packageName, R.layout.multidataset_service_list_item);
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java
new file mode 100644
index 00000000..e19288e3
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/HeuristicsService.java
@@ -0,0 +1,309 @@
+/*
+ * 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 static com.example.android.autofill.service.simple.BasicService.getLatestAssistStructure;
+import static com.example.android.autofill.service.simple.BasicService.newDatasetPresentation;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.content.Context;
+import android.content.IntentSender;
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+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.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.example.android.autofill.service.MyAutofillService;
+import com.example.android.autofill.service.settings.MyPreferences;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A basic service that uses some rudimentary heuristics to identify fields that are not explicitly
+ * marked with autofill hints.
+ *
+ * <p>The goal of this class is to provide a simple autofill service implementation that is easy
+ * to understand and extend, but it should <strong>not</strong> be used as-is on real apps because
+ * it lacks fundamental security requirements such as data partitioning and package verification
+ * &mdashthese requirements are fullfilled by {@link MyAutofillService}.
+ */
+public class HeuristicsService extends AutofillService {
+
+ private static final String TAG = "HeuristicsService";
+
+ private boolean mAuthenticateResponses;
+ private boolean mAuthenticateDatasets;
+ private int mNumberDatasets = 4;
+
+ @Override
+ public void onConnected() {
+ super.onConnected();
+
+ // TODO(b/114236837): use its own preferences?
+ MyPreferences pref = MyPreferences.getInstance(getApplicationContext());
+ mAuthenticateResponses = pref.isResponseAuth();
+ mAuthenticateDatasets = pref.isDatasetAuth();
+ // TODO(b/114236837): get number dataset from preferences
+
+ Log.d(TAG, "onConnected(): numberDatasets=" + mNumberDatasets
+ + ", authResponses=" + mAuthenticateResponses
+ + ", authDatasets=" + mAuthenticateDatasets);
+ }
+
+ @Override
+ public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+ FillCallback callback) {
+ Log.d(TAG, "onFillRequest()");
+
+ // Find autofillable fields
+ AssistStructure structure = getLatestAssistStructure(request);
+ ArrayMap<String, AutofillId> fields = getAutofillableFields(structure);
+ Log.d(TAG, "autofillable fields:" + fields);
+
+ if (fields.isEmpty()) {
+ toast("No autofill hints found");
+ callback.onSuccess(null);
+ return;
+ }
+
+ // Create response...
+ FillResponse response;
+ if (mAuthenticateResponses) {
+ int size = fields.size();
+ String[] hints = new String[size];
+ AutofillId[] ids = new AutofillId[size];
+ for (int i = 0; i < size; i++) {
+ hints[i] = fields.keyAt(i);
+ ids[i] = fields.valueAt(i);
+ }
+
+ IntentSender authentication = SimpleAuthActivity.newIntentSenderForResponse(this, hints,
+ ids, mAuthenticateDatasets);
+ RemoteViews presentation = newDatasetPresentation(getPackageName(),
+ "Tap to auth response");
+
+ response = new FillResponse.Builder()
+ .setAuthentication(ids, authentication, presentation).build();
+ } else {
+ response = createResponse(this, fields, mNumberDatasets,mAuthenticateDatasets);
+ }
+
+ // ... and return it
+ callback.onSuccess(response);
+ }
+
+ @Override
+ public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+ Log.d(TAG, "onSaveRequest()");
+ toast("Save not supported");
+ callback.onSuccess();
+ }
+
+ /**
+ * Parses the {@link AssistStructure} representing the activity being autofilled, and returns a
+ * map of autofillable fields (represented by their autofill ids) mapped by the hint associate
+ * with them.
+ *
+ * <p>An autofillable field is a {@link ViewNode} whose {@link #getHint(ViewNode)} metho
+ */
+ @NonNull
+ private ArrayMap<String, AutofillId> getAutofillableFields(@NonNull AssistStructure structure) {
+ ArrayMap<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;
+ }
+
+ /**
+ * Adds any autofillable view from the {@link ViewNode} and its descendants to the map.
+ */
+ private void addAutofillableFields(@NonNull Map<String, AutofillId> fields,
+ @NonNull ViewNode node) {
+ String hint = getHint(node);
+ if (hint != null) {
+ AutofillId id = node.getAutofillId();
+ if (!fields.containsKey(hint)) {
+ Log.v(TAG, "Setting hint '" + hint + "' on " + id);
+ fields.put(hint, id);
+ } else {
+ Log.v(TAG, "Ignoring hint '" + hint + "' on " + id
+ + " because it was already set");
+ }
+ }
+ int childrenSize = node.getChildCount();
+ for (int i = 0; i < childrenSize; i++) {
+ addAutofillableFields(fields, node.getChildAt(i));
+ }
+ }
+
+ @Nullable
+ protected String getHint(@NonNull ViewNode node) {
+
+ // First try the explicit autofill hints...
+
+ String[] hints = node.getAutofillHints();
+ if (hints != null) {
+ // We're simple, we only care about the first hint
+ return hints[0].toLowerCase();
+ }
+
+ // Then try some rudimentary heuristics based on other node properties
+
+ String viewHint = node.getHint();
+ String hint = inferHint(viewHint);
+ if (hint != null) {
+ Log.d(TAG, "Found hint using view hint(" + viewHint + "): " + hint);
+ return hint;
+ } else if (!TextUtils.isEmpty(viewHint)) {
+ Log.v(TAG, "No hint using view hint: " + viewHint);
+ }
+
+ String resourceId = node.getIdEntry();
+ hint = inferHint(resourceId);
+ if (hint != null) {
+ Log.d(TAG, "Found hint using resourceId(" + resourceId + "): " + hint);
+ return hint;
+ } else if (!TextUtils.isEmpty(resourceId)) {
+ Log.v(TAG, "No hint using resourceId: " + resourceId);
+ }
+
+ CharSequence text = node.getText();
+ CharSequence className = node.getClassName();
+ if (text != null && className != null && className.toString().contains("EditText")) {
+ hint = inferHint(text.toString());
+ if (hint != null) {
+ // NODE: text should not be logged, as it could contain PII
+ Log.d(TAG, "Found hint using text(" + text + "): " + hint);
+ return hint;
+ }
+ } else if (!TextUtils.isEmpty(text)) {
+ // NODE: text should not be logged, as it could contain PII
+ Log.v(TAG, "No hint using text: " + text + " and class " + className);
+ }
+ return null;
+ }
+
+ /**
+ * Uses heuristics to infer an autofill hint from a {@code string}.
+ *
+ * @return standard autofill hint, or {@code null} when it could not be inferred.
+ */
+ @Nullable
+ protected String inferHint(@Nullable String string) {
+ if (string == null) return null;
+
+ string = string.toLowerCase();
+ if (string.contains("label")) {
+ Log.v(TAG, "Ignoring 'label' hint: " + string);
+ return null;
+ }
+ if (string.contains("password")) return View.AUTOFILL_HINT_PASSWORD;
+ if (string.contains("username")
+ || (string.contains("login") && string.contains("id")))
+ return View.AUTOFILL_HINT_USERNAME;
+ if (string.contains("email")) return View.AUTOFILL_HINT_EMAIL_ADDRESS;
+ if (string.contains("name")) return View.AUTOFILL_HINT_NAME;
+ if (string.contains("phone")) return View.AUTOFILL_HINT_PHONE;
+
+ return null;
+ }
+
+ static FillResponse createResponse(@NonNull Context context,
+ @NonNull ArrayMap<String, AutofillId> fields, int numDatasets,
+ boolean authenticateDatasets) {
+ String packageName = context.getPackageName();
+ FillResponse.Builder response = new FillResponse.Builder();
+ // 1.Add the dynamic datasets
+ for (int i = 1; i <= numDatasets; i++) {
+ Dataset unlockedDataset = newUnlockedDataset(fields, packageName, i);
+ if (authenticateDatasets) {
+ Dataset.Builder lockedDataset = new Dataset.Builder();
+ for (Entry<String, AutofillId> field : fields.entrySet()) {
+ String hint = field.getKey();
+ AutofillId id = field.getValue();
+ String value = i + "-" + hint;
+ IntentSender authentication =
+ SimpleAuthActivity.newIntentSenderForDataset(context, unlockedDataset);
+ RemoteViews presentation = newDatasetPresentation(packageName,
+ "Tap to auth " + value);
+ lockedDataset.setValue(id, null, presentation)
+ .setAuthentication(authentication);
+ }
+ response.addDataset(lockedDataset.build());
+ } else {
+ response.addDataset(unlockedDataset);
+ }
+ }
+
+ // 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!
+ return response.build();
+ }
+
+ static Dataset newUnlockedDataset(@NonNull Map<String, AutofillId> fields,
+ @NonNull String packageName, int i) {
+ Dataset.Builder dataset = new Dataset.Builder();
+ for (Entry<String, AutofillId> field : fields.entrySet()) {
+ String hint = field.getKey();
+ AutofillId id = field.getValue();
+ String value = i + "-" + hint;
+
+ // We're simple - our dataset values are hardcoded as "N-hint" (for example,
+ // "1-username", "2-username") and they're displayed as such, except if they're a
+ // password
+ String displayValue = hint.contains("password") ? "password for #" + i : value;
+ RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
+ dataset.setValue(id, AutofillValue.forText(value), presentation);
+ }
+
+ return dataset.build();
+ }
+
+ /**
+ * Displays a toast with the given message.
+ */
+ private void toast(@NonNull CharSequence message) {
+ Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java
new file mode 100644
index 00000000..4ff97a77
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/SimpleAuthActivity.java
@@ -0,0 +1,117 @@
+/*
+ * 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 static android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE;
+import static android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.ArrayMap;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import com.example.android.autofill.service.R;
+
+import java.util.Map.Entry;
+
+/**
+ * Activity used for autofill authentication, it simply sets the dataste upon tapping OK.
+ */
+// TODO(b/114236837): should display a small dialog, not take the full screen
+public class SimpleAuthActivity extends Activity {
+
+ private static final String EXTRA_DATASET = "dataset";
+ private static final String EXTRA_HINTS = "hints";
+ private static final String EXTRA_IDS = "ids";
+ private static final String EXTRA_AUTH_DATASETS = "auth_datasets";
+
+ private static int sPendingIntentId = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.simple_service_auth_activity);
+ findViewById(R.id.yes).setOnClickListener((view) -> onYes());
+ findViewById(R.id.no).setOnClickListener((view) -> onNo());
+ }
+
+ private void onYes() {
+ Intent myIntent = getIntent();
+ Intent replyIntent = new Intent();
+ Dataset dataset = myIntent.getParcelableExtra(EXTRA_DATASET);
+ if (dataset != null) {
+ replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, dataset);
+ } else {
+ String[] hints = myIntent.getStringArrayExtra(EXTRA_HINTS);
+ Parcelable[] ids = myIntent.getParcelableArrayExtra(EXTRA_IDS);
+ boolean authenticateDatasets = myIntent.getBooleanExtra(EXTRA_AUTH_DATASETS, false);
+ int size = hints.length;
+ ArrayMap<String, AutofillId> fields = new ArrayMap<>(size);
+ for (int i = 0; i < size; i++) {
+ fields.put(hints[i], (AutofillId) ids[i]);
+ }
+ FillResponse response =
+ HeuristicsService.createResponse(this, fields, 1, authenticateDatasets);
+ replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, response);
+
+ }
+ setResult(RESULT_OK, replyIntent);
+ finish();
+ }
+
+ private void onNo() {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ public static IntentSender newIntentSenderForDataset(@NonNull Context context,
+ @NonNull Dataset dataset) {
+ return newIntentSender(context, dataset, null, null, false);
+ }
+
+ public static IntentSender newIntentSenderForResponse(@NonNull Context context,
+ @NonNull String[] hints, @NonNull AutofillId[] ids, boolean authenticateDatasets) {
+ return newIntentSender(context, null, hints, ids, authenticateDatasets);
+ }
+
+ private static IntentSender newIntentSender(@NonNull Context context,
+ @Nullable Dataset dataset, @Nullable String[] hints, @Nullable AutofillId[] ids,
+ boolean authenticateDatasets) {
+ Intent intent = new Intent(context, SimpleAuthActivity.class);
+ if (dataset != null) {
+ intent.putExtra(EXTRA_DATASET, dataset);
+ } else {
+ intent.putExtra(EXTRA_HINTS, hints);
+ intent.putExtra(EXTRA_IDS, ids);
+ intent.putExtra(EXTRA_AUTH_DATASETS, authenticateDatasets);
+ }
+
+ return PendingIntent.getActivity(context, ++sPendingIntentId, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+ }
+}
diff --git a/input/autofill/AutofillFramework/afservice/src/main/res/layout/simple_service_auth_activity.xml b/input/autofill/AutofillFramework/afservice/src/main/res/layout/simple_service_auth_activity.xml
new file mode 100644
index 00000000..54fb6759
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/res/layout/simple_service_auth_activity.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAutofill="noExcludeDescendants"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Authenticate?" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <Button
+ android:id="@+id/no"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="No" />
+ <Button
+ android:id="@+id/yes"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Yes" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/input/autofill/AutofillFramework/afservice/src/main/res/xml/basic_heuristics_service.xml b/input/autofill/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml
index cfaabb03..fed1c479 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/res/xml/basic_heuristics_service.xml
+++ b/input/autofill/AutofillFramework/afservice/src/main/res/xml/heuristics_service.xml
@@ -14,13 +14,16 @@
* limitations under the License.
-->
-<autofill-service xmlns:android="http://schemas.android.com/apk/res/android">
+<!-- TODO(b/114236837): use its own Settings Activity -->
+<autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.example.android.autofill.service.settings.SettingsActivity">
+
<!-- sample app -->
<compatibility-package
android:name="com.example.android.autofill.app"
android:maxLongVersionCode="10000000000"/>
- <!-- well-known browswers -->
+ <!-- well-known browswers, alphabetical order -->
<compatibility-package
android:name="com.android.chrome"
android:maxLongVersionCode="10000000000"/>
@@ -55,6 +58,9 @@
android:name="com.sec.android.app.sbrowser.beta"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
+ android:name="org.chromium.chrome"
+ android:maxLongVersionCode="10000000000"/>
+ <compatibility-package
android:name="org.mozilla.fennec_aurora"
android:maxLongVersionCode="10000000000"/>
<compatibility-package