Added auto-fill support for DatePicker and TimePicker.

Also fixed some getAutoFillType() implementations to return null
when the view is disabled.

Bug: 33550221
Bug: 35840787
Test: CtsAutoFillServiceTestCases (with new tests) pass
Test: m update-api

Change-Id: I46acc1fb106cf2153515cc1c9567b34cfabd1c62
This commit is contained in:
Felipe Leme
2017-02-27 12:46:04 -08:00
parent f561734e9c
commit 305b72c925
13 changed files with 222 additions and 24 deletions

View File

@@ -40,18 +40,13 @@ public final class AutoFillType implements Parcelable {
private static class DefaultTypesHolder {
static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE, 0);
static final AutoFillType LIST = new AutoFillType(TYPE_LIST, 0);
static final AutoFillType DATE = new AutoFillType(TYPE_DATE, 0);
}
private static final int TYPE_TEXT = 1;
private static final int TYPE_TOGGLE = 2;
private static final int TYPE_LIST = 3;
// TODO(b/33197203): add others, like date picker? That would be trick, because they're set as:
// updateDate(int year, int month, int dayOfMonth)
// So, we would have to either use a long representing the Date.time(), or a custom long
// representing:
// year * 10000 + month * 100 + day
// Then a custom getDatePickerValue(Bundle) that returns an immutable object with these 3 fields
private static final int TYPE_DATE = 4;
private final int mType;
private final int mSubType;
@@ -65,7 +60,7 @@ public final class AutoFillType implements Parcelable {
* Checks if this is a type for a text field, which is filled by a {@link CharSequence}.
*
* <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
* {@link AutoFillValue#forText(CharSequence)}, and the value of a bundle passed to auto-fill a
* {@link AutoFillValue#forText(CharSequence)}, and the value passed to auto-fill a
* {@link View} can be fetched through {@link AutoFillValue#getTextValue()}.
*
* <p>Sub-type for this type is the value defined by {@link TextView#getInputType()}.
@@ -78,7 +73,7 @@ public final class AutoFillType implements Parcelable {
* Checks if this is a a type for a togglable field, which is filled by a {@code boolean}.
*
* <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
* {@link AutoFillValue#forToggle(boolean)}, and the value of a bundle passed to auto-fill a
* {@link AutoFillValue#forToggle(boolean)}, and the value passed to auto-fill a
* {@link View} can be fetched through {@link AutoFillValue#getToggleValue()}.
*
* <p>This type has no sub-types.
@@ -92,7 +87,7 @@ public final class AutoFillType implements Parcelable {
* representing the element index inside the list (starting at {@code 0}.
*
* <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
* {@link AutoFillValue#forList(int)}, and the value of a bundle passed to auto-fill a
* {@link AutoFillValue#forList(int)}, and the value passed to auto-fill a
* {@link View} can be fetched through {@link AutoFillValue#getListValue()}.
*
* <p>This type has no sub-types.
@@ -101,6 +96,20 @@ public final class AutoFillType implements Parcelable {
return mType == TYPE_LIST;
}
/**
* Checks if this is a type for a date and time, which is represented by a long representing
* the number of milliseconds since the standard base time known as "the epoch", namely
* January 1, 1970, 00:00:00 GMT (see {@link java.util.Date#getTime()}.
*
* <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
* {@link AutoFillValue#forDate(long)}, and the values passed to
* auto-fill a {@link View} can be fetched through {@link AutoFillValue#getDateValue()}.
*
* <p>This type has no sub-types.
*/
public boolean isDate() {
return mType == TYPE_DATE;
}
/**
* Gets the optional sub-type, representing the {@link View}'s semantic.
@@ -206,4 +215,13 @@ public final class AutoFillType implements Parcelable {
public static AutoFillType forList() {
return DefaultTypesHolder.LIST;
}
/**
* Creates a type that represents a date.
*
* <p>See {@link #isDate()} for more info.
*/
public static AutoFillType forDate() {
return DefaultTypesHolder.DATE;
}
}

View File

@@ -35,11 +35,13 @@ public final class AutoFillValue implements Parcelable {
private final String mText;
private final int mListIndex;
private final boolean mToggle;
private final long mDate;
private AutoFillValue(CharSequence text, int listIndex, boolean toggle) {
private AutoFillValue(CharSequence text, int listIndex, boolean toggle, long date) {
mText = (text == null) ? null : text.toString();
mListIndex = listIndex;
mToggle = toggle;
mDate = date;
}
/**
@@ -69,6 +71,17 @@ public final class AutoFillValue implements Parcelable {
return mListIndex;
}
/**
* Gets the value representing the the number of milliseconds since the standard base time known
* as "the epoch", namely January 1, 1970, 00:00:00 GMT (see {@link java.util.Date#getTime()}
* of a date field.
*
* <p>See {@link AutoFillType#isDate()} for more info.
*/
public long getDateValue() {
return mDate;
}
/////////////////////////////////////
// Object "contract" methods. //
/////////////////////////////////////
@@ -77,9 +90,10 @@ public final class AutoFillValue implements Parcelable {
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + mListIndex;
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;
}
@@ -89,13 +103,14 @@ public final class AutoFillValue implements Parcelable {
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final AutoFillValue other = (AutoFillValue) obj;
if (mListIndex != other.mListIndex) return false;
if (mText == null) {
if (other.mText != null) return false;
} else {
if (!mText.equals(other.mText)) return false;
}
if (mListIndex != other.mListIndex) return false;
if (mToggle != other.mToggle) return false;
if (mDate != other.mDate) return false;
return true;
}
@@ -113,7 +128,7 @@ public final class AutoFillValue implements Parcelable {
return mText.length() + "_chars";
}
return "[listIndex=" + mListIndex + ", toggle=" + mToggle + "]";
return "[l=" + mListIndex + ", t=" + mToggle + ", d=" + mDate + "]";
}
/////////////////////////////////////
@@ -130,12 +145,14 @@ public final class AutoFillValue implements Parcelable {
parcel.writeString(mText);
parcel.writeInt(mListIndex);
parcel.writeInt(mToggle ? 1 : 0);
parcel.writeLong(mDate);
}
private AutoFillValue(Parcel parcel) {
mText = parcel.readString();
mListIndex = parcel.readInt();
mToggle = parcel.readInt() == 1;
mDate = parcel.readLong();
}
public static final Parcelable.Creator<AutoFillValue> CREATOR =
@@ -157,31 +174,42 @@ public final class AutoFillValue implements Parcelable {
// TODO(b/33197203): add unit tests for each supported type (new / get should return same value)
/**
* Creates a new {@link AutoFillValue} to auto-fill a text field.
* Creates a new {@link AutoFillValue} to auto-fill a {@link View} representing a text field.
*
* <p>See {@link AutoFillType#isText()} 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);
return value == null ? null : new AutoFillValue(value, 0, false, 0);
}
/**
* Creates a new {@link AutoFillValue} to auto-fill a toggable field.
* Creates a new {@link AutoFillValue} to auto-fill a {@link View} representing a toggable
* field.
*
* <p>See {@link AutoFillType#isToggle()} for more info.
*/
public static AutoFillValue forToggle(boolean value) {
return new AutoFillValue(null, 0, value);
return new AutoFillValue(null, 0, value, 0);
}
/**
* Creates a new {@link AutoFillValue} to auto-fill a selection list field.
* Creates a new {@link AutoFillValue} to auto-fill a {@link View} representing a selection
* list.
*
* <p>See {@link AutoFillType#isList()} for more info.
*/
public static AutoFillValue forList(int value) {
return new AutoFillValue(null, value, false);
return new AutoFillValue(null, value, false, 0);
}
/**
* Creates a new {@link AutoFillValue} to auto-fill a {@link View} representing a date.
*
* <p>See {@link AutoFillType#isDate()} for more info.
*/
public static AutoFillValue forDate(long date) {
return new AutoFillValue(null, 0, false, date);
}
}

View File

@@ -591,7 +591,7 @@ public abstract class CompoundButton extends Button implements Checkable {
@Override
public AutoFillType getAutoFillType() {
return AutoFillType.forToggle();
return isEnabled() ? AutoFillType.forToggle() : null;
}
@Override

View File

@@ -31,7 +31,11 @@ import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewStructure;
import android.view.accessibility.AccessibilityEvent;
import android.view.autofill.AutoFillManager;
import android.view.autofill.AutoFillType;
import android.view.autofill.AutoFillValue;
import com.android.internal.R;
@@ -170,6 +174,13 @@ public class DatePicker extends FrameLayout {
if (firstDayOfWeek != 0) {
setFirstDayOfWeek(firstDayOfWeek);
}
mDelegate.setAutoFillChangeListener((v, y, m, d) -> {
final AutoFillManager afm = context.getSystemService(AutoFillManager.class);
if (afm != null) {
afm.valueChanged(this);
}
});
}
private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs,
@@ -503,12 +514,15 @@ public class DatePicker extends FrameLayout {
OnDateChangedListener onDateChangedListener);
void setOnDateChangedListener(OnDateChangedListener onDateChangedListener);
void setAutoFillChangeListener(OnDateChangedListener onDateChangedListener);
void updateDate(int year, int month, int dayOfMonth);
void updateDate(long date);
int getYear();
int getMonth();
int getDayOfMonth();
long getDate();
void setFirstDayOfWeek(int firstDayOfWeek);
int getFirstDayOfWeek();
@@ -558,6 +572,7 @@ public class DatePicker extends FrameLayout {
// Callbacks
protected OnDateChangedListener mOnDateChangedListener;
protected OnDateChangedListener mAutoFillChangeListener;
protected ValidationCallback mValidationCallback;
public AbstractDatePickerDelegate(DatePicker delegator, Context context) {
@@ -579,11 +594,29 @@ public class DatePicker extends FrameLayout {
mOnDateChangedListener = callback;
}
@Override
public void setAutoFillChangeListener(OnDateChangedListener callback) {
mAutoFillChangeListener = callback;
}
@Override
public void setValidationCallback(ValidationCallback callback) {
mValidationCallback = callback;
}
@Override
public void updateDate(long date) {
Calendar cal = Calendar.getInstance(mCurrentLocale);
cal.setTimeInMillis(date);
updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH),
cal.get(Calendar.DAY_OF_MONTH));
}
@Override
public long getDate() {
return mCurrentDate.getTimeInMillis();
}
protected void onValidationChanged(boolean valid) {
if (mValidationCallback != null) {
mValidationCallback.onValidationChanged(valid);
@@ -723,4 +756,31 @@ public class DatePicker extends FrameLayout {
public interface ValidationCallback {
void onValidationChanged(boolean valid);
}
// TODO(b/33197203): add unit/CTS tests for auto-fill methods (and make sure they handle enable)
@Override
public void dispatchProvideAutoFillStructure(ViewStructure structure, int flags) {
// This view is self-sufficient for auto-fill, so it needs to call
// onProvideAutoFillStructure() to fill itself, but it does not need to call
// dispatchProvideAutoFillStructure() to fill its children.
onProvideAutoFillStructure(structure, flags);
}
@Override
public void autoFill(AutoFillValue value) {
if (!isEnabled()) return;
mDelegate.updateDate(value.getDateValue());
}
@Override
public AutoFillType getAutoFillType() {
return isEnabled() ? AutoFillType.forDate() : null;
}
@Override
public AutoFillValue getAutoFillValue() {
return isEnabled() ? AutoFillValue.forDate(mDelegate.getDate()) : null;
}
}

View File

@@ -390,10 +390,16 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
private void onDateChanged(boolean fromUser, boolean callbackToClient) {
final int year = mCurrentDate.get(Calendar.YEAR);
if (callbackToClient && mOnDateChangedListener != null) {
if (callbackToClient
&& (mOnDateChangedListener != null || mAutoFillChangeListener != null)) {
final int monthOfYear = mCurrentDate.get(Calendar.MONTH);
final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH);
mOnDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
if (mOnDateChangedListener != null) {
mOnDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
}
if (mAutoFillChangeListener != null) {
mAutoFillChangeListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
}
}
mDayPickerView.setDate(mCurrentDate.getTimeInMillis());

View File

@@ -576,6 +576,10 @@ class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate {
mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(),
getDayOfMonth());
}
if (mAutoFillChangeListener != null) {
mAutoFillChangeListener.onDateChanged(mDelegator, getYear(), getMonth(),
getDayOfMonth());
}
}
/**

View File

@@ -436,7 +436,7 @@ public class RadioGroup extends LinearLayout {
@Override
public AutoFillType getAutoFillType() {
return AutoFillType.forList();
return isEnabled() ? AutoFillType.forList() : null;
}
@Override

View File

@@ -23,12 +23,17 @@ import android.annotation.TestApi;
import android.annotation.Widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.icu.util.Calendar;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewStructure;
import android.view.accessibility.AccessibilityEvent;
import android.view.autofill.AutoFillManager;
import android.view.autofill.AutoFillType;
import android.view.autofill.AutoFillValue;
import com.android.internal.R;
@@ -132,6 +137,12 @@ public class TimePicker extends FrameLayout {
this, context, attrs, defStyleAttr, defStyleRes);
break;
}
mDelegate.setAutoFillChangeListener((v, h, m) -> {
final AutoFillManager afm = context.getSystemService(AutoFillManager.class);
if (afm != null) {
afm.valueChanged(this);
}
});
}
/**
@@ -348,12 +359,16 @@ public class TimePicker extends FrameLayout {
void setMinute(@IntRange(from = 0, to = 59) int minute);
int getMinute();
void setDate(long date);
long getDate();
void setIs24Hour(boolean is24Hour);
boolean is24Hour();
boolean validateInput();
void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
void setAutoFillChangeListener(OnTimeChangedListener autoFillChangeListener);
void setEnabled(boolean enabled);
boolean isEnabled();
@@ -398,6 +413,7 @@ public class TimePicker extends FrameLayout {
protected final Locale mLocale;
protected OnTimeChangedListener mOnTimeChangedListener;
protected OnTimeChangedListener mAutoFillChangeListener;
public AbstractTimePickerDelegate(@NonNull TimePicker delegator, @NonNull Context context) {
mDelegator = delegator;
@@ -410,6 +426,27 @@ public class TimePicker extends FrameLayout {
mOnTimeChangedListener = callback;
}
@Override
public void setAutoFillChangeListener(OnTimeChangedListener callback) {
mAutoFillChangeListener = callback;
}
@Override
public void setDate(long date) {
Calendar cal = Calendar.getInstance(mLocale);
cal.setTimeInMillis(date);
setHour(cal.get(Calendar.HOUR_OF_DAY));
setMinute(cal.get(Calendar.MINUTE));
}
@Override
public long getDate() {
Calendar cal = Calendar.getInstance(mLocale);
cal.set(Calendar.HOUR_OF_DAY, getHour());
cal.set(Calendar.MINUTE, getMinute());
return cal.getTimeInMillis();
}
protected static class SavedState extends View.BaseSavedState {
private final int mHour;
private final int mMinute;
@@ -474,4 +511,31 @@ public class TimePicker extends FrameLayout {
};
}
}
// TODO(b/33197203): add unit/CTS tests for auto-fill methods (and make sure they handle enable)
@Override
public void dispatchProvideAutoFillStructure(ViewStructure structure, int flags) {
// This view is self-sufficient for auto-fill, so it needs to call
// onProvideAutoFillStructure() to fill itself, but it does not need to call
// dispatchProvideAutoFillStructure() to fill its children.
onProvideAutoFillStructure(structure, flags);
}
@Override
public void autoFill(AutoFillValue value) {
if (!isEnabled()) return;
mDelegate.setDate(value.getDateValue());
}
@Override
public AutoFillType getAutoFillType() {
return isEnabled() ? AutoFillType.forDate() : null;
}
@Override
public AutoFillValue getAutoFillValue() {
return isEnabled() ? AutoFillValue.forDate(mDelegate.getDate()) : null;
}
}

View File

@@ -671,6 +671,9 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
if (mOnTimeChangedListener != null) {
mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
}
if (mAutoFillChangeListener != null) {
mAutoFillChangeListener.onTimeChanged(mDelegator, getHour(), getMinute());
}
}
private void tryVibrate() {

View File

@@ -495,6 +495,9 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(),
getMinute());
}
if (mAutoFillChangeListener != null) {
mAutoFillChangeListener.onTimeChanged(mDelegator, getHour(), getMinute());
}
}
private void updateHourControl() {