60 | * See the Pickers 61 | * guide. 62 | *
63 | *64 | * For a dialog using this view, see {@link android.app.DatePickerDialog}. 65 | *
66 | * 67 | * @attr ref android.R.styleable#DatePicker_startYear 68 | * @attr ref android.R.styleable#DatePicker_endYear 69 | * @attr ref android.R.styleable#DatePicker_maxDate 70 | * @attr ref android.R.styleable#DatePicker_minDate 71 | * @attr ref android.R.styleable#DatePicker_spinnersShown 72 | * @attr ref android.R.styleable#DatePicker_calendarViewShown 73 | */ 74 | //@Widget 75 | public class DatePicker extends FrameLayout { 76 | 77 | private static final String LOG_TAG = DatePicker.class.getSimpleName(); 78 | 79 | private static final String DATE_FORMAT = "MM/dd/yyyy"; 80 | 81 | private static final int DEFAULT_START_YEAR = 1900; 82 | 83 | private static final int DEFAULT_END_YEAR = 2100; 84 | 85 | private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true; 86 | 87 | private static final boolean DEFAULT_SPINNERS_SHOWN = true; 88 | 89 | private static final boolean DEFAULT_ENABLED_STATE = true; 90 | 91 | private final LinearLayout mSpinners; 92 | 93 | private final NumberPicker mDaySpinner; 94 | 95 | private final NumberPicker mMonthSpinner; 96 | 97 | private final NumberPicker mYearSpinner; 98 | 99 | private final EditText mDaySpinnerInput; 100 | 101 | private final EditText mMonthSpinnerInput; 102 | 103 | private final EditText mYearSpinnerInput; 104 | 105 | private final CalendarView mCalendarView; 106 | 107 | private Locale mCurrentLocale; 108 | 109 | private OnDateChangedListener mOnDateChangedListener; 110 | 111 | private String[] mShortMonths; 112 | 113 | private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); 114 | 115 | private int mNumberOfMonths; 116 | 117 | private Calendar mTempDate; 118 | 119 | private Calendar mMinDate; 120 | 121 | private Calendar mMaxDate; 122 | 123 | private Calendar mCurrentDate; 124 | 125 | private boolean mIsEnabled = DEFAULT_ENABLED_STATE; 126 | 127 | /** 128 | * The callback used to indicate the user changes\d the date. 129 | */ 130 | public interface OnDateChangedListener { 131 | 132 | /** 133 | * Called upon a date change. 134 | * 135 | * @param view The view associated with this listener. 136 | * @param year The year that was set. 137 | * @param monthOfYear The month that was set (0-11) for compatibility 138 | * with {@link java.util.Calendar}. 139 | * @param dayOfMonth The day of the month that was set. 140 | */ 141 | void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth); 142 | } 143 | 144 | public DatePicker(Context context) { 145 | this(context, null); 146 | } 147 | 148 | public DatePicker(Context context, AttributeSet attrs) { 149 | this(context, attrs, R.attr.datePickerStyle); 150 | } 151 | 152 | public DatePicker(Context context, AttributeSet attrs, int defStyle) { 153 | super(context, attrs, defStyle); 154 | 155 | // initialization based on locale 156 | setCurrentLocale(Locale.getDefault()); 157 | 158 | TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.DatePicker, 159 | defStyle, 0); 160 | boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_dp_spinnersShown, 161 | DEFAULT_SPINNERS_SHOWN); 162 | boolean calendarViewShown = attributesArray.getBoolean( 163 | R.styleable.DatePicker_dp_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN); 164 | int startYear = attributesArray.getInt(R.styleable.DatePicker_dp_startYear, 165 | DEFAULT_START_YEAR); 166 | int endYear = attributesArray.getInt(R.styleable.DatePicker_dp_endYear, DEFAULT_END_YEAR); 167 | String minDate = attributesArray.getString(R.styleable.DatePicker_dp_minDate); 168 | String maxDate = attributesArray.getString(R.styleable.DatePicker_dp_maxDate); 169 | int layoutResourceId = attributesArray.getResourceId(R.styleable.DatePicker_dp_internalLayout, 170 | R.layout.date_picker_holo); 171 | attributesArray.recycle(); 172 | 173 | LayoutInflater inflater = (LayoutInflater) context 174 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 175 | inflater.inflate(layoutResourceId, this, true); 176 | 177 | NumberPicker.OnValueChangeListener onChangeListener = new NumberPicker.OnValueChangeListener() { 178 | public void onValueChange(NumberPicker picker, int oldVal, int newVal) { 179 | updateInputState(); 180 | mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis()); 181 | // take care of wrapping of days and months to update greater fields 182 | if (picker == mDaySpinner) { 183 | int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH); 184 | if (oldVal == maxDayOfMonth && newVal == 1) { 185 | mTempDate.add(Calendar.DAY_OF_MONTH, 1); 186 | } else if (oldVal == 1 && newVal == maxDayOfMonth) { 187 | mTempDate.add(Calendar.DAY_OF_MONTH, -1); 188 | } else { 189 | mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal); 190 | } 191 | } else if (picker == mMonthSpinner) { 192 | if (oldVal == 11 && newVal == 0) { 193 | mTempDate.add(Calendar.MONTH, 1); 194 | } else if (oldVal == 0 && newVal == 11) { 195 | mTempDate.add(Calendar.MONTH, -1); 196 | } else { 197 | mTempDate.add(Calendar.MONTH, newVal - oldVal); 198 | } 199 | } else if (picker == mYearSpinner) { 200 | mTempDate.set(Calendar.YEAR, newVal); 201 | } else { 202 | throw new IllegalArgumentException(); 203 | } 204 | // now set the date to the adjusted one 205 | setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH), 206 | mTempDate.get(Calendar.DAY_OF_MONTH)); 207 | updateSpinners(); 208 | updateCalendarView(); 209 | notifyDateChanged(); 210 | } 211 | }; 212 | 213 | mSpinners = (LinearLayout) findViewById(R.id.pickers); 214 | 215 | // calendar view day-picker 216 | mCalendarView = (CalendarView) findViewById(R.id.calendar_view); 217 | mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() { 218 | public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) { 219 | setDate(year, month, monthDay); 220 | updateSpinners(); 221 | notifyDateChanged(); 222 | } 223 | }); 224 | 225 | // day 226 | mDaySpinner = (NumberPicker) findViewById(R.id.day); 227 | mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); 228 | mDaySpinner.setOnLongPressUpdateInterval(100); 229 | mDaySpinner.setOnValueChangedListener(onChangeListener); 230 | mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.np__numberpicker_input); 231 | 232 | // month 233 | mMonthSpinner = (NumberPicker) findViewById(R.id.month); 234 | mMonthSpinner.setMinValue(0); 235 | mMonthSpinner.setMaxValue(mNumberOfMonths - 1); 236 | mMonthSpinner.setDisplayedValues(mShortMonths); 237 | mMonthSpinner.setOnLongPressUpdateInterval(200); 238 | mMonthSpinner.setOnValueChangedListener(onChangeListener); 239 | mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.np__numberpicker_input); 240 | 241 | // year 242 | mYearSpinner = (NumberPicker) findViewById(R.id.year); 243 | mYearSpinner.setOnLongPressUpdateInterval(100); 244 | mYearSpinner.setOnValueChangedListener(onChangeListener); 245 | mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.np__numberpicker_input); 246 | 247 | // show only what the user required but make sure we 248 | // show something and the spinners have higher priority 249 | if (!spinnersShown && !calendarViewShown) { 250 | setSpinnersShown(true); 251 | } else { 252 | setSpinnersShown(spinnersShown); 253 | setCalendarViewShown(calendarViewShown); 254 | } 255 | 256 | // set the min date giving priority of the minDate over startYear 257 | mTempDate.clear(); 258 | if (!TextUtils.isEmpty(minDate)) { 259 | if (!parseDate(minDate, mTempDate)) { 260 | mTempDate.set(startYear, 0, 1); 261 | } 262 | } else { 263 | mTempDate.set(startYear, 0, 1); 264 | } 265 | setMinDate(mTempDate.getTimeInMillis()); 266 | 267 | // set the max date giving priority of the maxDate over endYear 268 | mTempDate.clear(); 269 | if (!TextUtils.isEmpty(maxDate)) { 270 | if (!parseDate(maxDate, mTempDate)) { 271 | mTempDate.set(endYear, 11, 31); 272 | } 273 | } else { 274 | mTempDate.set(endYear, 11, 31); 275 | } 276 | setMaxDate(mTempDate.getTimeInMillis()); 277 | 278 | // initialize to current date 279 | mCurrentDate.setTimeInMillis(System.currentTimeMillis()); 280 | init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate 281 | .get(Calendar.DAY_OF_MONTH), null); 282 | 283 | // re-order the number spinners to match the current date format 284 | reorderSpinners(); 285 | 286 | // accessibility 287 | setContentDescriptions(); 288 | 289 | // If not explicitly specified this view is important for accessibility. 290 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 291 | setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 292 | } 293 | } 294 | 295 | /** 296 | * Gets the minimal date supported by this {@link DatePicker} in 297 | * milliseconds since January 1, 1970 00:00:00 in 298 | * {@link TimeZone#getDefault()} time zone. 299 | *300 | * Note: The default minimal date is 01/01/1900. 301 | *
302 | * 303 | * @return The minimal supported date. 304 | */ 305 | public long getMinDate() { 306 | return mCalendarView.getMinDate(); 307 | } 308 | 309 | /** 310 | * Sets the minimal date supported by this {@link NumberPicker} in 311 | * milliseconds since January 1, 1970 00:00:00 in 312 | * {@link TimeZone#getDefault()} time zone. 313 | * 314 | * @param minDate The minimal supported date. 315 | */ 316 | public void setMinDate(long minDate) { 317 | mTempDate.setTimeInMillis(minDate); 318 | if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) 319 | && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { 320 | return; 321 | } 322 | mMinDate.setTimeInMillis(minDate); 323 | mCalendarView.setMinDate(minDate); 324 | if (mCurrentDate.before(mMinDate)) { 325 | mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); 326 | updateCalendarView(); 327 | } 328 | updateSpinners(); 329 | } 330 | 331 | /** 332 | * Gets the maximal date supported by this {@link DatePicker} in 333 | * milliseconds since January 1, 1970 00:00:00 in 334 | * {@link TimeZone#getDefault()} time zone. 335 | *
336 | * Note: The default maximal date is 12/31/2100. 337 | *
338 | *
339 | * @return The maximal supported date.
340 | */
341 | public long getMaxDate() {
342 | return mCalendarView.getMaxDate();
343 | }
344 |
345 | /**
346 | * Sets the maximal date supported by this {@link DatePicker} in
347 | * milliseconds since January 1, 1970 00:00:00 in
348 | * {@link TimeZone#getDefault()} time zone.
349 | *
350 | * @param maxDate The maximal supported date.
351 | */
352 | public void setMaxDate(long maxDate) {
353 | mTempDate.setTimeInMillis(maxDate);
354 | if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
355 | && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
356 | return;
357 | }
358 | mMaxDate.setTimeInMillis(maxDate);
359 | mCalendarView.setMaxDate(maxDate);
360 | if (mCurrentDate.after(mMaxDate)) {
361 | mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
362 | updateCalendarView();
363 | }
364 | updateSpinners();
365 | }
366 |
367 | @Override
368 | public void setEnabled(boolean enabled) {
369 | if (mIsEnabled == enabled) {
370 | return;
371 | }
372 | super.setEnabled(enabled);
373 | mDaySpinner.setEnabled(enabled);
374 | mMonthSpinner.setEnabled(enabled);
375 | mYearSpinner.setEnabled(enabled);
376 | mCalendarView.setEnabled(enabled);
377 | mIsEnabled = enabled;
378 | }
379 |
380 | @Override
381 | public boolean isEnabled() {
382 | return mIsEnabled;
383 | }
384 |
385 | @Override
386 | public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
387 | onPopulateAccessibilityEvent(event);
388 | return true;
389 | }
390 |
391 | @Override
392 | public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
393 | super.onPopulateAccessibilityEvent(event);
394 |
395 | final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
396 | String selectedDateUtterance = DateUtils.formatDateTime(getContext(),
397 | mCurrentDate.getTimeInMillis(), flags);
398 | event.getText().add(selectedDateUtterance);
399 | }
400 |
401 | @Override
402 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
403 | super.onInitializeAccessibilityEvent(event);
404 | event.setClassName(DatePicker.class.getName());
405 | }
406 |
407 | @Override
408 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
409 | super.onInitializeAccessibilityNodeInfo(info);
410 | info.setClassName(DatePicker.class.getName());
411 | }
412 |
413 | @Override
414 | protected void onConfigurationChanged(Configuration newConfig) {
415 | super.onConfigurationChanged(newConfig);
416 | setCurrentLocale(newConfig.locale);
417 | }
418 |
419 | /**
420 | * Gets whether the {@link CalendarView} is shown.
421 | *
422 | * @return True if the calendar view is shown.
423 | * @see #getCalendarView()
424 | */
425 | public boolean getCalendarViewShown() {
426 | return mCalendarView.isShown();
427 | }
428 |
429 | /**
430 | * Gets the {@link CalendarView}.
431 | *
432 | * @return The calendar view.
433 | * @see #getCalendarViewShown()
434 | */
435 | public CalendarView getCalendarView () {
436 | return mCalendarView;
437 | }
438 |
439 | /**
440 | * Sets whether the {@link CalendarView} is shown.
441 | *
442 | * @param shown True if the calendar view is to be shown.
443 | */
444 | public void setCalendarViewShown(boolean shown) {
445 | mCalendarView.setVisibility(shown ? VISIBLE : GONE);
446 | }
447 |
448 | /**
449 | * Gets whether the spinners are shown.
450 | *
451 | * @return True if the spinners are shown.
452 | */
453 | public boolean getSpinnersShown() {
454 | return mSpinners.isShown();
455 | }
456 |
457 | /**
458 | * Sets whether the spinners are shown.
459 | *
460 | * @param shown True if the spinners are to be shown.
461 | */
462 | public void setSpinnersShown(boolean shown) {
463 | mSpinners.setVisibility(shown ? VISIBLE : GONE);
464 | }
465 |
466 | /**
467 | * Sets the current locale.
468 | *
469 | * @param locale The current locale.
470 | */
471 | private void setCurrentLocale(Locale locale) {
472 | if (locale.equals(mCurrentLocale)) {
473 | return;
474 | }
475 |
476 | mCurrentLocale = locale;
477 |
478 | mTempDate = getCalendarForLocale(mTempDate, locale);
479 | mMinDate = getCalendarForLocale(mMinDate, locale);
480 | mMaxDate = getCalendarForLocale(mMaxDate, locale);
481 | mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
482 |
483 | mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
484 | mShortMonths = new String[mNumberOfMonths];
485 | for (int i = 0; i < mNumberOfMonths; i++) {
486 | mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
487 | DateUtils.LENGTH_MEDIUM);
488 | }
489 | }
490 |
491 | /**
492 | * Gets a calendar for locale bootstrapped with the value of a given calendar.
493 | *
494 | * @param oldCalendar The old calendar.
495 | * @param locale The locale.
496 | */
497 | private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
498 | if (oldCalendar == null) {
499 | return Calendar.getInstance(locale);
500 | } else {
501 | final long currentTimeMillis = oldCalendar.getTimeInMillis();
502 | Calendar newCalendar = Calendar.getInstance(locale);
503 | newCalendar.setTimeInMillis(currentTimeMillis);
504 | return newCalendar;
505 | }
506 | }
507 |
508 | /**
509 | * Reorders the spinners according to the date format that is
510 | * explicitly set by the user and if no such is set fall back
511 | * to the current locale's default format.
512 | */
513 | private void reorderSpinners() {
514 | mSpinners.removeAllViews();
515 | char[] order = DateFormat.getDateFormatOrder(getContext());
516 | final int spinnerCount = order.length;
517 | for (int i = 0; i < spinnerCount; i++) {
518 | switch (order[i]) {
519 | case DateFormat.DATE:
520 | mSpinners.addView(mDaySpinner);
521 | setImeOptions(mDaySpinner, spinnerCount, i);
522 | break;
523 | case DateFormat.MONTH:
524 | mSpinners.addView(mMonthSpinner);
525 | setImeOptions(mMonthSpinner, spinnerCount, i);
526 | break;
527 | case DateFormat.YEAR:
528 | mSpinners.addView(mYearSpinner);
529 | setImeOptions(mYearSpinner, spinnerCount, i);
530 | break;
531 | default:
532 | throw new IllegalArgumentException();
533 | }
534 | }
535 | }
536 |
537 | /**
538 | * Updates the current date.
539 | *
540 | * @param year The year.
541 | * @param month The month which is starting from zero.
542 | * @param dayOfMonth The day of the month.
543 | */
544 | public void updateDate(int year, int month, int dayOfMonth) {
545 | if (!isNewDate(year, month, dayOfMonth)) {
546 | return;
547 | }
548 | setDate(year, month, dayOfMonth);
549 | updateSpinners();
550 | updateCalendarView();
551 | notifyDateChanged();
552 | }
553 |
554 | // Override so we are in complete control of save / restore for this widget.
555 | @Override
556 | protected void dispatchRestoreInstanceState(SparseArray See the Pickers
35 | * guide.date and in case of success sets the result
595 | * to the outDate.
596 | *
597 | * @return True if the date was parsed.
598 | */
599 | private boolean parseDate(String date, Calendar outDate) {
600 | try {
601 | outDate.setTime(mDateFormat.parse(date));
602 | return true;
603 | } catch (ParseException e) {
604 | Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
605 | return false;
606 | }
607 | }
608 |
609 | private boolean isNewDate(int year, int month, int dayOfMonth) {
610 | return (mCurrentDate.get(Calendar.YEAR) != year
611 | || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
612 | || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
613 | }
614 |
615 | private void setDate(int year, int month, int dayOfMonth) {
616 | mCurrentDate.set(year, month, dayOfMonth);
617 | if (mCurrentDate.before(mMinDate)) {
618 | mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
619 | } else if (mCurrentDate.after(mMaxDate)) {
620 | mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
621 | }
622 | }
623 |
624 | private void updateSpinners() {
625 | // set the spinner ranges respecting the min and max dates
626 | if (mCurrentDate.equals(mMinDate)) {
627 | mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
628 | mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
629 | mDaySpinner.setWrapSelectorWheel(false);
630 | mMonthSpinner.setDisplayedValues(null);
631 | mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
632 | mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
633 | mMonthSpinner.setWrapSelectorWheel(false);
634 | } else if (mCurrentDate.equals(mMaxDate)) {
635 | mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
636 | mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
637 | mDaySpinner.setWrapSelectorWheel(false);
638 | mMonthSpinner.setDisplayedValues(null);
639 | mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
640 | mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
641 | mMonthSpinner.setWrapSelectorWheel(false);
642 | } else {
643 | mDaySpinner.setMinValue(1);
644 | mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
645 | mDaySpinner.setWrapSelectorWheel(true);
646 | mMonthSpinner.setDisplayedValues(null);
647 | mMonthSpinner.setMinValue(0);
648 | mMonthSpinner.setMaxValue(11);
649 | mMonthSpinner.setWrapSelectorWheel(true);
650 | }
651 |
652 | // make sure the month names are a zero based array
653 | // with the months in the month spinner
654 | String[] displayedValues = CVArrays.copyOfRange(mShortMonths,
655 | mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
656 | mMonthSpinner.setDisplayedValues(displayedValues);
657 |
658 | // year spinner range does not change based on the current date
659 | mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
660 | mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
661 | mYearSpinner.setWrapSelectorWheel(false);
662 |
663 | // set the spinner values
664 | mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
665 | mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
666 | mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
667 | }
668 |
669 | /**
670 | * Updates the calendar view with the current date.
671 | */
672 | private void updateCalendarView() {
673 | mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
674 | }
675 |
676 | /**
677 | * @return The selected year.
678 | */
679 | public int getYear() {
680 | return mCurrentDate.get(Calendar.YEAR);
681 | }
682 |
683 | /**
684 | * @return The selected month.
685 | */
686 | public int getMonth() {
687 | return mCurrentDate.get(Calendar.MONTH);
688 | }
689 |
690 | /**
691 | * @return The selected day of month.
692 | */
693 | public int getDayOfMonth() {
694 | return mCurrentDate.get(Calendar.DAY_OF_MONTH);
695 | }
696 |
697 | /**
698 | * Notifies the listener, if such, for a change in the selected date.
699 | */
700 | private void notifyDateChanged() {
701 | sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
702 | if (mOnDateChangedListener != null) {
703 | mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth());
704 | }
705 | }
706 |
707 | /**
708 | * Sets the IME options for a spinner based on its ordering.
709 | *
710 | * @param spinner The spinner.
711 | * @param spinnerCount The total spinner count.
712 | * @param spinnerIndex The index of the given spinner.
713 | */
714 | private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
715 | final int imeOptions;
716 | if (spinnerIndex < spinnerCount - 1) {
717 | imeOptions = EditorInfo.IME_ACTION_NEXT;
718 | } else {
719 | imeOptions = EditorInfo.IME_ACTION_DONE;
720 | }
721 | TextView input = (TextView) spinner.findViewById(R.id.np__numberpicker_input);
722 | input.setImeOptions(imeOptions);
723 | }
724 |
725 | private void setContentDescriptions() {
726 | if (true) return; // increment/decrement buttons don't exist in backport
727 | // Day
728 | trySetContentDescription(mDaySpinner, R.id.np__increment,
729 | R.string.date_picker_increment_day_button);
730 | trySetContentDescription(mDaySpinner, R.id.np__decrement,
731 | R.string.date_picker_decrement_day_button);
732 | // Month
733 | trySetContentDescription(mMonthSpinner, R.id.np__increment,
734 | R.string.date_picker_increment_month_button);
735 | trySetContentDescription(mMonthSpinner, R.id.np__decrement,
736 | R.string.date_picker_decrement_month_button);
737 | // Year
738 | trySetContentDescription(mYearSpinner, R.id.np__increment,
739 | R.string.date_picker_increment_year_button);
740 | trySetContentDescription(mYearSpinner, R.id.np__decrement,
741 | R.string.date_picker_decrement_year_button);
742 | }
743 |
744 | private void trySetContentDescription(View root, int viewId, int contDescResId) {
745 | View target = root.findViewById(viewId);
746 | if (target != null) {
747 | target.setContentDescription(getContext().getString(contDescResId));
748 | }
749 | }
750 |
751 | private void updateInputState() {
752 | // Make sure that if the user changes the value and the IME is active
753 | // for one of the inputs if this widget, the IME is closed. If the user
754 | // changed the value via the IME and there is a next input the IME will
755 | // be shown, otherwise the user chose another means of changing the
756 | // value and having the IME up makes no sense.
757 | //InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
758 | InputMethodManager inputMethodManager =
759 | (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
760 | if (inputMethodManager != null) {
761 | if (inputMethodManager.isActive(mYearSpinnerInput)) {
762 | mYearSpinnerInput.clearFocus();
763 | inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
764 | } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
765 | mMonthSpinnerInput.clearFocus();
766 | inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
767 | } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
768 | mDaySpinnerInput.clearFocus();
769 | inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
770 | }
771 | }
772 | }
773 |
774 | /**
775 | * Class for managing state storing/restoring.
776 | */
777 | private static class SavedState extends BaseSavedState {
778 |
779 | private final int mYear;
780 |
781 | private final int mMonth;
782 |
783 | private final int mDay;
784 |
785 | /**
786 | * Constructor called from {@link DatePicker#onSaveInstanceState()}
787 | */
788 | private SavedState(Parcelable superState, int year, int month, int day) {
789 | super(superState);
790 | mYear = year;
791 | mMonth = month;
792 | mDay = day;
793 | }
794 |
795 | /**
796 | * Constructor called from {@link #CREATOR}
797 | */
798 | private SavedState(Parcel in) {
799 | super(in);
800 | mYear = in.readInt();
801 | mMonth = in.readInt();
802 | mDay = in.readInt();
803 | }
804 |
805 | @Override
806 | public void writeToParcel(Parcel dest, int flags) {
807 | super.writeToParcel(dest, flags);
808 | dest.writeInt(mYear);
809 | dest.writeInt(mMonth);
810 | dest.writeInt(mDay);
811 | }
812 |
813 | @SuppressWarnings("all")
814 | // suppress unused and hiding
815 | public static final Parcelable.Creator