OSDN Git Service

Moved getAutofillOptions() logic from SpinnerAbs to AdapterView and Adapter.
authorFelipe Leme <felipeal@google.com>
Tue, 18 Apr 2017 16:45:58 +0000 (09:45 -0700)
committerFelipe Leme <felipeal@google.com>
Tue, 18 Apr 2017 22:36:04 +0000 (15:36 -0700)
Fixes: 37330022
Test: CtsAutoFillServiceTestCases pass
Test: manual verification using app from bug 37327881

Change-Id: I66260131c7ef0d5b3c2b20514f29efd5eb10057e

api/current.txt
api/system-current.txt
api/test-current.txt
core/java/android/app/assist/AssistStructure.java
core/java/android/view/View.java
core/java/android/view/ViewStructure.java
core/java/android/widget/AbsSpinner.java
core/java/android/widget/Adapter.java
core/java/android/widget/AdapterView.java
core/java/android/widget/ArrayAdapter.java
core/java/android/widget/SimpleAdapter.java

index 5b55fdc..1be17d6 100644 (file)
@@ -6626,7 +6626,7 @@ package android.app.assist {
     method public float getAlpha();
     method public java.lang.String[] getAutofillHints();
     method public android.view.autofill.AutofillId getAutofillId();
-    method public java.lang.String[] getAutofillOptions();
+    method public java.lang.CharSequence[] getAutofillOptions();
     method public int getAutofillType();
     method public android.view.autofill.AutofillValue getAutofillValue();
     method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
@@ -46550,7 +46550,7 @@ package android.view {
     method public abstract void setAlpha(float);
     method public abstract void setAutofillHints(java.lang.String[]);
     method public abstract void setAutofillId(android.view.ViewStructure, int);
-    method public abstract void setAutofillOptions(java.lang.String[]);
+    method public abstract void setAutofillOptions(java.lang.CharSequence[]);
     method public abstract void setAutofillType(int);
     method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
     method public abstract void setCheckable(boolean);
@@ -49371,6 +49371,7 @@ package android.widget {
   }
 
   public abstract interface Adapter {
+    method public default java.lang.CharSequence[] getAutofillOptions();
     method public abstract int getCount();
     method public abstract java.lang.Object getItem(int);
     method public abstract long getItemId(int);
@@ -49520,6 +49521,7 @@ package android.widget {
     method public void addAll(T...);
     method public void clear();
     method public static android.widget.ArrayAdapter<java.lang.CharSequence> createFromResource(android.content.Context, int, int);
+    method public java.lang.CharSequence[] getAutofillOptions();
     method public android.content.Context getContext();
     method public int getCount();
     method public android.content.res.Resources.Theme getDropDownViewTheme();
index 23a8cad..ac874d2 100644 (file)
@@ -6871,7 +6871,7 @@ package android.app.assist {
     method public float getAlpha();
     method public java.lang.String[] getAutofillHints();
     method public android.view.autofill.AutofillId getAutofillId();
-    method public java.lang.String[] getAutofillOptions();
+    method public java.lang.CharSequence[] getAutofillOptions();
     method public int getAutofillType();
     method public android.view.autofill.AutofillValue getAutofillValue();
     method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
@@ -50125,7 +50125,7 @@ package android.view {
     method public abstract void setAlpha(float);
     method public abstract void setAutofillHints(java.lang.String[]);
     method public abstract void setAutofillId(android.view.ViewStructure, int);
-    method public abstract void setAutofillOptions(java.lang.String[]);
+    method public abstract void setAutofillOptions(java.lang.CharSequence[]);
     method public abstract void setAutofillType(int);
     method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
     method public abstract void setCheckable(boolean);
@@ -53314,6 +53314,7 @@ package android.widget {
   }
 
   public abstract interface Adapter {
+    method public default java.lang.CharSequence[] getAutofillOptions();
     method public abstract int getCount();
     method public abstract java.lang.Object getItem(int);
     method public abstract long getItemId(int);
@@ -53463,6 +53464,7 @@ package android.widget {
     method public void addAll(T...);
     method public void clear();
     method public static android.widget.ArrayAdapter<java.lang.CharSequence> createFromResource(android.content.Context, int, int);
+    method public java.lang.CharSequence[] getAutofillOptions();
     method public android.content.Context getContext();
     method public int getCount();
     method public android.content.res.Resources.Theme getDropDownViewTheme();
index 3373ab7..0c76d9b 100644 (file)
@@ -6656,7 +6656,7 @@ package android.app.assist {
     method public float getAlpha();
     method public java.lang.String[] getAutofillHints();
     method public android.view.autofill.AutofillId getAutofillId();
-    method public java.lang.String[] getAutofillOptions();
+    method public java.lang.CharSequence[] getAutofillOptions();
     method public int getAutofillType();
     method public android.view.autofill.AutofillValue getAutofillValue();
     method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
@@ -46924,7 +46924,7 @@ package android.view {
     method public abstract void setAlpha(float);
     method public abstract void setAutofillHints(java.lang.String[]);
     method public abstract void setAutofillId(android.view.ViewStructure, int);
-    method public abstract void setAutofillOptions(java.lang.String[]);
+    method public abstract void setAutofillOptions(java.lang.CharSequence[]);
     method public abstract void setAutofillType(int);
     method public abstract void setAutofillValue(android.view.autofill.AutofillValue);
     method public abstract void setCheckable(boolean);
@@ -49749,6 +49749,7 @@ package android.widget {
   }
 
   public abstract interface Adapter {
+    method public default java.lang.CharSequence[] getAutofillOptions();
     method public abstract int getCount();
     method public abstract java.lang.Object getItem(int);
     method public abstract long getItemId(int);
@@ -49898,6 +49899,7 @@ package android.widget {
     method public void addAll(T...);
     method public void clear();
     method public static android.widget.ArrayAdapter<java.lang.CharSequence> createFromResource(android.content.Context, int, int);
+    method public java.lang.CharSequence[] getAutofillOptions();
     method public android.content.Context getContext();
     method public int getCount();
     method public android.content.res.Resources.Theme getDropDownViewTheme();
index 9f911f5..9e5c9e7 100644 (file)
@@ -595,7 +595,7 @@ public class AssistStructure implements Parcelable {
         @View.AutofillType int mAutofillType;
         @Nullable String[] mAutofillHints;
         AutofillValue mAutofillValue;
-        String[] mAutofillOptions;
+        CharSequence[] mAutofillOptions;
         boolean mSanitized;
         HtmlInfo mHtmlInfo;
 
@@ -689,7 +689,7 @@ public class AssistStructure implements Parcelable {
                 mAutofillType = in.readInt();
                 mAutofillHints = in.readStringArray();
                 mAutofillValue = in.readParcelable(null);
-                mAutofillOptions = in.readStringArray();
+                mAutofillOptions = in.readCharSequenceArray();
                 final Parcelable p = in.readParcelable(null);
                 if (p instanceof HtmlInfo) {
                     mHtmlInfo = (HtmlInfo) p;
@@ -850,7 +850,7 @@ public class AssistStructure implements Parcelable {
                     sanitizedValue = null;
                 }
                 out.writeParcelable(sanitizedValue,  0);
-                out.writeStringArray(mAutofillOptions);
+                out.writeCharSequenceArray(mAutofillOptions);
                 if (mHtmlInfo instanceof Parcelable) {
                     out.writeParcelable((Parcelable) mHtmlInfo, 0);
                 } else {
@@ -1002,7 +1002,7 @@ public class AssistStructure implements Parcelable {
          * <p>It's only set when the {@link AssistStructure} is used for autofilling purposes, not
          * for assist.
          */
-        public String[] getAutofillOptions() {
+        public CharSequence[] getAutofillOptions() {
             return mAutofillOptions;
         }
 
@@ -1694,7 +1694,7 @@ public class AssistStructure implements Parcelable {
         }
 
         @Override
-        public void setAutofillOptions(String[] options) {
+        public void setAutofillOptions(CharSequence[] options) {
             mNode.mAutofillOptions = options;
         }
 
index 7d2d77e..2a68ac6 100644 (file)
@@ -7261,7 +7261,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
      * <li>The view contents does not include PII (Personally Identifiable Information), so it
      * can call {@link ViewStructure#setDataIsSensitive(boolean)} passing {@code false}.
      * <li>It must set fields such {@link ViewStructure#setText(CharSequence)},
-     * {@link ViewStructure#setAutofillOptions(String[])}, or {@link ViewStructure#setUrl(String)}.
+     * {@link ViewStructure#setAutofillOptions(CharSequence[])},
+     * or {@link ViewStructure#setUrl(String)}.
      * </ol>
      *
      * @param structure Fill in with structured view data. The default implementation
index b157709..435610e 100644 (file)
@@ -314,7 +314,7 @@ public abstract class ViewStructure {
      * <p>Typically used by nodes whose {@link View#getAutofillType()} is a list to indicate the
      * meaning of each possible value in the list.
      */
-    public abstract void setAutofillOptions(String[] options);
+    public abstract void setAutofillOptions(CharSequence[] options);
 
     /**
      * Sets the {@link android.text.InputType} bits of this node.
@@ -326,8 +326,8 @@ public abstract class ViewStructure {
     /**
      * Sets whether the data on this node is sensitive; if it is, then its content (text, autofill
      * value, etc..) is striped before calls to {@link
-     * android.service.autofill.AutofillService#onFillRequest(android.app.assist.AssistStructure,
-     * Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback)}.
+     * android.service.autofill.AutofillService#onFillRequest(android.service.autofill.FillRequest,
+     * android.os.CancellationSignal, android.service.autofill.FillCallback)}.
      *
      * <p>By default, all nodes are assumed to be sensitive, and only nodes that does not have PII
      * (Personally Identifiable Information - sensitive data such as email addresses, credit card
@@ -336,8 +336,8 @@ public abstract class ViewStructure {
      *
      * <p>Notice that the content of even sensitive nodes are sent to the service (through the
      * {@link
-     * android.service.autofill.AutofillService#onSaveRequest(android.app.assist.AssistStructure,
-     * Bundle, android.service.autofill.SaveCallback)} call) when the user consented to save
+     * android.service.autofill.AutofillService#onSaveRequest(android.service.autofill.SaveRequest,
+     * android.service.autofill.SaveCallback)} call) when the user consented to save
      * thedata, so it is important to set the content of sensitive nodes as well, but mark them as
      * sensitive.
      *
index 352e7de..f80c9e6 100644 (file)
@@ -27,7 +27,6 @@ import android.util.Log;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewStructure;
 import android.view.autofill.AutofillValue;
 
 import com.android.internal.R;
@@ -495,37 +494,6 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
     // TODO(b/33197203): add unit/CTS tests for auto-fill methods (and make sure they handle enable)
 
     @Override
-    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
-        super.onProvideAutofillStructure(structure, flags);
-
-        final SpinnerAdapter adapter = getAdapter();
-
-        if (adapter == null) return;
-
-        // TODO(b/33197203): implement sanitization so initial value is only sanitized when coming
-        // from resources.
-
-        final int count = adapter.getCount();
-        int size = 0;
-        if (count > 0) {
-            final String[] options = new String[count];
-            for (int i = 0; i < count; i++) {
-                final Object item = adapter.getItem(i);
-                if (item != null) {
-                    options[size++] = item.toString();
-                }
-            }
-            if (size == count) {
-                structure.setAutofillOptions(options);
-            } else {
-                final String[] validOptions = new String[size];
-                System.arraycopy(options, 0, validOptions, 0, size);
-                structure.setAutofillOptions(validOptions);
-            }
-        }
-    }
-
-    @Override
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
index 518718f..4e463dd 100644 (file)
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.annotation.Nullable;
 import android.database.DataSetObserver;
 import android.view.View;
 import android.view.ViewGroup;
@@ -146,4 +147,22 @@ public interface Adapter {
       * adapters might want a different behavior.
       */
      boolean isEmpty();
+
+    /**
+     * Gets a string representation of the adapter data that can help
+     * {@link android.service.autofill.AutofillService} autofill the view backed by the adapter.
+     *
+     * <p>
+     * It should only be set (i.e., non-{@code null} if the values do not represent PII
+     * (Personally Identifiable Information - sensitive data such as email addresses,
+     * credit card numbers, passwords, etc...). For
+     * example, it's ok to return a list of month names, but not a list of usernames. A good rule of
+     * thumb is that if the adapter data comes from static resources, such data is not PII - see
+     * {@link android.view.ViewStructure#setDataIsSensitive(boolean)} for more info.
+     *
+     * @return {@code null} by default, unless implementations override it.
+     */
+    default @Nullable CharSequence[] getAutofillOptions() {
+        return null;
+    }
 }
index be54869..dd01251 100644 (file)
@@ -31,6 +31,7 @@ import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewHierarchyEncoder;
+import android.view.ViewStructure;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -1283,4 +1284,24 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
         encoder.addProperty("list:selectedPosition", mSelectedPosition);
         encoder.addProperty("list:itemCount", mItemCount);
     }
-}
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>It also sets the autofill options in the structure; when overridden, it should set it as
+     * well, either explicitly by calling {@link ViewStructure#setAutofillOptions(CharSequence[])}
+     * or implicitly by calling {@code super.onProvideAutofillStructure(structure, flags)}.
+     */
+    @Override
+    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
+        super.onProvideAutofillStructure(structure, flags);
+
+        final Adapter adapter = getAdapter();
+        if (adapter == null) return;
+
+        final CharSequence[] options = adapter.getAutofillOptions();
+        if (options != null) {
+            structure.setAutofillOptions(options);
+        }
+    }
+}
\ No newline at end of file
index 81f0d3d..869ef71 100644 (file)
@@ -83,6 +83,11 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp
     private List<T> mObjects;
 
     /**
+     * Indicates whether the contents of {@link #mObjects} came from static resources.
+     */
+    private boolean mObjectsFromResources;
+
+    /**
      * If the inflated resource is not a TextView, {@code mFieldId} is used to find
      * a TextView inside the inflated views hierarchy. This field must contain the
      * identifier that matches the one defined in the resource file.
@@ -177,10 +182,16 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp
      */
     public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
             @IdRes int textViewResourceId, @NonNull List<T> objects) {
+        this(context, resource, textViewResourceId, objects, false);
+    }
+
+    private ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
+            @IdRes int textViewResourceId, @NonNull List<T> objects, boolean objsFromResources) {
         mContext = context;
         mInflater = LayoutInflater.from(context);
         mResource = mDropDownResource = resource;
         mObjects = objects;
+        mObjectsFromResources = objsFromResources;
         mFieldId = textViewResourceId;
     }
 
@@ -196,6 +207,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp
             } else {
                 mObjects.add(object);
             }
+            mObjectsFromResources = false;
         }
         if (mNotifyOnChange) notifyDataSetChanged();
     }
@@ -221,6 +233,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp
             } else {
                 mObjects.addAll(collection);
             }
+            mObjectsFromResources = false;
         }
         if (mNotifyOnChange) notifyDataSetChanged();
     }
@@ -237,6 +250,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp
             } else {
                 Collections.addAll(mObjects, items);
             }
+            mObjectsFromResources = false;
         }
         if (mNotifyOnChange) notifyDataSetChanged();
     }
@@ -254,6 +268,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp
             } else {
                 mObjects.add(index, object);
             }
+            mObjectsFromResources = false;
         }
         if (mNotifyOnChange) notifyDataSetChanged();
     }
@@ -270,6 +285,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp
             } else {
                 mObjects.remove(object);
             }
+            mObjectsFromResources = false;
         }
         if (mNotifyOnChange) notifyDataSetChanged();
     }
@@ -284,6 +300,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp
             } else {
                 mObjects.clear();
             }
+            mObjectsFromResources = false;
         }
         if (mNotifyOnChange) notifyDataSetChanged();
     }
@@ -470,7 +487,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp
     public static @NonNull ArrayAdapter<CharSequence> createFromResource(@NonNull Context context,
             @ArrayRes int textArrayResId, @LayoutRes int textViewResId) {
         final CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
-        return new ArrayAdapter<>(context, textViewResId, strings);
+        return new ArrayAdapter<>(context, textViewResId, 0, Arrays.asList(strings), true);
     }
 
     @Override
@@ -482,6 +499,24 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp
     }
 
     /**
+     * {@inheritDoc}
+     *
+     * @return values from the string array used by {@link #createFromResource(Context, int, int)},
+     * or {@code null} if object was created otherwsie or if contents were dynamically changed after
+     * creation.
+     */
+    @Override
+    public CharSequence[] getAutofillOptions() {
+        if (!mObjectsFromResources || mObjects == null || mObjects.isEmpty()) {
+            return null;
+        }
+        final int size = mObjects.size();
+        final CharSequence[] options = new CharSequence[size];
+        mObjects.toArray(options);
+        return options;
+    }
+
+    /**
      * <p>An array filter constrains the content of the array adapter with
      * a prefix. Each item that does not start with the supplied prefix
      * is removed from the list.</p>
index 9190117..57e2ece 100644 (file)
@@ -320,6 +320,9 @@ public class SimpleAdapter extends BaseAdapter implements Filterable, ThemedSpin
         return mFilter;
     }
 
+    // TODO(b/33197203): implement getAutofillOptions
+
+
     /**
      * This class can be used by external clients of SimpleAdapter to bind
      * values to views.