1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget; 18 19 import android.annotation.IntDef; 20 import android.annotation.Nullable; 21 import android.annotation.TestApi; 22 import android.annotation.UnsupportedAppUsage; 23 import android.annotation.Widget; 24 import android.content.Context; 25 import android.content.res.Configuration; 26 import android.content.res.TypedArray; 27 import android.icu.util.Calendar; 28 import android.icu.util.TimeZone; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.text.format.DateUtils; 32 import android.util.AttributeSet; 33 import android.util.Log; 34 import android.util.SparseArray; 35 import android.view.View; 36 import android.view.ViewStructure; 37 import android.view.accessibility.AccessibilityEvent; 38 import android.view.autofill.AutofillManager; 39 import android.view.autofill.AutofillValue; 40 import android.view.inspector.InspectableProperty; 41 42 import com.android.internal.R; 43 44 import java.lang.annotation.Retention; 45 import java.lang.annotation.RetentionPolicy; 46 import java.util.Locale; 47 48 /** 49 * Provides a widget for selecting a date. 50 * <p> 51 * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is 52 * set to {@code spinner}, the date can be selected using year, month, and day 53 * spinners or a {@link CalendarView}. The set of spinners and the calendar 54 * view are automatically synchronized. The client can customize whether only 55 * the spinners, or only the calendar view, or both to be displayed. 56 * </p> 57 * <p> 58 * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is 59 * set to {@code calendar}, the month and day can be selected using a 60 * calendar-style view while the year can be selected separately using a list. 61 * </p> 62 * <p> 63 * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a> 64 * guide. 65 * </p> 66 * <p> 67 * For a dialog using this view, see {@link android.app.DatePickerDialog}. 68 * </p> 69 * 70 * @attr ref android.R.styleable#DatePicker_startYear 71 * @attr ref android.R.styleable#DatePicker_endYear 72 * @attr ref android.R.styleable#DatePicker_maxDate 73 * @attr ref android.R.styleable#DatePicker_minDate 74 * @attr ref android.R.styleable#DatePicker_spinnersShown 75 * @attr ref android.R.styleable#DatePicker_calendarViewShown 76 * @attr ref android.R.styleable#DatePicker_dayOfWeekBackground 77 * @attr ref android.R.styleable#DatePicker_dayOfWeekTextAppearance 78 * @attr ref android.R.styleable#DatePicker_headerBackground 79 * @attr ref android.R.styleable#DatePicker_headerMonthTextAppearance 80 * @attr ref android.R.styleable#DatePicker_headerDayOfMonthTextAppearance 81 * @attr ref android.R.styleable#DatePicker_headerYearTextAppearance 82 * @attr ref android.R.styleable#DatePicker_yearListItemTextAppearance 83 * @attr ref android.R.styleable#DatePicker_yearListSelectorColor 84 * @attr ref android.R.styleable#DatePicker_calendarTextColor 85 * @attr ref android.R.styleable#DatePicker_datePickerMode 86 */ 87 @Widget 88 public class DatePicker extends FrameLayout { 89 private static final String LOG_TAG = DatePicker.class.getSimpleName(); 90 91 /** 92 * Presentation mode for the Holo-style date picker that uses a set of 93 * {@link android.widget.NumberPicker}s. 94 * 95 * @see #getMode() 96 * @hide Visible for testing only. 97 */ 98 @TestApi 99 public static final int MODE_SPINNER = 1; 100 101 /** 102 * Presentation mode for the Material-style date picker that uses a 103 * calendar. 104 * 105 * @see #getMode() 106 * @hide Visible for testing only. 107 */ 108 @TestApi 109 public static final int MODE_CALENDAR = 2; 110 111 /** @hide */ 112 @IntDef(prefix = { "MODE_" }, value = { 113 MODE_SPINNER, 114 MODE_CALENDAR 115 }) 116 @Retention(RetentionPolicy.SOURCE) 117 public @interface DatePickerMode {} 118 119 @UnsupportedAppUsage 120 private final DatePickerDelegate mDelegate; 121 122 @DatePickerMode 123 private final int mMode; 124 125 /** 126 * The callback used to indicate the user changed the date. 127 */ 128 public interface OnDateChangedListener { 129 130 /** 131 * Called upon a date change. 132 * 133 * @param view The view associated with this listener. 134 * @param year The year that was set. 135 * @param monthOfYear The month that was set (0-11) for compatibility 136 * with {@link java.util.Calendar}. 137 * @param dayOfMonth The day of the month that was set. 138 */ onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth)139 void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth); 140 } 141 DatePicker(Context context)142 public DatePicker(Context context) { 143 this(context, null); 144 } 145 DatePicker(Context context, AttributeSet attrs)146 public DatePicker(Context context, AttributeSet attrs) { 147 this(context, attrs, R.attr.datePickerStyle); 148 } 149 DatePicker(Context context, AttributeSet attrs, int defStyleAttr)150 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) { 151 this(context, attrs, defStyleAttr, 0); 152 } 153 DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)154 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 155 super(context, attrs, defStyleAttr, defStyleRes); 156 157 // DatePicker is important by default, unless app developer overrode attribute. 158 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { 159 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); 160 } 161 162 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker, 163 defStyleAttr, defStyleRes); 164 saveAttributeDataForStyleable(context, R.styleable.DatePicker, 165 attrs, a, defStyleAttr, defStyleRes); 166 final boolean isDialogMode = a.getBoolean(R.styleable.DatePicker_dialogMode, false); 167 final int requestedMode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER); 168 final int firstDayOfWeek = a.getInt(R.styleable.DatePicker_firstDayOfWeek, 0); 169 a.recycle(); 170 171 if (requestedMode == MODE_CALENDAR && isDialogMode) { 172 // You want MODE_CALENDAR? YOU CAN'T HANDLE MODE_CALENDAR! Well, 173 // maybe you can depending on your screen size. Let's check... 174 mMode = context.getResources().getInteger(R.integer.date_picker_mode); 175 } else { 176 mMode = requestedMode; 177 } 178 179 switch (mMode) { 180 case MODE_CALENDAR: 181 mDelegate = createCalendarUIDelegate(context, attrs, defStyleAttr, defStyleRes); 182 break; 183 case MODE_SPINNER: 184 default: 185 mDelegate = createSpinnerUIDelegate(context, attrs, defStyleAttr, defStyleRes); 186 break; 187 } 188 189 if (firstDayOfWeek != 0) { 190 setFirstDayOfWeek(firstDayOfWeek); 191 } 192 193 mDelegate.setAutoFillChangeListener((v, y, m, d) -> { 194 final AutofillManager afm = context.getSystemService(AutofillManager.class); 195 if (afm != null) { 196 afm.notifyValueChanged(this); 197 } 198 }); 199 } 200 createSpinnerUIDelegate(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)201 private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs, 202 int defStyleAttr, int defStyleRes) { 203 return new DatePickerSpinnerDelegate(this, context, attrs, defStyleAttr, defStyleRes); 204 } 205 createCalendarUIDelegate(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)206 private DatePickerDelegate createCalendarUIDelegate(Context context, AttributeSet attrs, 207 int defStyleAttr, int defStyleRes) { 208 return new DatePickerCalendarDelegate(this, context, attrs, defStyleAttr, 209 defStyleRes); 210 } 211 212 /** 213 * @return the picker's presentation mode, one of {@link #MODE_CALENDAR} or 214 * {@link #MODE_SPINNER} 215 * @attr ref android.R.styleable#DatePicker_datePickerMode 216 * @hide Visible for testing only. 217 */ 218 @InspectableProperty(name = "datePickerMode", enumMapping = { 219 @InspectableProperty.EnumEntry(value = MODE_SPINNER, name = "spinner"), 220 @InspectableProperty.EnumEntry(value = MODE_CALENDAR, name = "calendar") 221 }) 222 @DatePickerMode 223 @TestApi getMode()224 public int getMode() { 225 return mMode; 226 } 227 228 /** 229 * Initialize the state. If the provided values designate an inconsistent 230 * date the values are normalized before updating the spinners. 231 * 232 * @param year The initial year. 233 * @param monthOfYear The initial month <strong>starting from zero</strong>. 234 * @param dayOfMonth The initial day of the month. 235 * @param onDateChangedListener How user is notified date is changed by 236 * user, can be null. 237 */ init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener)238 public void init(int year, int monthOfYear, int dayOfMonth, 239 OnDateChangedListener onDateChangedListener) { 240 mDelegate.init(year, monthOfYear, dayOfMonth, onDateChangedListener); 241 } 242 243 /** 244 * Set the callback that indicates the date has been adjusted by the user. 245 * 246 * @param onDateChangedListener How user is notified date is changed by 247 * user, can be null. 248 */ setOnDateChangedListener(OnDateChangedListener onDateChangedListener)249 public void setOnDateChangedListener(OnDateChangedListener onDateChangedListener) { 250 mDelegate.setOnDateChangedListener(onDateChangedListener); 251 } 252 253 /** 254 * Update the current date. 255 * 256 * @param year The year. 257 * @param month The month which is <strong>starting from zero</strong>. 258 * @param dayOfMonth The day of the month. 259 */ updateDate(int year, int month, int dayOfMonth)260 public void updateDate(int year, int month, int dayOfMonth) { 261 mDelegate.updateDate(year, month, dayOfMonth); 262 } 263 264 /** 265 * @return The selected year. 266 */ 267 @InspectableProperty(hasAttributeId = false) getYear()268 public int getYear() { 269 return mDelegate.getYear(); 270 } 271 272 /** 273 * @return The selected month. 274 */ 275 @InspectableProperty(hasAttributeId = false) getMonth()276 public int getMonth() { 277 return mDelegate.getMonth(); 278 } 279 280 /** 281 * @return The selected day of month. 282 */ 283 @InspectableProperty(hasAttributeId = false) getDayOfMonth()284 public int getDayOfMonth() { 285 return mDelegate.getDayOfMonth(); 286 } 287 288 /** 289 * Gets the minimal date supported by this {@link DatePicker} in 290 * milliseconds since January 1, 1970 00:00:00 in 291 * {@link TimeZone#getDefault()} time zone. 292 * <p> 293 * Note: The default minimal date is 01/01/1900. 294 * <p> 295 * 296 * @return The minimal supported date. 297 */ 298 @InspectableProperty getMinDate()299 public long getMinDate() { 300 return mDelegate.getMinDate().getTimeInMillis(); 301 } 302 303 /** 304 * Sets the minimal date supported by this {@link NumberPicker} in 305 * milliseconds since January 1, 1970 00:00:00 in 306 * {@link TimeZone#getDefault()} time zone. 307 * 308 * @param minDate The minimal supported date. 309 */ setMinDate(long minDate)310 public void setMinDate(long minDate) { 311 mDelegate.setMinDate(minDate); 312 } 313 314 /** 315 * Gets the maximal date supported by this {@link DatePicker} in 316 * milliseconds since January 1, 1970 00:00:00 in 317 * {@link TimeZone#getDefault()} time zone. 318 * <p> 319 * Note: The default maximal date is 12/31/2100. 320 * <p> 321 * 322 * @return The maximal supported date. 323 */ 324 @InspectableProperty getMaxDate()325 public long getMaxDate() { 326 return mDelegate.getMaxDate().getTimeInMillis(); 327 } 328 329 /** 330 * Sets the maximal date supported by this {@link DatePicker} in 331 * milliseconds since January 1, 1970 00:00:00 in 332 * {@link TimeZone#getDefault()} time zone. 333 * 334 * @param maxDate The maximal supported date. 335 */ setMaxDate(long maxDate)336 public void setMaxDate(long maxDate) { 337 mDelegate.setMaxDate(maxDate); 338 } 339 340 /** 341 * Sets the callback that indicates the current date is valid. 342 * 343 * @param callback the callback, may be null 344 * @hide 345 */ 346 @UnsupportedAppUsage setValidationCallback(@ullable ValidationCallback callback)347 public void setValidationCallback(@Nullable ValidationCallback callback) { 348 mDelegate.setValidationCallback(callback); 349 } 350 351 @Override setEnabled(boolean enabled)352 public void setEnabled(boolean enabled) { 353 if (mDelegate.isEnabled() == enabled) { 354 return; 355 } 356 super.setEnabled(enabled); 357 mDelegate.setEnabled(enabled); 358 } 359 360 @Override isEnabled()361 public boolean isEnabled() { 362 return mDelegate.isEnabled(); 363 } 364 365 /** @hide */ 366 @Override dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)367 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { 368 return mDelegate.dispatchPopulateAccessibilityEvent(event); 369 } 370 371 /** @hide */ 372 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)373 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 374 super.onPopulateAccessibilityEventInternal(event); 375 mDelegate.onPopulateAccessibilityEvent(event); 376 } 377 378 @Override getAccessibilityClassName()379 public CharSequence getAccessibilityClassName() { 380 return DatePicker.class.getName(); 381 } 382 383 @Override onConfigurationChanged(Configuration newConfig)384 protected void onConfigurationChanged(Configuration newConfig) { 385 super.onConfigurationChanged(newConfig); 386 mDelegate.onConfigurationChanged(newConfig); 387 } 388 389 /** 390 * Sets the first day of week. 391 * 392 * @param firstDayOfWeek The first day of the week conforming to the 393 * {@link CalendarView} APIs. 394 * @see Calendar#SUNDAY 395 * @see Calendar#MONDAY 396 * @see Calendar#TUESDAY 397 * @see Calendar#WEDNESDAY 398 * @see Calendar#THURSDAY 399 * @see Calendar#FRIDAY 400 * @see Calendar#SATURDAY 401 * 402 * @attr ref android.R.styleable#DatePicker_firstDayOfWeek 403 */ setFirstDayOfWeek(int firstDayOfWeek)404 public void setFirstDayOfWeek(int firstDayOfWeek) { 405 if (firstDayOfWeek < Calendar.SUNDAY || firstDayOfWeek > Calendar.SATURDAY) { 406 throw new IllegalArgumentException("firstDayOfWeek must be between 1 and 7"); 407 } 408 mDelegate.setFirstDayOfWeek(firstDayOfWeek); 409 } 410 411 /** 412 * Gets the first day of week. 413 * 414 * @return The first day of the week conforming to the {@link CalendarView} 415 * APIs. 416 * @see Calendar#SUNDAY 417 * @see Calendar#MONDAY 418 * @see Calendar#TUESDAY 419 * @see Calendar#WEDNESDAY 420 * @see Calendar#THURSDAY 421 * @see Calendar#FRIDAY 422 * @see Calendar#SATURDAY 423 * 424 * @attr ref android.R.styleable#DatePicker_firstDayOfWeek 425 */ 426 @InspectableProperty getFirstDayOfWeek()427 public int getFirstDayOfWeek() { 428 return mDelegate.getFirstDayOfWeek(); 429 } 430 431 /** 432 * Returns whether the {@link CalendarView} is shown. 433 * <p> 434 * <strong>Note:</strong> This method returns {@code false} when the 435 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set 436 * to {@code calendar}. 437 * 438 * @return {@code true} if the calendar view is shown 439 * @see #getCalendarView() 440 * @deprecated Not supported by Material-style {@code calendar} mode 441 */ 442 @InspectableProperty 443 @Deprecated getCalendarViewShown()444 public boolean getCalendarViewShown() { 445 return mDelegate.getCalendarViewShown(); 446 } 447 448 /** 449 * Returns the {@link CalendarView} used by this picker. 450 * <p> 451 * <strong>Note:</strong> This method throws an 452 * {@link UnsupportedOperationException} when the 453 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set 454 * to {@code calendar}. 455 * 456 * @return the calendar view 457 * @see #getCalendarViewShown() 458 * @deprecated Not supported by Material-style {@code calendar} mode 459 * @throws UnsupportedOperationException if called when the picker is 460 * displayed in {@code calendar} mode 461 */ 462 @Deprecated getCalendarView()463 public CalendarView getCalendarView() { 464 return mDelegate.getCalendarView(); 465 } 466 467 /** 468 * Sets whether the {@link CalendarView} is shown. 469 * <p> 470 * <strong>Note:</strong> Calling this method has no effect when the 471 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set 472 * to {@code calendar}. 473 * 474 * @param shown {@code true} to show the calendar view, {@code false} to 475 * hide it 476 * @deprecated Not supported by Material-style {@code calendar} mode 477 */ 478 @Deprecated setCalendarViewShown(boolean shown)479 public void setCalendarViewShown(boolean shown) { 480 mDelegate.setCalendarViewShown(shown); 481 } 482 483 /** 484 * Returns whether the spinners are shown. 485 * <p> 486 * <strong>Note:</strong> his method returns {@code false} when the 487 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set 488 * to {@code calendar}. 489 * 490 * @return {@code true} if the spinners are shown 491 * @deprecated Not supported by Material-style {@code calendar} mode 492 */ 493 @InspectableProperty 494 @Deprecated getSpinnersShown()495 public boolean getSpinnersShown() { 496 return mDelegate.getSpinnersShown(); 497 } 498 499 /** 500 * Sets whether the spinners are shown. 501 * <p> 502 * Calling this method has no effect when the 503 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set 504 * to {@code calendar}. 505 * 506 * @param shown {@code true} to show the spinners, {@code false} to hide 507 * them 508 * @deprecated Not supported by Material-style {@code calendar} mode 509 */ 510 @Deprecated setSpinnersShown(boolean shown)511 public void setSpinnersShown(boolean shown) { 512 mDelegate.setSpinnersShown(shown); 513 } 514 515 @Override dispatchRestoreInstanceState(SparseArray<Parcelable> container)516 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 517 dispatchThawSelfOnly(container); 518 } 519 520 @Override onSaveInstanceState()521 protected Parcelable onSaveInstanceState() { 522 Parcelable superState = super.onSaveInstanceState(); 523 return mDelegate.onSaveInstanceState(superState); 524 } 525 526 @Override onRestoreInstanceState(Parcelable state)527 protected void onRestoreInstanceState(Parcelable state) { 528 BaseSavedState ss = (BaseSavedState) state; 529 super.onRestoreInstanceState(ss.getSuperState()); 530 mDelegate.onRestoreInstanceState(ss); 531 } 532 533 /** 534 * A delegate interface that defined the public API of the DatePicker. Allows different 535 * DatePicker implementations. This would need to be implemented by the DatePicker delegates 536 * for the real behavior. 537 * 538 * @hide 539 */ 540 interface DatePickerDelegate { init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener)541 void init(int year, int monthOfYear, int dayOfMonth, 542 OnDateChangedListener onDateChangedListener); 543 setOnDateChangedListener(OnDateChangedListener onDateChangedListener)544 void setOnDateChangedListener(OnDateChangedListener onDateChangedListener); setAutoFillChangeListener(OnDateChangedListener onDateChangedListener)545 void setAutoFillChangeListener(OnDateChangedListener onDateChangedListener); 546 updateDate(int year, int month, int dayOfMonth)547 void updateDate(int year, int month, int dayOfMonth); 548 getYear()549 int getYear(); getMonth()550 int getMonth(); getDayOfMonth()551 int getDayOfMonth(); 552 autofill(AutofillValue value)553 void autofill(AutofillValue value); getAutofillValue()554 AutofillValue getAutofillValue(); 555 setFirstDayOfWeek(int firstDayOfWeek)556 void setFirstDayOfWeek(int firstDayOfWeek); getFirstDayOfWeek()557 int getFirstDayOfWeek(); 558 setMinDate(long minDate)559 void setMinDate(long minDate); getMinDate()560 Calendar getMinDate(); 561 setMaxDate(long maxDate)562 void setMaxDate(long maxDate); getMaxDate()563 Calendar getMaxDate(); 564 setEnabled(boolean enabled)565 void setEnabled(boolean enabled); isEnabled()566 boolean isEnabled(); 567 getCalendarView()568 CalendarView getCalendarView(); 569 setCalendarViewShown(boolean shown)570 void setCalendarViewShown(boolean shown); getCalendarViewShown()571 boolean getCalendarViewShown(); 572 setSpinnersShown(boolean shown)573 void setSpinnersShown(boolean shown); getSpinnersShown()574 boolean getSpinnersShown(); 575 setValidationCallback(ValidationCallback callback)576 void setValidationCallback(ValidationCallback callback); 577 onConfigurationChanged(Configuration newConfig)578 void onConfigurationChanged(Configuration newConfig); 579 onSaveInstanceState(Parcelable superState)580 Parcelable onSaveInstanceState(Parcelable superState); onRestoreInstanceState(Parcelable state)581 void onRestoreInstanceState(Parcelable state); 582 dispatchPopulateAccessibilityEvent(AccessibilityEvent event)583 boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event); onPopulateAccessibilityEvent(AccessibilityEvent event)584 void onPopulateAccessibilityEvent(AccessibilityEvent event); 585 } 586 587 /** 588 * An abstract class which can be used as a start for DatePicker implementations 589 */ 590 abstract static class AbstractDatePickerDelegate implements DatePickerDelegate { 591 // The delegator 592 protected DatePicker mDelegator; 593 594 // The context 595 protected Context mContext; 596 597 // NOTE: when subclasses change this variable, they must call resetAutofilledValue(). 598 protected Calendar mCurrentDate; 599 600 // The current locale 601 protected Locale mCurrentLocale; 602 603 // Callbacks 604 protected OnDateChangedListener mOnDateChangedListener; 605 protected OnDateChangedListener mAutoFillChangeListener; 606 protected ValidationCallback mValidationCallback; 607 608 // The value that was passed to autofill() - it must be stored because it getAutofillValue() 609 // must return the exact same value that was autofilled, otherwise the widget will not be 610 // properly highlighted after autofill(). 611 private long mAutofilledValue; 612 AbstractDatePickerDelegate(DatePicker delegator, Context context)613 public AbstractDatePickerDelegate(DatePicker delegator, Context context) { 614 mDelegator = delegator; 615 mContext = context; 616 617 setCurrentLocale(Locale.getDefault()); 618 } 619 setCurrentLocale(Locale locale)620 protected void setCurrentLocale(Locale locale) { 621 if (!locale.equals(mCurrentLocale)) { 622 mCurrentLocale = locale; 623 onLocaleChanged(locale); 624 } 625 } 626 627 @Override setOnDateChangedListener(OnDateChangedListener callback)628 public void setOnDateChangedListener(OnDateChangedListener callback) { 629 mOnDateChangedListener = callback; 630 } 631 632 @Override setAutoFillChangeListener(OnDateChangedListener callback)633 public void setAutoFillChangeListener(OnDateChangedListener callback) { 634 mAutoFillChangeListener = callback; 635 } 636 637 @Override setValidationCallback(ValidationCallback callback)638 public void setValidationCallback(ValidationCallback callback) { 639 mValidationCallback = callback; 640 } 641 642 @Override autofill(AutofillValue value)643 public final void autofill(AutofillValue value) { 644 if (value == null || !value.isDate()) { 645 Log.w(LOG_TAG, value + " could not be autofilled into " + this); 646 return; 647 } 648 649 final long time = value.getDateValue(); 650 651 final Calendar cal = Calendar.getInstance(mCurrentLocale); 652 cal.setTimeInMillis(time); 653 updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 654 cal.get(Calendar.DAY_OF_MONTH)); 655 656 // Must set mAutofilledValue *after* calling subclass method to make sure the value 657 // returned by getAutofillValue() matches it. 658 mAutofilledValue = time; 659 } 660 661 @Override getAutofillValue()662 public final AutofillValue getAutofillValue() { 663 final long time = mAutofilledValue != 0 664 ? mAutofilledValue 665 : mCurrentDate.getTimeInMillis(); 666 return AutofillValue.forDate(time); 667 } 668 669 /** 670 * This method must be called every time the value of the year, month, and/or day is 671 * changed by a subclass method. 672 */ resetAutofilledValue()673 protected void resetAutofilledValue() { 674 mAutofilledValue = 0; 675 } 676 onValidationChanged(boolean valid)677 protected void onValidationChanged(boolean valid) { 678 if (mValidationCallback != null) { 679 mValidationCallback.onValidationChanged(valid); 680 } 681 } 682 onLocaleChanged(Locale locale)683 protected void onLocaleChanged(Locale locale) { 684 // Stub. 685 } 686 687 @Override onPopulateAccessibilityEvent(AccessibilityEvent event)688 public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 689 event.getText().add(getFormattedCurrentDate()); 690 } 691 getFormattedCurrentDate()692 protected String getFormattedCurrentDate() { 693 return DateUtils.formatDateTime(mContext, mCurrentDate.getTimeInMillis(), 694 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR 695 | DateUtils.FORMAT_SHOW_WEEKDAY); 696 } 697 698 /** 699 * Class for managing state storing/restoring. 700 */ 701 static class SavedState extends View.BaseSavedState { 702 private final int mSelectedYear; 703 private final int mSelectedMonth; 704 private final int mSelectedDay; 705 private final long mMinDate; 706 private final long mMaxDate; 707 private final int mCurrentView; 708 private final int mListPosition; 709 private final int mListPositionOffset; 710 SavedState(Parcelable superState, int year, int month, int day, long minDate, long maxDate)711 public SavedState(Parcelable superState, int year, int month, int day, long minDate, 712 long maxDate) { 713 this(superState, year, month, day, minDate, maxDate, 0, 0, 0); 714 } 715 716 /** 717 * Constructor called from {@link DatePicker#onSaveInstanceState()} 718 */ SavedState(Parcelable superState, int year, int month, int day, long minDate, long maxDate, int currentView, int listPosition, int listPositionOffset)719 public SavedState(Parcelable superState, int year, int month, int day, long minDate, 720 long maxDate, int currentView, int listPosition, int listPositionOffset) { 721 super(superState); 722 mSelectedYear = year; 723 mSelectedMonth = month; 724 mSelectedDay = day; 725 mMinDate = minDate; 726 mMaxDate = maxDate; 727 mCurrentView = currentView; 728 mListPosition = listPosition; 729 mListPositionOffset = listPositionOffset; 730 } 731 732 /** 733 * Constructor called from {@link #CREATOR} 734 */ SavedState(Parcel in)735 private SavedState(Parcel in) { 736 super(in); 737 mSelectedYear = in.readInt(); 738 mSelectedMonth = in.readInt(); 739 mSelectedDay = in.readInt(); 740 mMinDate = in.readLong(); 741 mMaxDate = in.readLong(); 742 mCurrentView = in.readInt(); 743 mListPosition = in.readInt(); 744 mListPositionOffset = in.readInt(); 745 } 746 747 @Override writeToParcel(Parcel dest, int flags)748 public void writeToParcel(Parcel dest, int flags) { 749 super.writeToParcel(dest, flags); 750 dest.writeInt(mSelectedYear); 751 dest.writeInt(mSelectedMonth); 752 dest.writeInt(mSelectedDay); 753 dest.writeLong(mMinDate); 754 dest.writeLong(mMaxDate); 755 dest.writeInt(mCurrentView); 756 dest.writeInt(mListPosition); 757 dest.writeInt(mListPositionOffset); 758 } 759 getSelectedDay()760 public int getSelectedDay() { 761 return mSelectedDay; 762 } 763 getSelectedMonth()764 public int getSelectedMonth() { 765 return mSelectedMonth; 766 } 767 getSelectedYear()768 public int getSelectedYear() { 769 return mSelectedYear; 770 } 771 getMinDate()772 public long getMinDate() { 773 return mMinDate; 774 } 775 getMaxDate()776 public long getMaxDate() { 777 return mMaxDate; 778 } 779 getCurrentView()780 public int getCurrentView() { 781 return mCurrentView; 782 } 783 getListPosition()784 public int getListPosition() { 785 return mListPosition; 786 } 787 getListPositionOffset()788 public int getListPositionOffset() { 789 return mListPositionOffset; 790 } 791 792 @SuppressWarnings("all") 793 // suppress unused and hiding 794 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { 795 796 public SavedState createFromParcel(Parcel in) { 797 return new SavedState(in); 798 } 799 800 public SavedState[] newArray(int size) { 801 return new SavedState[size]; 802 } 803 }; 804 } 805 } 806 807 /** 808 * A callback interface for updating input validity when the date picker 809 * when included into a dialog. 810 * 811 * @hide 812 */ 813 public interface ValidationCallback { onValidationChanged(boolean valid)814 void onValidationChanged(boolean valid); 815 } 816 817 @Override dispatchProvideAutofillStructure(ViewStructure structure, int flags)818 public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) { 819 // This view is self-sufficient for autofill, so it needs to call 820 // onProvideAutoFillStructure() to fill itself, but it does not need to call 821 // dispatchProvideAutoFillStructure() to fill its children. 822 structure.setAutofillId(getAutofillId()); 823 onProvideAutofillStructure(structure, flags); 824 } 825 826 @Override autofill(AutofillValue value)827 public void autofill(AutofillValue value) { 828 if (!isEnabled()) return; 829 830 mDelegate.autofill(value); 831 } 832 833 @Override getAutofillType()834 public @AutofillType int getAutofillType() { 835 return isEnabled() ? AUTOFILL_TYPE_DATE : AUTOFILL_TYPE_NONE; 836 } 837 838 @Override getAutofillValue()839 public AutofillValue getAutofillValue() { 840 return isEnabled() ? mDelegate.getAutofillValue() : null; 841 } 842 } 843