diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 87bee44611d8d..6e3dbd8c5e1d2 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -23,32 +23,17 @@ import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; -import android.text.InputType; -import android.text.format.DateFormat; -import android.text.format.DateUtils; import android.util.AttributeSet; -import android.util.Log; import android.util.SparseArray; -import android.view.LayoutInflater; import android.view.View; import android.view.accessibility.AccessibilityEvent; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.NumberPicker.OnValueChangeListener; import com.android.internal.R; -import java.text.DateFormatSymbols; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; -import libcore.icu.ICU; - /** * Provides a widget for selecting a date. *

@@ -527,6 +512,114 @@ public class DatePicker extends FrameLayout { protected void onLocaleChanged(Locale locale) { // Stub. } + + /** + * Class for managing state storing/restoring. + */ + static class SavedState extends View.BaseSavedState { + private final int mSelectedYear; + private final int mSelectedMonth; + private final int mSelectedDay; + private final long mMinDate; + private final long mMaxDate; + private final int mCurrentView; + private final int mListPosition; + private final int mListPositionOffset; + + public SavedState(Parcelable superState, int year, int month, int day, long minDate, + long maxDate) { + this(superState, year, month, day, minDate, maxDate, 0, 0, 0); + } + + /** + * Constructor called from {@link DatePicker#onSaveInstanceState()} + */ + public SavedState(Parcelable superState, int year, int month, int day, long minDate, + long maxDate, int currentView, int listPosition, int listPositionOffset) { + super(superState); + mSelectedYear = year; + mSelectedMonth = month; + mSelectedDay = day; + mMinDate = minDate; + mMaxDate = maxDate; + mCurrentView = currentView; + mListPosition = listPosition; + mListPositionOffset = listPositionOffset; + } + + /** + * Constructor called from {@link #CREATOR} + */ + private SavedState(Parcel in) { + super(in); + mSelectedYear = in.readInt(); + mSelectedMonth = in.readInt(); + mSelectedDay = in.readInt(); + mMinDate = in.readLong(); + mMaxDate = in.readLong(); + mCurrentView = in.readInt(); + mListPosition = in.readInt(); + mListPositionOffset = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mSelectedYear); + dest.writeInt(mSelectedMonth); + dest.writeInt(mSelectedDay); + dest.writeLong(mMinDate); + dest.writeLong(mMaxDate); + dest.writeInt(mCurrentView); + dest.writeInt(mListPosition); + dest.writeInt(mListPositionOffset); + } + + public int getSelectedDay() { + return mSelectedDay; + } + + public int getSelectedMonth() { + return mSelectedMonth; + } + + public int getSelectedYear() { + return mSelectedYear; + } + + public long getMinDate() { + return mMinDate; + } + + public long getMaxDate() { + return mMaxDate; + } + + public int getCurrentView() { + return mCurrentView; + } + + public int getListPosition() { + return mListPosition; + } + + public int getListPositionOffset() { + return mListPositionOffset; + } + + @SuppressWarnings("all") + // suppress unused and hiding + public static final Parcelable.Creator CREATOR = new Creator() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } } /** @@ -535,666 +628,7 @@ public class DatePicker extends FrameLayout { * * @hide */ - public static interface ValidationCallback { + public interface ValidationCallback { void onValidationChanged(boolean valid); } - - /** - * A delegate implementing the basic DatePicker - */ - private static class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate { - - private static final String DATE_FORMAT = "MM/dd/yyyy"; - - private static final int DEFAULT_START_YEAR = 1900; - - private static final int DEFAULT_END_YEAR = 2100; - - private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true; - - private static final boolean DEFAULT_SPINNERS_SHOWN = true; - - private static final boolean DEFAULT_ENABLED_STATE = true; - - private final LinearLayout mSpinners; - - private final NumberPicker mDaySpinner; - - private final NumberPicker mMonthSpinner; - - private final NumberPicker mYearSpinner; - - private final EditText mDaySpinnerInput; - - private final EditText mMonthSpinnerInput; - - private final EditText mYearSpinnerInput; - - private final CalendarView mCalendarView; - - private String[] mShortMonths; - - private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); - - private int mNumberOfMonths; - - private Calendar mTempDate; - - private Calendar mMinDate; - - private Calendar mMaxDate; - - private Calendar mCurrentDate; - - private boolean mIsEnabled = DEFAULT_ENABLED_STATE; - - DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - super(delegator, context); - - mDelegator = delegator; - mContext = context; - - // initialization based on locale - setCurrentLocale(Locale.getDefault()); - - final TypedArray attributesArray = context.obtainStyledAttributes(attrs, - R.styleable.DatePicker, defStyleAttr, defStyleRes); - boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown, - DEFAULT_SPINNERS_SHOWN); - boolean calendarViewShown = attributesArray.getBoolean( - R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN); - int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear, - DEFAULT_START_YEAR); - int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); - String minDate = attributesArray.getString(R.styleable.DatePicker_minDate); - String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate); - int layoutResourceId = attributesArray.getResourceId( - R.styleable.DatePicker_legacyLayout, R.layout.date_picker_legacy); - attributesArray.recycle(); - - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(layoutResourceId, mDelegator, true); - - OnValueChangeListener onChangeListener = new OnValueChangeListener() { - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - updateInputState(); - mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis()); - // take care of wrapping of days and months to update greater fields - if (picker == mDaySpinner) { - int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH); - if (oldVal == maxDayOfMonth && newVal == 1) { - mTempDate.add(Calendar.DAY_OF_MONTH, 1); - } else if (oldVal == 1 && newVal == maxDayOfMonth) { - mTempDate.add(Calendar.DAY_OF_MONTH, -1); - } else { - mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal); - } - } else if (picker == mMonthSpinner) { - if (oldVal == 11 && newVal == 0) { - mTempDate.add(Calendar.MONTH, 1); - } else if (oldVal == 0 && newVal == 11) { - mTempDate.add(Calendar.MONTH, -1); - } else { - mTempDate.add(Calendar.MONTH, newVal - oldVal); - } - } else if (picker == mYearSpinner) { - mTempDate.set(Calendar.YEAR, newVal); - } else { - throw new IllegalArgumentException(); - } - // now set the date to the adjusted one - setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH), - mTempDate.get(Calendar.DAY_OF_MONTH)); - updateSpinners(); - updateCalendarView(); - notifyDateChanged(); - } - }; - - mSpinners = (LinearLayout) mDelegator.findViewById(R.id.pickers); - - // calendar view day-picker - mCalendarView = (CalendarView) mDelegator.findViewById(R.id.calendar_view); - mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() { - public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) { - setDate(year, month, monthDay); - updateSpinners(); - notifyDateChanged(); - } - }); - - // day - mDaySpinner = (NumberPicker) mDelegator.findViewById(R.id.day); - mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); - mDaySpinner.setOnLongPressUpdateInterval(100); - mDaySpinner.setOnValueChangedListener(onChangeListener); - mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input); - - // month - mMonthSpinner = (NumberPicker) mDelegator.findViewById(R.id.month); - mMonthSpinner.setMinValue(0); - mMonthSpinner.setMaxValue(mNumberOfMonths - 1); - mMonthSpinner.setDisplayedValues(mShortMonths); - mMonthSpinner.setOnLongPressUpdateInterval(200); - mMonthSpinner.setOnValueChangedListener(onChangeListener); - mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input); - - // year - mYearSpinner = (NumberPicker) mDelegator.findViewById(R.id.year); - mYearSpinner.setOnLongPressUpdateInterval(100); - mYearSpinner.setOnValueChangedListener(onChangeListener); - mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input); - - // show only what the user required but make sure we - // show something and the spinners have higher priority - if (!spinnersShown && !calendarViewShown) { - setSpinnersShown(true); - } else { - setSpinnersShown(spinnersShown); - setCalendarViewShown(calendarViewShown); - } - - // set the min date giving priority of the minDate over startYear - mTempDate.clear(); - if (!TextUtils.isEmpty(minDate)) { - if (!parseDate(minDate, mTempDate)) { - mTempDate.set(startYear, 0, 1); - } - } else { - mTempDate.set(startYear, 0, 1); - } - setMinDate(mTempDate.getTimeInMillis()); - - // set the max date giving priority of the maxDate over endYear - mTempDate.clear(); - if (!TextUtils.isEmpty(maxDate)) { - if (!parseDate(maxDate, mTempDate)) { - mTempDate.set(endYear, 11, 31); - } - } else { - mTempDate.set(endYear, 11, 31); - } - setMaxDate(mTempDate.getTimeInMillis()); - - // initialize to current date - mCurrentDate.setTimeInMillis(System.currentTimeMillis()); - init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate - .get(Calendar.DAY_OF_MONTH), null); - - // re-order the number spinners to match the current date format - reorderSpinners(); - - // accessibility - setContentDescriptions(); - - // If not explicitly specified this view is important for accessibility. - if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - } - } - - @Override - public void init(int year, int monthOfYear, int dayOfMonth, - OnDateChangedListener onDateChangedListener) { - setDate(year, monthOfYear, dayOfMonth); - updateSpinners(); - updateCalendarView(); - mOnDateChangedListener = onDateChangedListener; - } - - @Override - public void updateDate(int year, int month, int dayOfMonth) { - if (!isNewDate(year, month, dayOfMonth)) { - return; - } - setDate(year, month, dayOfMonth); - updateSpinners(); - updateCalendarView(); - notifyDateChanged(); - } - - @Override - public int getYear() { - return mCurrentDate.get(Calendar.YEAR); - } - - @Override - public int getMonth() { - return mCurrentDate.get(Calendar.MONTH); - } - - @Override - public int getDayOfMonth() { - return mCurrentDate.get(Calendar.DAY_OF_MONTH); - } - - @Override - public void setFirstDayOfWeek(int firstDayOfWeek) { - mCalendarView.setFirstDayOfWeek(firstDayOfWeek); - } - - @Override - public int getFirstDayOfWeek() { - return mCalendarView.getFirstDayOfWeek(); - } - - @Override - public void setMinDate(long minDate) { - mTempDate.setTimeInMillis(minDate); - if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) - && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { - return; - } - mMinDate.setTimeInMillis(minDate); - mCalendarView.setMinDate(minDate); - if (mCurrentDate.before(mMinDate)) { - mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); - updateCalendarView(); - } - updateSpinners(); - } - - @Override - public Calendar getMinDate() { - final Calendar minDate = Calendar.getInstance(); - minDate.setTimeInMillis(mCalendarView.getMinDate()); - return minDate; - } - - @Override - public void setMaxDate(long maxDate) { - mTempDate.setTimeInMillis(maxDate); - if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) - && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) { - return; - } - mMaxDate.setTimeInMillis(maxDate); - mCalendarView.setMaxDate(maxDate); - if (mCurrentDate.after(mMaxDate)) { - mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); - updateCalendarView(); - } - updateSpinners(); - } - - @Override - public Calendar getMaxDate() { - final Calendar maxDate = Calendar.getInstance(); - maxDate.setTimeInMillis(mCalendarView.getMaxDate()); - return maxDate; - } - - @Override - public void setEnabled(boolean enabled) { - mDaySpinner.setEnabled(enabled); - mMonthSpinner.setEnabled(enabled); - mYearSpinner.setEnabled(enabled); - mCalendarView.setEnabled(enabled); - mIsEnabled = enabled; - } - - @Override - public boolean isEnabled() { - return mIsEnabled; - } - - @Override - public CalendarView getCalendarView() { - return mCalendarView; - } - - @Override - public void setCalendarViewShown(boolean shown) { - mCalendarView.setVisibility(shown ? VISIBLE : GONE); - } - - @Override - public boolean getCalendarViewShown() { - return (mCalendarView.getVisibility() == View.VISIBLE); - } - - @Override - public void setSpinnersShown(boolean shown) { - mSpinners.setVisibility(shown ? VISIBLE : GONE); - } - - @Override - public boolean getSpinnersShown() { - return mSpinners.isShown(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - setCurrentLocale(newConfig.locale); - } - - @Override - public Parcelable onSaveInstanceState(Parcelable superState) { - return new SavedState(superState, getYear(), getMonth(), getDayOfMonth()); - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - SavedState ss = (SavedState) state; - setDate(ss.mYear, ss.mMonth, ss.mDay); - updateSpinners(); - updateCalendarView(); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - onPopulateAccessibilityEvent(event); - return true; - } - - @Override - public void onPopulateAccessibilityEvent(AccessibilityEvent event) { - final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; - String selectedDateUtterance = DateUtils.formatDateTime(mContext, - mCurrentDate.getTimeInMillis(), flags); - event.getText().add(selectedDateUtterance); - } - - /** - * Sets the current locale. - * - * @param locale The current locale. - */ - @Override - protected void setCurrentLocale(Locale locale) { - super.setCurrentLocale(locale); - - mTempDate = getCalendarForLocale(mTempDate, locale); - mMinDate = getCalendarForLocale(mMinDate, locale); - mMaxDate = getCalendarForLocale(mMaxDate, locale); - mCurrentDate = getCalendarForLocale(mCurrentDate, locale); - - mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1; - mShortMonths = new DateFormatSymbols().getShortMonths(); - - if (usingNumericMonths()) { - // We're in a locale where a date should either be all-numeric, or all-text. - // All-text would require custom NumberPicker formatters for day and year. - mShortMonths = new String[mNumberOfMonths]; - for (int i = 0; i < mNumberOfMonths; ++i) { - mShortMonths[i] = String.format("%d", i + 1); - } - } - } - - /** - * Tests whether the current locale is one where there are no real month names, - * such as Chinese, Japanese, or Korean locales. - */ - private boolean usingNumericMonths() { - return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0)); - } - - /** - * Gets a calendar for locale bootstrapped with the value of a given calendar. - * - * @param oldCalendar The old calendar. - * @param locale The locale. - */ - private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { - if (oldCalendar == null) { - return Calendar.getInstance(locale); - } else { - final long currentTimeMillis = oldCalendar.getTimeInMillis(); - Calendar newCalendar = Calendar.getInstance(locale); - newCalendar.setTimeInMillis(currentTimeMillis); - return newCalendar; - } - } - - /** - * Reorders the spinners according to the date format that is - * explicitly set by the user and if no such is set fall back - * to the current locale's default format. - */ - private void reorderSpinners() { - mSpinners.removeAllViews(); - // We use numeric spinners for year and day, but textual months. Ask icu4c what - // order the user's locale uses for that combination. http://b/7207103. - String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd"); - char[] order = ICU.getDateFormatOrder(pattern); - final int spinnerCount = order.length; - for (int i = 0; i < spinnerCount; i++) { - switch (order[i]) { - case 'd': - mSpinners.addView(mDaySpinner); - setImeOptions(mDaySpinner, spinnerCount, i); - break; - case 'M': - mSpinners.addView(mMonthSpinner); - setImeOptions(mMonthSpinner, spinnerCount, i); - break; - case 'y': - mSpinners.addView(mYearSpinner); - setImeOptions(mYearSpinner, spinnerCount, i); - break; - default: - throw new IllegalArgumentException(Arrays.toString(order)); - } - } - } - - /** - * Parses the given date and in case of success sets the result - * to the outDate. - * - * @return True if the date was parsed. - */ - private boolean parseDate(String date, Calendar outDate) { - try { - outDate.setTime(mDateFormat.parse(date)); - return true; - } catch (ParseException e) { - Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); - return false; - } - } - - private boolean isNewDate(int year, int month, int dayOfMonth) { - return (mCurrentDate.get(Calendar.YEAR) != year - || mCurrentDate.get(Calendar.MONTH) != month - || mCurrentDate.get(Calendar.DAY_OF_MONTH) != dayOfMonth); - } - - private void setDate(int year, int month, int dayOfMonth) { - mCurrentDate.set(year, month, dayOfMonth); - if (mCurrentDate.before(mMinDate)) { - mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); - } else if (mCurrentDate.after(mMaxDate)) { - mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); - } - } - - private void updateSpinners() { - // set the spinner ranges respecting the min and max dates - if (mCurrentDate.equals(mMinDate)) { - mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); - mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); - mDaySpinner.setWrapSelectorWheel(false); - mMonthSpinner.setDisplayedValues(null); - mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH)); - mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH)); - mMonthSpinner.setWrapSelectorWheel(false); - } else if (mCurrentDate.equals(mMaxDate)) { - mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH)); - mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); - mDaySpinner.setWrapSelectorWheel(false); - mMonthSpinner.setDisplayedValues(null); - mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH)); - mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH)); - mMonthSpinner.setWrapSelectorWheel(false); - } else { - mDaySpinner.setMinValue(1); - mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); - mDaySpinner.setWrapSelectorWheel(true); - mMonthSpinner.setDisplayedValues(null); - mMonthSpinner.setMinValue(0); - mMonthSpinner.setMaxValue(11); - mMonthSpinner.setWrapSelectorWheel(true); - } - - // make sure the month names are a zero based array - // with the months in the month spinner - String[] displayedValues = Arrays.copyOfRange(mShortMonths, - mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1); - mMonthSpinner.setDisplayedValues(displayedValues); - - // year spinner range does not change based on the current date - mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR)); - mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR)); - mYearSpinner.setWrapSelectorWheel(false); - - // set the spinner values - mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR)); - mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH)); - mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); - - if (usingNumericMonths()) { - mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER); - } - } - - /** - * Updates the calendar view with the current date. - */ - private void updateCalendarView() { - mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false); - } - - - /** - * Notifies the listener, if such, for a change in the selected date. - */ - private void notifyDateChanged() { - mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - if (mOnDateChangedListener != null) { - mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(), - getDayOfMonth()); - } - } - - /** - * Sets the IME options for a spinner based on its ordering. - * - * @param spinner The spinner. - * @param spinnerCount The total spinner count. - * @param spinnerIndex The index of the given spinner. - */ - private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) { - final int imeOptions; - if (spinnerIndex < spinnerCount - 1) { - imeOptions = EditorInfo.IME_ACTION_NEXT; - } else { - imeOptions = EditorInfo.IME_ACTION_DONE; - } - TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input); - input.setImeOptions(imeOptions); - } - - private void setContentDescriptions() { - // Day - trySetContentDescription(mDaySpinner, R.id.increment, - R.string.date_picker_increment_day_button); - trySetContentDescription(mDaySpinner, R.id.decrement, - R.string.date_picker_decrement_day_button); - // Month - trySetContentDescription(mMonthSpinner, R.id.increment, - R.string.date_picker_increment_month_button); - trySetContentDescription(mMonthSpinner, R.id.decrement, - R.string.date_picker_decrement_month_button); - // Year - trySetContentDescription(mYearSpinner, R.id.increment, - R.string.date_picker_increment_year_button); - trySetContentDescription(mYearSpinner, R.id.decrement, - R.string.date_picker_decrement_year_button); - } - - private void trySetContentDescription(View root, int viewId, int contDescResId) { - View target = root.findViewById(viewId); - if (target != null) { - target.setContentDescription(mContext.getString(contDescResId)); - } - } - - private void updateInputState() { - // Make sure that if the user changes the value and the IME is active - // for one of the inputs if this widget, the IME is closed. If the user - // changed the value via the IME and there is a next input the IME will - // be shown, otherwise the user chose another means of changing the - // value and having the IME up makes no sense. - InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); - if (inputMethodManager != null) { - if (inputMethodManager.isActive(mYearSpinnerInput)) { - mYearSpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); - } else if (inputMethodManager.isActive(mMonthSpinnerInput)) { - mMonthSpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); - } else if (inputMethodManager.isActive(mDaySpinnerInput)) { - mDaySpinnerInput.clearFocus(); - inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); - } - } - } - } - - /** - * Class for managing state storing/restoring. - */ - private static class SavedState extends BaseSavedState { - - private final int mYear; - - private final int mMonth; - - private final int mDay; - - /** - * Constructor called from {@link DatePicker#onSaveInstanceState()} - */ - private SavedState(Parcelable superState, int year, int month, int day) { - super(superState); - mYear = year; - mMonth = month; - mDay = day; - } - - /** - * Constructor called from {@link #CREATOR} - */ - private SavedState(Parcel in) { - super(in); - mYear = in.readInt(); - mMonth = in.readInt(); - mDay = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(mYear); - dest.writeInt(mMonth); - dest.writeInt(mDay); - } - - @SuppressWarnings("all") - // suppress unused and hiding - public static final Parcelable.Creator CREATOR = new Creator() { - - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } } diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java index cbb61093cd39d..5adac01957018 100755 --- a/core/java/android/widget/DatePickerCalendarDelegate.java +++ b/core/java/android/widget/DatePickerCalendarDelegate.java @@ -16,13 +16,14 @@ package android.widget; +import com.android.internal.R; + import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; -import android.os.Parcel; import android.os.Parcelable; import android.text.format.DateFormat; import android.text.format.DateUtils; @@ -37,8 +38,6 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.DayPickerView.OnDaySelectedListener; import android.widget.YearPickerView.OnYearSelectedListener; -import com.android.internal.R; - import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; @@ -550,25 +549,27 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { @Override public void onRestoreInstanceState(Parcelable state) { - final SavedState ss = (SavedState) state; + if (state instanceof SavedState) { + final SavedState ss = (SavedState) state; - // TODO: Move instance state into DayPickerView, YearPickerView. - mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay()); - mMinDate.setTimeInMillis(ss.getMinDate()); - mMaxDate.setTimeInMillis(ss.getMaxDate()); + // TODO: Move instance state into DayPickerView, YearPickerView. + mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay()); + mMinDate.setTimeInMillis(ss.getMinDate()); + mMaxDate.setTimeInMillis(ss.getMaxDate()); - onCurrentDateChanged(false); + onCurrentDateChanged(false); - final int currentView = ss.getCurrentView(); - setCurrentView(currentView); + final int currentView = ss.getCurrentView(); + setCurrentView(currentView); - final int listPosition = ss.getListPosition(); - if (listPosition != -1) { - if (currentView == VIEW_MONTH_DAY) { - mDayPickerView.setPosition(listPosition); - } else if (currentView == VIEW_YEAR) { - final int listPositionOffset = ss.getListPositionOffset(); - mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset); + final int listPosition = ss.getListPosition(); + if (listPosition != -1) { + if (currentView == VIEW_MONTH_DAY) { + mDayPickerView.setPosition(listPosition); + } else if (currentView == VIEW_YEAR) { + final int listPositionOffset = ss.getListPositionOffset(); + mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset); + } } } } @@ -613,108 +614,4 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { private void tryVibrate() { mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE); } - - /** - * Class for managing state storing/restoring. - */ - private static class SavedState extends View.BaseSavedState { - private final int mSelectedYear; - private final int mSelectedMonth; - private final int mSelectedDay; - private final long mMinDate; - private final long mMaxDate; - private final int mCurrentView; - private final int mListPosition; - private final int mListPositionOffset; - - /** - * Constructor called from {@link DatePicker#onSaveInstanceState()} - */ - private SavedState(Parcelable superState, int year, int month, int day, - long minDate, long maxDate, int currentView, int listPosition, - int listPositionOffset) { - super(superState); - mSelectedYear = year; - mSelectedMonth = month; - mSelectedDay = day; - mMinDate = minDate; - mMaxDate = maxDate; - mCurrentView = currentView; - mListPosition = listPosition; - mListPositionOffset = listPositionOffset; - } - - /** - * Constructor called from {@link #CREATOR} - */ - private SavedState(Parcel in) { - super(in); - mSelectedYear = in.readInt(); - mSelectedMonth = in.readInt(); - mSelectedDay = in.readInt(); - mMinDate = in.readLong(); - mMaxDate = in.readLong(); - mCurrentView = in.readInt(); - mListPosition = in.readInt(); - mListPositionOffset = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(mSelectedYear); - dest.writeInt(mSelectedMonth); - dest.writeInt(mSelectedDay); - dest.writeLong(mMinDate); - dest.writeLong(mMaxDate); - dest.writeInt(mCurrentView); - dest.writeInt(mListPosition); - dest.writeInt(mListPositionOffset); - } - - public int getSelectedDay() { - return mSelectedDay; - } - - public int getSelectedMonth() { - return mSelectedMonth; - } - - public int getSelectedYear() { - return mSelectedYear; - } - - public long getMinDate() { - return mMinDate; - } - - public long getMaxDate() { - return mMaxDate; - } - - public int getCurrentView() { - return mCurrentView; - } - - public int getListPosition() { - return mListPosition; - } - - public int getListPositionOffset() { - return mListPositionOffset; - } - - @SuppressWarnings("all") - // suppress unused and hiding - public static final Parcelable.Creator CREATOR = new Creator() { - - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } } diff --git a/core/java/android/widget/DatePickerSpinnerDelegate.java b/core/java/android/widget/DatePickerSpinnerDelegate.java new file mode 100644 index 0000000000000..255de79dbf32a --- /dev/null +++ b/core/java/android/widget/DatePickerSpinnerDelegate.java @@ -0,0 +1,652 @@ +/* + * Copyright (C) 2016 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 android.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.os.Parcelable; +import android.text.InputType; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.DatePicker.AbstractDatePickerDelegate; +import android.widget.NumberPicker.OnValueChangeListener; + +import java.text.DateFormatSymbols; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Locale; + +import libcore.icu.ICU; + +/** + * A delegate implementing the basic DatePicker + */ +class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate { + + private static final String DATE_FORMAT = "MM/dd/yyyy"; + + private static final int DEFAULT_START_YEAR = 1900; + + private static final int DEFAULT_END_YEAR = 2100; + + private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true; + + private static final boolean DEFAULT_SPINNERS_SHOWN = true; + + private static final boolean DEFAULT_ENABLED_STATE = true; + + private final LinearLayout mSpinners; + + private final NumberPicker mDaySpinner; + + private final NumberPicker mMonthSpinner; + + private final NumberPicker mYearSpinner; + + private final EditText mDaySpinnerInput; + + private final EditText mMonthSpinnerInput; + + private final EditText mYearSpinnerInput; + + private final CalendarView mCalendarView; + + private String[] mShortMonths; + + private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); + + private int mNumberOfMonths; + + private Calendar mTempDate; + + private Calendar mMinDate; + + private Calendar mMaxDate; + + private Calendar mCurrentDate; + + private boolean mIsEnabled = DEFAULT_ENABLED_STATE; + + DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(delegator, context); + + mDelegator = delegator; + mContext = context; + + // initialization based on locale + setCurrentLocale(Locale.getDefault()); + + final TypedArray attributesArray = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.DatePicker, defStyleAttr, defStyleRes); + boolean spinnersShown = attributesArray.getBoolean(com.android.internal.R.styleable.DatePicker_spinnersShown, + DEFAULT_SPINNERS_SHOWN); + boolean calendarViewShown = attributesArray.getBoolean( + com.android.internal.R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN); + int startYear = attributesArray.getInt(com.android.internal.R.styleable.DatePicker_startYear, + DEFAULT_START_YEAR); + int endYear = attributesArray.getInt(com.android.internal.R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); + String minDate = attributesArray.getString(com.android.internal.R.styleable.DatePicker_minDate); + String maxDate = attributesArray.getString(com.android.internal.R.styleable.DatePicker_maxDate); + int layoutResourceId = attributesArray.getResourceId( + com.android.internal.R.styleable.DatePicker_legacyLayout, com.android.internal.R.layout.date_picker_legacy); + attributesArray.recycle(); + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(layoutResourceId, mDelegator, true); + + OnValueChangeListener onChangeListener = new OnValueChangeListener() { + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + updateInputState(); + mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis()); + // take care of wrapping of days and months to update greater fields + if (picker == mDaySpinner) { + int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH); + if (oldVal == maxDayOfMonth && newVal == 1) { + mTempDate.add(Calendar.DAY_OF_MONTH, 1); + } else if (oldVal == 1 && newVal == maxDayOfMonth) { + mTempDate.add(Calendar.DAY_OF_MONTH, -1); + } else { + mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal); + } + } else if (picker == mMonthSpinner) { + if (oldVal == 11 && newVal == 0) { + mTempDate.add(Calendar.MONTH, 1); + } else if (oldVal == 0 && newVal == 11) { + mTempDate.add(Calendar.MONTH, -1); + } else { + mTempDate.add(Calendar.MONTH, newVal - oldVal); + } + } else if (picker == mYearSpinner) { + mTempDate.set(Calendar.YEAR, newVal); + } else { + throw new IllegalArgumentException(); + } + // now set the date to the adjusted one + setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH), + mTempDate.get(Calendar.DAY_OF_MONTH)); + updateSpinners(); + updateCalendarView(); + notifyDateChanged(); + } + }; + + mSpinners = (LinearLayout) mDelegator.findViewById(com.android.internal.R.id.pickers); + + // calendar view day-picker + mCalendarView = (CalendarView) mDelegator.findViewById(com.android.internal.R.id.calendar_view); + mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() { + public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) { + setDate(year, month, monthDay); + updateSpinners(); + notifyDateChanged(); + } + }); + + // day + mDaySpinner = (NumberPicker) mDelegator.findViewById(com.android.internal.R.id.day); + mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); + mDaySpinner.setOnLongPressUpdateInterval(100); + mDaySpinner.setOnValueChangedListener(onChangeListener); + mDaySpinnerInput = (EditText) mDaySpinner.findViewById(com.android.internal.R.id.numberpicker_input); + + // month + mMonthSpinner = (NumberPicker) mDelegator.findViewById(com.android.internal.R.id.month); + mMonthSpinner.setMinValue(0); + mMonthSpinner.setMaxValue(mNumberOfMonths - 1); + mMonthSpinner.setDisplayedValues(mShortMonths); + mMonthSpinner.setOnLongPressUpdateInterval(200); + mMonthSpinner.setOnValueChangedListener(onChangeListener); + mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(com.android.internal.R.id.numberpicker_input); + + // year + mYearSpinner = (NumberPicker) mDelegator.findViewById(com.android.internal.R.id.year); + mYearSpinner.setOnLongPressUpdateInterval(100); + mYearSpinner.setOnValueChangedListener(onChangeListener); + mYearSpinnerInput = (EditText) mYearSpinner.findViewById(com.android.internal.R.id.numberpicker_input); + + // show only what the user required but make sure we + // show something and the spinners have higher priority + if (!spinnersShown && !calendarViewShown) { + setSpinnersShown(true); + } else { + setSpinnersShown(spinnersShown); + setCalendarViewShown(calendarViewShown); + } + + // set the min date giving priority of the minDate over startYear + mTempDate.clear(); + if (!TextUtils.isEmpty(minDate)) { + if (!parseDate(minDate, mTempDate)) { + mTempDate.set(startYear, 0, 1); + } + } else { + mTempDate.set(startYear, 0, 1); + } + setMinDate(mTempDate.getTimeInMillis()); + + // set the max date giving priority of the maxDate over endYear + mTempDate.clear(); + if (!TextUtils.isEmpty(maxDate)) { + if (!parseDate(maxDate, mTempDate)) { + mTempDate.set(endYear, 11, 31); + } + } else { + mTempDate.set(endYear, 11, 31); + } + setMaxDate(mTempDate.getTimeInMillis()); + + // initialize to current date + mCurrentDate.setTimeInMillis(System.currentTimeMillis()); + init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate + .get(Calendar.DAY_OF_MONTH), null); + + // re-order the number spinners to match the current date format + reorderSpinners(); + + // accessibility + setContentDescriptions(); + + // If not explicitly specified this view is important for accessibility. + if (mDelegator.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + mDelegator.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + } + + @Override + public void init(int year, int monthOfYear, int dayOfMonth, + DatePicker.OnDateChangedListener onDateChangedListener) { + setDate(year, monthOfYear, dayOfMonth); + updateSpinners(); + updateCalendarView(); + mOnDateChangedListener = onDateChangedListener; + } + + @Override + public void updateDate(int year, int month, int dayOfMonth) { + if (!isNewDate(year, month, dayOfMonth)) { + return; + } + setDate(year, month, dayOfMonth); + updateSpinners(); + updateCalendarView(); + notifyDateChanged(); + } + + @Override + public int getYear() { + return mCurrentDate.get(Calendar.YEAR); + } + + @Override + public int getMonth() { + return mCurrentDate.get(Calendar.MONTH); + } + + @Override + public int getDayOfMonth() { + return mCurrentDate.get(Calendar.DAY_OF_MONTH); + } + + @Override + public void setFirstDayOfWeek(int firstDayOfWeek) { + mCalendarView.setFirstDayOfWeek(firstDayOfWeek); + } + + @Override + public int getFirstDayOfWeek() { + return mCalendarView.getFirstDayOfWeek(); + } + + @Override + public void setMinDate(long minDate) { + mTempDate.setTimeInMillis(minDate); + if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) + && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { + return; + } + mMinDate.setTimeInMillis(minDate); + mCalendarView.setMinDate(minDate); + if (mCurrentDate.before(mMinDate)) { + mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); + updateCalendarView(); + } + updateSpinners(); + } + + @Override + public Calendar getMinDate() { + final Calendar minDate = Calendar.getInstance(); + minDate.setTimeInMillis(mCalendarView.getMinDate()); + return minDate; + } + + @Override + public void setMaxDate(long maxDate) { + mTempDate.setTimeInMillis(maxDate); + if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) + && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) { + return; + } + mMaxDate.setTimeInMillis(maxDate); + mCalendarView.setMaxDate(maxDate); + if (mCurrentDate.after(mMaxDate)) { + mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); + updateCalendarView(); + } + updateSpinners(); + } + + @Override + public Calendar getMaxDate() { + final Calendar maxDate = Calendar.getInstance(); + maxDate.setTimeInMillis(mCalendarView.getMaxDate()); + return maxDate; + } + + @Override + public void setEnabled(boolean enabled) { + mDaySpinner.setEnabled(enabled); + mMonthSpinner.setEnabled(enabled); + mYearSpinner.setEnabled(enabled); + mCalendarView.setEnabled(enabled); + mIsEnabled = enabled; + } + + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + @Override + public CalendarView getCalendarView() { + return mCalendarView; + } + + @Override + public void setCalendarViewShown(boolean shown) { + mCalendarView.setVisibility(shown ? View.VISIBLE : View.GONE); + } + + @Override + public boolean getCalendarViewShown() { + return (mCalendarView.getVisibility() == View.VISIBLE); + } + + @Override + public void setSpinnersShown(boolean shown) { + mSpinners.setVisibility(shown ? View.VISIBLE : View.GONE); + } + + @Override + public boolean getSpinnersShown() { + return mSpinners.isShown(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + setCurrentLocale(newConfig.locale); + } + + @Override + public Parcelable onSaveInstanceState(Parcelable superState) { + return new SavedState(superState, getYear(), getMonth(), getDayOfMonth(), + getMinDate().getTimeInMillis(), getMaxDate().getTimeInMillis()); + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (state instanceof SavedState) { + final SavedState ss = (SavedState) state; + setDate(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay()); + updateSpinners(); + updateCalendarView(); + } + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + onPopulateAccessibilityEvent(event); + return true; + } + + @Override + public void onPopulateAccessibilityEvent(AccessibilityEvent event) { + final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; + String selectedDateUtterance = DateUtils.formatDateTime(mContext, + mCurrentDate.getTimeInMillis(), flags); + event.getText().add(selectedDateUtterance); + } + + /** + * Sets the current locale. + * + * @param locale The current locale. + */ + @Override + protected void setCurrentLocale(Locale locale) { + super.setCurrentLocale(locale); + + mTempDate = getCalendarForLocale(mTempDate, locale); + mMinDate = getCalendarForLocale(mMinDate, locale); + mMaxDate = getCalendarForLocale(mMaxDate, locale); + mCurrentDate = getCalendarForLocale(mCurrentDate, locale); + + mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1; + mShortMonths = new DateFormatSymbols().getShortMonths(); + + if (usingNumericMonths()) { + // We're in a locale where a date should either be all-numeric, or all-text. + // All-text would require custom NumberPicker formatters for day and year. + mShortMonths = new String[mNumberOfMonths]; + for (int i = 0; i < mNumberOfMonths; ++i) { + mShortMonths[i] = String.format("%d", i + 1); + } + } + } + + /** + * Tests whether the current locale is one where there are no real month names, + * such as Chinese, Japanese, or Korean locales. + */ + private boolean usingNumericMonths() { + return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0)); + } + + /** + * Gets a calendar for locale bootstrapped with the value of a given calendar. + * + * @param oldCalendar The old calendar. + * @param locale The locale. + */ + private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { + if (oldCalendar == null) { + return Calendar.getInstance(locale); + } else { + final long currentTimeMillis = oldCalendar.getTimeInMillis(); + Calendar newCalendar = Calendar.getInstance(locale); + newCalendar.setTimeInMillis(currentTimeMillis); + return newCalendar; + } + } + + /** + * Reorders the spinners according to the date format that is + * explicitly set by the user and if no such is set fall back + * to the current locale's default format. + */ + private void reorderSpinners() { + mSpinners.removeAllViews(); + // We use numeric spinners for year and day, but textual months. Ask icu4c what + // order the user's locale uses for that combination. http://b/7207103. + String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd"); + char[] order = ICU.getDateFormatOrder(pattern); + final int spinnerCount = order.length; + for (int i = 0; i < spinnerCount; i++) { + switch (order[i]) { + case 'd': + mSpinners.addView(mDaySpinner); + setImeOptions(mDaySpinner, spinnerCount, i); + break; + case 'M': + mSpinners.addView(mMonthSpinner); + setImeOptions(mMonthSpinner, spinnerCount, i); + break; + case 'y': + mSpinners.addView(mYearSpinner); + setImeOptions(mYearSpinner, spinnerCount, i); + break; + default: + throw new IllegalArgumentException(Arrays.toString(order)); + } + } + } + + /** + * Parses the given date and in case of success sets the result + * to the outDate. + * + * @return True if the date was parsed. + */ + private boolean parseDate(String date, Calendar outDate) { + try { + outDate.setTime(mDateFormat.parse(date)); + return true; + } catch (ParseException e) { + e.printStackTrace(); + return false; + } + } + + private boolean isNewDate(int year, int month, int dayOfMonth) { + return (mCurrentDate.get(Calendar.YEAR) != year + || mCurrentDate.get(Calendar.MONTH) != month + || mCurrentDate.get(Calendar.DAY_OF_MONTH) != dayOfMonth); + } + + private void setDate(int year, int month, int dayOfMonth) { + mCurrentDate.set(year, month, dayOfMonth); + if (mCurrentDate.before(mMinDate)) { + mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); + } else if (mCurrentDate.after(mMaxDate)) { + mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); + } + } + + private void updateSpinners() { + // set the spinner ranges respecting the min and max dates + if (mCurrentDate.equals(mMinDate)) { + mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); + mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); + mDaySpinner.setWrapSelectorWheel(false); + mMonthSpinner.setDisplayedValues(null); + mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH)); + mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH)); + mMonthSpinner.setWrapSelectorWheel(false); + } else if (mCurrentDate.equals(mMaxDate)) { + mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH)); + mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); + mDaySpinner.setWrapSelectorWheel(false); + mMonthSpinner.setDisplayedValues(null); + mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH)); + mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH)); + mMonthSpinner.setWrapSelectorWheel(false); + } else { + mDaySpinner.setMinValue(1); + mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); + mDaySpinner.setWrapSelectorWheel(true); + mMonthSpinner.setDisplayedValues(null); + mMonthSpinner.setMinValue(0); + mMonthSpinner.setMaxValue(11); + mMonthSpinner.setWrapSelectorWheel(true); + } + + // make sure the month names are a zero based array + // with the months in the month spinner + String[] displayedValues = Arrays.copyOfRange(mShortMonths, + mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1); + mMonthSpinner.setDisplayedValues(displayedValues); + + // year spinner range does not change based on the current date + mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR)); + mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR)); + mYearSpinner.setWrapSelectorWheel(false); + + // set the spinner values + mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR)); + mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH)); + mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); + + if (usingNumericMonths()) { + mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER); + } + } + + /** + * Updates the calendar view with the current date. + */ + private void updateCalendarView() { + mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false); + } + + + /** + * Notifies the listener, if such, for a change in the selected date. + */ + private void notifyDateChanged() { + mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + if (mOnDateChangedListener != null) { + mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(), + getDayOfMonth()); + } + } + + /** + * Sets the IME options for a spinner based on its ordering. + * + * @param spinner The spinner. + * @param spinnerCount The total spinner count. + * @param spinnerIndex The index of the given spinner. + */ + private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) { + final int imeOptions; + if (spinnerIndex < spinnerCount - 1) { + imeOptions = EditorInfo.IME_ACTION_NEXT; + } else { + imeOptions = EditorInfo.IME_ACTION_DONE; + } + TextView input = (TextView) spinner.findViewById(com.android.internal.R.id.numberpicker_input); + input.setImeOptions(imeOptions); + } + + private void setContentDescriptions() { + // Day + trySetContentDescription(mDaySpinner, com.android.internal.R.id.increment, + com.android.internal.R.string.date_picker_increment_day_button); + trySetContentDescription(mDaySpinner, com.android.internal.R.id.decrement, + com.android.internal.R.string.date_picker_decrement_day_button); + // Month + trySetContentDescription(mMonthSpinner, com.android.internal.R.id.increment, + com.android.internal.R.string.date_picker_increment_month_button); + trySetContentDescription(mMonthSpinner, com.android.internal.R.id.decrement, + com.android.internal.R.string.date_picker_decrement_month_button); + // Year + trySetContentDescription(mYearSpinner, com.android.internal.R.id.increment, + com.android.internal.R.string.date_picker_increment_year_button); + trySetContentDescription(mYearSpinner, com.android.internal.R.id.decrement, + com.android.internal.R.string.date_picker_decrement_year_button); + } + + private void trySetContentDescription(View root, int viewId, int contDescResId) { + View target = root.findViewById(viewId); + if (target != null) { + target.setContentDescription(mContext.getString(contDescResId)); + } + } + + private void updateInputState() { + // Make sure that if the user changes the value and the IME is active + // for one of the inputs if this widget, the IME is closed. If the user + // changed the value via the IME and there is a next input the IME will + // be shown, otherwise the user chose another means of changing the + // value and having the IME up makes no sense. + InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); + if (inputMethodManager != null) { + if (inputMethodManager.isActive(mYearSpinnerInput)) { + mYearSpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } else if (inputMethodManager.isActive(mMonthSpinnerInput)) { + mMonthSpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } else if (inputMethodManager.isActive(mDaySpinnerInput)) { + mDaySpinnerInput.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); + } + } + } +} diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index a24d37f4b097c..f2fc617ccc33e 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -22,8 +22,11 @@ import android.annotation.Widget; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; +import android.os.Parcel; import android.os.Parcelable; +import android.os.Parcelable.Creator; import android.util.AttributeSet; +import android.view.View; import android.view.accessibility.AccessibilityEvent; import com.android.internal.R; @@ -301,5 +304,69 @@ public class TimePicker extends FrameLayout { mContext = context; mLocale = context.getResources().getConfiguration().locale; } + + protected static class SavedState extends View.BaseSavedState { + private final int mHour; + private final int mMinute; + private final boolean mIs24HourMode; + private final int mCurrentItemShowing; + + public SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode) { + this(superState, hour, minute, is24HourMode, 0); + } + + public SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode, + int currentItemShowing) { + super(superState); + mHour = hour; + mMinute = minute; + mIs24HourMode = is24HourMode; + mCurrentItemShowing = currentItemShowing; + } + + private SavedState(Parcel in) { + super(in); + mHour = in.readInt(); + mMinute = in.readInt(); + mIs24HourMode = (in.readInt() == 1); + mCurrentItemShowing = in.readInt(); + } + + public int getHour() { + return mHour; + } + + public int getMinute() { + return mMinute; + } + + public boolean is24HourMode() { + return mIs24HourMode; + } + + public int getCurrentItemShowing() { + return mCurrentItemShowing; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mHour); + dest.writeInt(mMinute); + dest.writeInt(mIs24HourMode ? 1 : 0); + dest.writeInt(mCurrentItemShowing); + } + + @SuppressWarnings({"unused", "hiding"}) + public static final Creator CREATOR = new Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } } } diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java index 05fd4c8ab69f3..4a24e26a28287 100644 --- a/core/java/android/widget/TimePickerClockDelegate.java +++ b/core/java/android/widget/TimePickerClockDelegate.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; -import android.os.Parcel; import android.os.Parcelable; import android.text.SpannableStringBuilder; import android.text.format.DateFormat; @@ -45,7 +44,6 @@ import com.android.internal.widget.NumericTextView; import com.android.internal.widget.NumericTextView.OnValueChangedListener; import java.util.Calendar; -import java.util.Locale; /** * A delegate implementing the radial clock-based TimePicker. @@ -501,9 +499,11 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl @Override public void onRestoreInstanceState(Parcelable state) { - final SavedState ss = (SavedState) state; - initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing()); - mRadialTimePickerView.invalidate(); + if (state instanceof SavedState) { + final SavedState ss = (SavedState) state; + initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing()); + mRadialTimePickerView.invalidate(); + } } @Override @@ -544,70 +544,6 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl } } - /** - * Used to save / restore state of time picker - */ - private static class SavedState extends View.BaseSavedState { - - private final int mHour; - private final int mMinute; - private final boolean mIs24HourMode; - private final int mCurrentItemShowing; - - private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode, - int currentItemShowing) { - super(superState); - mHour = hour; - mMinute = minute; - mIs24HourMode = is24HourMode; - mCurrentItemShowing = currentItemShowing; - } - - private SavedState(Parcel in) { - super(in); - mHour = in.readInt(); - mMinute = in.readInt(); - mIs24HourMode = (in.readInt() == 1); - mCurrentItemShowing = in.readInt(); - } - - public int getHour() { - return mHour; - } - - public int getMinute() { - return mMinute; - } - - public boolean is24HourMode() { - return mIs24HourMode; - } - - public int getCurrentItemShowing() { - return mCurrentItemShowing; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(mHour); - dest.writeInt(mMinute); - dest.writeInt(mIs24HourMode ? 1 : 0); - dest.writeInt(mCurrentItemShowing); - } - - @SuppressWarnings({"unused", "hiding"}) - public static final Creator CREATOR = new Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - private void tryVibrate() { mDelegator.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); } diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java index 863d409138362..b113fd94f21d7 100644 --- a/core/java/android/widget/TimePickerSpinnerDelegate.java +++ b/core/java/android/widget/TimePickerSpinnerDelegate.java @@ -18,7 +18,6 @@ package android.widget; import android.content.Context; import android.content.res.TypedArray; -import android.os.Parcel; import android.os.Parcelable; import android.text.format.DateFormat; import android.text.format.DateUtils; @@ -32,7 +31,6 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.R; import java.util.Calendar; -import java.util.Locale; import libcore.icu.LocaleData; @@ -387,14 +385,16 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { @Override public Parcelable onSaveInstanceState(Parcelable superState) { - return new SavedState(superState, getHour(), getMinute()); + return new SavedState(superState, getHour(), getMinute(), is24Hour()); } @Override public void onRestoreInstanceState(Parcelable state) { - SavedState ss = (SavedState) state; - setHour(ss.getHour()); - setMinute(ss.getMinute()); + if (state instanceof SavedState) { + final SavedState ss = (SavedState) state; + setHour(ss.getHour()); + setMinute(ss.getMinute()); + } } @Override @@ -525,52 +525,6 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } } - /** - * Used to save / restore state of time picker - */ - private static class SavedState extends View.BaseSavedState { - private final int mHour; - private final int mMinute; - - private SavedState(Parcelable superState, int hour, int minute) { - super(superState); - mHour = hour; - mMinute = minute; - } - - private SavedState(Parcel in) { - super(in); - mHour = in.readInt(); - mMinute = in.readInt(); - } - - public int getHour() { - return mHour; - } - - public int getMinute() { - return mMinute; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(mHour); - dest.writeInt(mMinute); - } - - @SuppressWarnings({"unused", "hiding"}) - public static final Parcelable.Creator CREATOR = new Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - public static String[] getAmPmStrings(Context context) { String[] result = new String[2]; LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale); diff --git a/core/res/res/values-h426dp-port/integers.xml b/core/res/res/values-h426dp-port/integers.xml new file mode 100644 index 0000000000000..94abbecdf64c1 --- /dev/null +++ b/core/res/res/values-h426dp-port/integers.xml @@ -0,0 +1,22 @@ + + + + 2 + 2 + diff --git a/core/res/res/values-w426dp-land/integers.xml b/core/res/res/values-w426dp-land/integers.xml new file mode 100644 index 0000000000000..94abbecdf64c1 --- /dev/null +++ b/core/res/res/values-w426dp-land/integers.xml @@ -0,0 +1,22 @@ + + + + 2 + 2 + diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml index 8e272267efc9d..8f8d59e957828 100644 --- a/core/res/res/values/integers.xml +++ b/core/res/res/values/integers.xml @@ -23,4 +23,7 @@ 100 100 250 + + 1 + 1 diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index db418f7947b97..a9c8a06369f5a 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -652,7 +652,7 @@ please see styles_device_defaults.xml.