Snap for 4731145 from e44363c82c9187230ba85cdc1beb3d2aba9595e6 to pi-release

Change-Id: I94b2d73b24d3cc230aa1423e7b1ae2b07321eff4
diff --git a/input/autofill/AutofillFramework/Application/build.gradle b/input/autofill/AutofillFramework/Application/build.gradle
index 4a877db..50bbdc3 100644
--- a/input/autofill/AutofillFramework/Application/build.gradle
+++ b/input/autofill/AutofillFramework/Application/build.gradle
@@ -7,12 +7,11 @@
         'main']     // main sample code; look here for the interesting stuff.
 
 android {
-    compileSdkVersion 26
-    buildToolsVersion '26.0.2'
+    compileSdkVersion "android-P"
 
     defaultConfig {
         minSdkVersion 26
-        targetSdkVersion 26
+        targetSdkVersion 28
     }
 
     compileOptions {
@@ -38,11 +37,11 @@
 }
 
 dependencies {
-    implementation "com.android.support:support-v4:26.1.0"
-    implementation "com.android.support:support-v13:26.1.0"
-    implementation "com.android.support:cardview-v7:26.1.0"
-    implementation "com.android.support:appcompat-v7:26.1.0"
-    implementation 'com.android.support:design:26.1.0'
+    implementation "com.android.support:support-v4:28.0.0-alpha1"
+    implementation "com.android.support:support-v13:28.0.0-alpha1"
+    implementation "com.android.support:cardview-v7:28.0.0-alpha1"
+    implementation "com.android.support:appcompat-v7:28.0.0-alpha1"
+    implementation 'com.android.support:design:28.0.0-alpha1'
     implementation 'com.android.support.constraint:constraint-layout:1.0.2'
     implementation group: 'com.google.guava', name: 'guava', version: '22.0-android'
 }
diff --git a/input/autofill/AutofillFramework/afservice/build.gradle b/input/autofill/AutofillFramework/afservice/build.gradle
index 07ccafd..f614207 100644
--- a/input/autofill/AutofillFramework/afservice/build.gradle
+++ b/input/autofill/AutofillFramework/afservice/build.gradle
@@ -1,11 +1,11 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 27
+    compileSdkVersion "android-P"
 
     defaultConfig {
         minSdkVersion 26
-        targetSdkVersion 27
+        targetSdkVersion 28
         versionCode 1
         versionName "1.0"
 
@@ -30,17 +30,17 @@
 }
 
 dependencies {
-    implementation 'com.android.support:appcompat-v7:27.0.2'
+    implementation 'com.android.support:appcompat-v7:28.0.0-alpha1'
     implementation "android.arch.persistence.room:runtime:1.0.0"
     annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
     implementation fileTree(dir: 'libs', include: ['*.jar'])
-    implementation 'com.android.support:design:27.0.2'
+    implementation 'com.android.support:design:28.0.0-alpha1'
     implementation 'com.android.support.constraint:constraint-layout:1.0.2'
-    implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.1'
+    implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.2'
     implementation 'com.squareup.retrofit2:retrofit:2.3.0'
     implementation group: 'com.google.guava', name: 'guava', version: '22.0-android'
     implementation "com.android.support.test.espresso:espresso-idling-resource:3.0.1"
-    implementation "com.google.code.findbugs:jsr305:2.0.1"
+    implementation "com.google.code.findbugs:jsr305:3.0.2"
 
     androidTestImplementation "junit:junit:4.12"
     androidTestImplementation ("com.android.support.test.espresso:espresso-core:3.0.1")
diff --git a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
index 83ac4ee..b02a591 100644
--- a/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
+++ b/input/autofill/AutofillFramework/afservice/src/main/AndroidManifest.xml
@@ -28,13 +28,23 @@
 
         <service
             android:name=".simple.BasicService"
-            android:label="Basic Service"
+            android:label="Basic Autofill Service"
             android:permission="android.permission.BIND_AUTOFILL_SERVICE">
             <intent-filter>
                 <action android:name="android.service.autofill.AutofillService" />
             </intent-filter>
         </service>
 
+        <service
+            android:name=".simple.BasicHeuristicsService"
+            android:label="Basic Heuristics Autofill 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/BasicHeuristicsService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java
new file mode 100644
index 0000000..843440c
--- /dev/null
+++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/simple/BasicHeuristicsService.java
@@ -0,0 +1,105 @@
+/*
+ * 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("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 af8a9af..92d6436 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
@@ -28,13 +28,16 @@
 import android.service.autofill.SaveInfo;
 import android.service.autofill.SaveRequest;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.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.R;
 
 import java.util.Collection;
@@ -46,27 +49,18 @@
  * 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
+ * <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 BasicService extends AutofillService {
 
     private static final String TAG = "BasicService";
 
+    /**
+     * Number of datasets sent on each request - we're simple, that value is hardcoded in our DNA!
+     */
     private static final int NUMBER_DATASETS = 4;
 
     @Override
@@ -84,7 +78,6 @@
             callback.onSuccess(null);
             return;
         }
-        Log.d(TAG, "autofill hints: " + fields);
 
         // Create the base response
         FillResponse.Builder response = new FillResponse.Builder();
@@ -97,6 +90,9 @@
                 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
+                // password
                 String displayValue = hint.contains("password") ? "password for #" + i : value;
                 RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
                 dataset.setValue(id, AutofillValue.forText(value), presentation);
@@ -123,7 +119,13 @@
         callback.onSuccess();
     }
 
-    // TODO: document
+    /**
+     * 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 Map<String, AutofillId> getAutofillableFields(@NonNull AssistStructure structure) {
         Map<String, AutofillId> fields = new ArrayMap<>();
@@ -135,16 +137,25 @@
         return fields;
     }
 
-    // TODO: document
+    /**
+     * 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[] 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 type = node.getAutofillType();
+        // We're simple, we just autofill text fields.
+        if (type == View.AUTOFILL_TYPE_TEXT) {
+            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++) {
@@ -152,14 +163,37 @@
         }
     }
 
-    // TODO: move to common code
+    /**
+     * 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) {
         List<FillContext> fillContexts = request.getFillContexts();
         return fillContexts.get(fillContexts.size() - 1).getStructure();
     }
 
-    // TODO: move to common code
+    /**
+     * Helper method to create a dataset presentation with the given text.
+     */
     @NonNull
     private static RemoteViews newDatasetPresentation(@NonNull String packageName,
             @NonNull CharSequence text) {
@@ -170,7 +204,9 @@
         return presentation;
     }
 
-    // TODO: move to common code
+    /**
+     * Displays a toast with the given message.
+     */
     private void toast(@NonNull CharSequence message) {
         Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
     }