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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -576,6 +576,10 @@ class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate {
|
||||
mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(),
|
||||
getDayOfMonth());
|
||||
}
|
||||
if (mAutoFillChangeListener != null) {
|
||||
mAutoFillChangeListener.onDateChanged(mDelegator, getYear(), getMonth(),
|
||||
getDayOfMonth());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -436,7 +436,7 @@ public class RadioGroup extends LinearLayout {
|
||||
|
||||
@Override
|
||||
public AutoFillType getAutoFillType() {
|
||||
return AutoFillType.forList();
|
||||
return isEnabled() ? AutoFillType.forList() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user