Make it clear which type of AutoFillValue is set

throw and handle errors if the wrong value is set for a view

Test: android.autofillservice.cts.AutofillValueTest
Change-Id: Ida80da7913a210bede6c47d6b7a6f215a012a84c
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 1f2ed00..1132159 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1012,9 +1012,8 @@
             mAutofillValue = value;
             // TODO(b/33197203, b/33802548): decide whether to set text as well (so it would work
             // with "legacy" views) or just the autofill value
-            final CharSequence text = value.getTextValue();
-            if (text != null) {
-                mText.mText = text;
+            if (value.isText()) {
+                mText.mText = value.getTextValue();
             }
         }
 
diff --git a/core/java/android/view/autofill/AutofillValue.java b/core/java/android/view/autofill/AutofillValue.java
index 0c7620e..9babb59 100644
--- a/core/java/android/view/autofill/AutofillValue.java
+++ b/core/java/android/view/autofill/AutofillValue.java
@@ -16,13 +16,22 @@
 
 package android.view.autofill;
 
+import static android.view.View.AUTOFILL_TYPE_DATE;
+import static android.view.View.AUTOFILL_TYPE_LIST;
+import static android.view.View.AUTOFILL_TYPE_TEXT;
+import static android.view.View.AUTOFILL_TYPE_TOGGLE;
 import static android.view.autofill.Helper.DEBUG;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.View;
 
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
 /**
  * Abstracts how a {@link View} can be autofilled by an
  * {@link android.service.autofill.AutofillService}.
@@ -31,52 +40,96 @@
  * {@link View#getAutofillType()}.
  */
 public final class AutofillValue implements Parcelable {
-    private final String mText;
-    private final int mListIndex;
-    private final boolean mToggle;
-    private final long mDate;
+    private final @View.AutofillType int mType;
+    private final @NonNull Object mValue;
 
-    private AutofillValue(CharSequence text, int listIndex, boolean toggle, long date) {
-        mText = (text == null) ? null : text.toString();
-        mListIndex = listIndex;
-        mToggle = toggle;
-        mDate = date;
+    private AutofillValue(@View.AutofillType int type, @NonNull Object value) {
+        mType = type;
+        mValue = value;
     }
 
     /**
      * Gets the value to autofill a text field.
      *
-     * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.
+     * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a text value
      */
-    public CharSequence getTextValue() {
-        return mText;
+    @NonNull public CharSequence getTextValue() {
+        Preconditions.checkState(isText(), "value must be a text value, not type=" + mType);
+        return (CharSequence) mValue;
+    }
+
+    /**
+     * Checks is this is a text value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p>
+     */
+    public boolean isText() {
+        return mType == AUTOFILL_TYPE_TEXT;
     }
 
     /**
      * Gets the value to autofill a toggable field.
      *
-     * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.
+     * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a toggle value
      */
     public boolean getToggleValue() {
-        return mToggle;
+        Preconditions.checkState(isToggle(), "value must be a toggle value, not type=" + mType);
+        return (Boolean) mValue;
+    }
+
+    /**
+     * Checks is this is a toggle value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p>
+     */
+    public boolean isToggle() {
+        return mType == AUTOFILL_TYPE_TOGGLE;
     }
 
     /**
      * Gets the value to autofill a selection list field.
      *
-     * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.
+     * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a list value
      */
     public int getListValue() {
-        return mListIndex;
+        Preconditions.checkState(isList(), "value must be a list value, not type=" + mType);
+        return (Integer) mValue;
+    }
+
+    /**
+     * Checks is this is a list value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p>
+     */
+    public boolean isList() {
+        return mType == AUTOFILL_TYPE_LIST;
     }
 
     /**
      * Gets the value to autofill a date field.
      *
-     * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.
+     * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p>
+     *
+     * @throws IllegalStateException if the value is not a date value
      */
     public long getDateValue() {
-        return mDate;
+        Preconditions.checkState(isDate(), "value must be a date value, not type=" + mType);
+        return (Long) mValue;
+    }
+
+    /**
+     * Checks is this is a date value.
+     *
+     * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p>
+     */
+    public boolean isDate() {
+        return mType == AUTOFILL_TYPE_DATE;
     }
 
     /////////////////////////////////////
@@ -85,13 +138,7 @@
 
     @Override
     public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((mText == null) ? 0 : mText.hashCode());
-        result = prime * result + mListIndex;
-        result = prime * result + (mToggle ? 1231 : 1237);
-        result = prime * result + (int) (mDate ^ (mDate >>> 32));
-        return result;
+        return mType + mValue.hashCode();
     }
 
     @Override
@@ -100,32 +147,31 @@
         if (obj == null) return false;
         if (getClass() != obj.getClass()) return false;
         final AutofillValue other = (AutofillValue) obj;
-        if (mText == null) {
-            if (other.mText != null) return false;
+
+        if (mType != other.mType) return false;
+
+        if (isText()) {
+            return mValue.toString().equals(other.mValue.toString());
         } else {
-            if (!mText.equals(other.mText)) return false;
+            return Objects.equals(mValue, other.mValue);
         }
-        if (mListIndex != other.mListIndex) return false;
-        if (mToggle != other.mToggle) return false;
-        if (mDate != other.mDate) return false;
-        return true;
     }
 
     /** @hide */
     public String coerceToString() {
         // TODO(b/33197203): How can we filter on toggles or list values?
-        return mText;
+        return mValue.toString();
     }
 
     @Override
     public String toString() {
         if (!DEBUG) return super.toString();
 
-        if (mText != null) {
-            return mText.length() + "_chars";
+        if (isText()) {
+            return ((CharSequence) mValue).length() + "_chars";
         }
 
-        return "[l=" + mListIndex + ", t=" + mToggle + ", d=" + mDate + "]";
+        return "[type=" + mType + ", value=" + mValue + "]";
     }
 
     /////////////////////////////////////
@@ -139,17 +185,44 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeString(mText);
-        parcel.writeInt(mListIndex);
-        parcel.writeInt(mToggle ? 1 : 0);
-        parcel.writeLong(mDate);
+        parcel.writeInt(mType);
+
+        switch (mType) {
+            case AUTOFILL_TYPE_TEXT:
+                parcel.writeCharSequence((CharSequence) mValue);
+                break;
+            case AUTOFILL_TYPE_TOGGLE:
+                parcel.writeInt((Boolean) mValue ? 1 : 0);
+                break;
+            case AUTOFILL_TYPE_LIST:
+                parcel.writeInt((Integer) mValue);
+                break;
+            case AUTOFILL_TYPE_DATE:
+                parcel.writeLong((Long) mValue);
+                break;
+        }
     }
 
-    private AutofillValue(Parcel parcel) {
-        mText = parcel.readString();
-        mListIndex = parcel.readInt();
-        mToggle = parcel.readInt() == 1;
-        mDate = parcel.readLong();
+    private AutofillValue(@NonNull Parcel parcel) {
+        mType = parcel.readInt();
+
+        switch (mType) {
+            case AUTOFILL_TYPE_TEXT:
+                mValue = parcel.readCharSequence();
+                break;
+            case AUTOFILL_TYPE_TOGGLE:
+                int rawValue = parcel.readInt();
+                mValue = rawValue != 0;
+                break;
+            case AUTOFILL_TYPE_LIST:
+                mValue = parcel.readInt();
+                break;
+            case AUTOFILL_TYPE_DATE:
+                mValue = parcel.readLong();
+                break;
+            default:
+                throw new IllegalArgumentException("type=" + mType + " not valid");
+        }
     }
 
     public static final Parcelable.Creator<AutofillValue> CREATOR =
@@ -175,9 +248,8 @@
      * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.
      */
     // TODO(b/33197203): use cache
-    @Nullable
     public static AutofillValue forText(@Nullable CharSequence value) {
-        return value == null ? null : new AutofillValue(value, 0, false, 0);
+        return value == null ? null : new AutofillValue(AUTOFILL_TYPE_TEXT, value);
     }
 
     /**
@@ -187,7 +259,7 @@
      * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.
      */
     public static AutofillValue forToggle(boolean value) {
-        return new AutofillValue(null, 0, value, 0);
+        return new AutofillValue(AUTOFILL_TYPE_TOGGLE, value);
     }
 
     /**
@@ -197,7 +269,7 @@
      * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.
      */
     public static AutofillValue forList(int value) {
-        return new AutofillValue(null, value, false, 0);
+        return new AutofillValue(AUTOFILL_TYPE_LIST, value);
     }
 
     /**
@@ -206,6 +278,6 @@
      * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.
      */
     public static AutofillValue forDate(long value) {
-        return new AutofillValue(null, 0, false, value);
+        return new AutofillValue(AUTOFILL_TYPE_DATE, value);
     }
 }
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 053574f..020e80a 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -23,6 +23,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
@@ -38,6 +39,8 @@
  * @attr ref android.R.styleable#AbsSpinner_entries
  */
 public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
+    private static final String LOG_TAG = AbsSpinner.class.getSimpleName();
+
     SpinnerAdapter mAdapter;
 
     int mHeightMeasureSpec;
@@ -514,8 +517,11 @@
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
-        final int position = value.getListValue();
-        setSelection(position);
+        if (value.isList()) {
+            setSelection(value.getListValue());
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+        }
     }
 
     @Override
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index d246405..ae58e2a 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -28,6 +28,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.SoundEffectConstants;
 import android.view.ViewDebug;
@@ -55,6 +56,7 @@
  * </p>
  */
 public abstract class CompoundButton extends Button implements Checkable {
+    private static final String LOG_TAG = CompoundButton.class.getSimpleName();
 
     private boolean mChecked;
     private boolean mBroadcasting;
@@ -585,7 +587,11 @@
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
-        setChecked(value.getToggleValue());
+        if (value.isToggle()) {
+            setChecked(value.getToggleValue());
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+        }
     }
 
     @Override
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 31a88d4..f63573f 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -29,6 +29,7 @@
 import android.os.Parcelable;
 import android.text.format.DateUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewStructure;
@@ -83,6 +84,8 @@
  */
 @Widget
 public class DatePicker extends FrameLayout {
+    private static final String LOG_TAG = DatePicker.class.getSimpleName();
+
     /**
      * Presentation mode for the Holo-style date picker that uses a set of
      * {@link android.widget.NumberPicker}s.
@@ -775,7 +778,11 @@
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
-        mDelegate.updateDate(value.getDateValue());
+        if (value.isDate()) {
+            mDelegate.updateDate(value.getDateValue());
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+        }
     }
 
     @Override
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index dc9976d..3dba3f9 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -55,6 +55,7 @@
  *
  */
 public class RadioGroup extends LinearLayout {
+    private static final String LOG_TAG = RadioGroup.class.getSimpleName();
 
     // holds the checked id; the selection is empty by default
     private int mCheckedId = -1;
@@ -428,7 +429,14 @@
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
-        final int index = value.getListValue();
+        int index;
+        if (value.isList()) {
+            index = value.getListValue();
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+            return;
+        }
+
         final View child = getChildAt(index);
         if (child == null) {
             Log.w(VIEW_LOG_TAG, "RadioGroup.autoFill(): no child with index " + index);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d591316f..65c7bd1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10014,10 +10014,12 @@
 
     @Override
     public void autofill(AutofillValue value) {
-        final CharSequence text = value.getTextValue();
-
-        if (text != null && isTextEditable()) {
-            setText(text, mBufferType, true, 0);
+        if (value.isText()) {
+            if (isTextEditable()) {
+                setText(value.getTextValue(), mBufferType, true, 0);
+            }
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
         }
     }
 
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 9825f1e..cfa78b5 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -27,6 +27,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewStructure;
@@ -53,6 +54,8 @@
  */
 @Widget
 public class TimePicker extends FrameLayout {
+    private static final String LOG_TAG = TimePicker.class.getSimpleName();
+
     /**
      * Presentation mode for the Holo-style time picker that uses a set of
      * {@link android.widget.NumberPicker}s.
@@ -530,7 +533,11 @@
     public void autofill(AutofillValue value) {
         if (!isEnabled()) return;
 
-        mDelegate.setDate(value.getDateValue());
+        if (value.isDate()) {
+            mDelegate.setDate(value.getDateValue());
+        } else {
+            Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+        }
     }
 
     @Override