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 17package android.widget; 18 19import android.annotation.Widget; 20import android.content.Context; 21import android.content.res.Configuration; 22import android.content.res.TypedArray; 23import android.os.Parcel; 24import android.os.Parcelable; 25import android.text.TextUtils; 26import android.text.InputType; 27import android.text.format.DateFormat; 28import android.text.format.DateUtils; 29import android.util.AttributeSet; 30import android.util.Log; 31import android.util.SparseArray; 32import android.view.LayoutInflater; 33import android.view.View; 34import android.view.accessibility.AccessibilityEvent; 35import android.view.accessibility.AccessibilityNodeInfo; 36import android.view.inputmethod.EditorInfo; 37import android.view.inputmethod.InputMethodManager; 38import android.widget.NumberPicker.OnValueChangeListener; 39 40import com.android.internal.R; 41 42import java.text.DateFormatSymbols; 43import java.text.ParseException; 44import java.text.SimpleDateFormat; 45import java.util.Arrays; 46import java.util.Calendar; 47import java.util.Locale; 48import java.util.TimeZone; 49 50import libcore.icu.ICU; 51 52/** 53 * This class is a widget for selecting a date. The date can be selected by a 54 * year, month, and day spinners or a {@link CalendarView}. The set of spinners 55 * and the calendar view are automatically synchronized. The client can 56 * customize whether only the spinners, or only the calendar view, or both to be 57 * displayed. Also the minimal and maximal date from which dates to be selected 58 * can be customized. 59 * <p> 60 * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a> 61 * guide. 62 * </p> 63 * <p> 64 * For a dialog using this view, see {@link android.app.DatePickerDialog}. 65 * </p> 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 75public 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_spinnersShown, 161 DEFAULT_SPINNERS_SHOWN); 162 boolean calendarViewShown = attributesArray.getBoolean( 163 R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN); 164 int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear, 165 DEFAULT_START_YEAR); 166 int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); 167 String minDate = attributesArray.getString(R.styleable.DatePicker_minDate); 168 String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate); 169 int layoutResourceId = attributesArray.getResourceId(R.styleable.DatePicker_internalLayout, 170 R.layout.date_picker); 171 attributesArray.recycle(); 172 173 LayoutInflater inflater = (LayoutInflater) context 174 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 175 inflater.inflate(layoutResourceId, this, true); 176 177 OnValueChangeListener onChangeListener = new 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.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.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.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 (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 * <p> 300 * Note: The default minimal date is 01/01/1900. 301 * <p> 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 * <p> 336 * Note: The default maximal date is 12/31/2100. 337 * <p> 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(mContext, 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.getVisibility() == View.VISIBLE); 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 DateFormatSymbols().getShortMonths(); 485 486 if (usingNumericMonths()) { 487 // We're in a locale where a date should either be all-numeric, or all-text. 488 // All-text would require custom NumberPicker formatters for day and year. 489 mShortMonths = new String[mNumberOfMonths]; 490 for (int i = 0; i < mNumberOfMonths; ++i) { 491 mShortMonths[i] = String.format("%d", i + 1); 492 } 493 } 494 } 495 496 /** 497 * Tests whether the current locale is one where there are no real month names, 498 * such as Chinese, Japanese, or Korean locales. 499 */ 500 private boolean usingNumericMonths() { 501 return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0)); 502 } 503 504 /** 505 * Gets a calendar for locale bootstrapped with the value of a given calendar. 506 * 507 * @param oldCalendar The old calendar. 508 * @param locale The locale. 509 */ 510 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { 511 if (oldCalendar == null) { 512 return Calendar.getInstance(locale); 513 } else { 514 final long currentTimeMillis = oldCalendar.getTimeInMillis(); 515 Calendar newCalendar = Calendar.getInstance(locale); 516 newCalendar.setTimeInMillis(currentTimeMillis); 517 return newCalendar; 518 } 519 } 520 521 /** 522 * Reorders the spinners according to the date format that is 523 * explicitly set by the user and if no such is set fall back 524 * to the current locale's default format. 525 */ 526 private void reorderSpinners() { 527 mSpinners.removeAllViews(); 528 // We use numeric spinners for year and day, but textual months. Ask icu4c what 529 // order the user's locale uses for that combination. http://b/7207103. 530 String pattern = ICU.getBestDateTimePattern("yyyyMMMdd", Locale.getDefault().toString()); 531 char[] order = ICU.getDateFormatOrder(pattern); 532 final int spinnerCount = order.length; 533 for (int i = 0; i < spinnerCount; i++) { 534 switch (order[i]) { 535 case 'd': 536 mSpinners.addView(mDaySpinner); 537 setImeOptions(mDaySpinner, spinnerCount, i); 538 break; 539 case 'M': 540 mSpinners.addView(mMonthSpinner); 541 setImeOptions(mMonthSpinner, spinnerCount, i); 542 break; 543 case 'y': 544 mSpinners.addView(mYearSpinner); 545 setImeOptions(mYearSpinner, spinnerCount, i); 546 break; 547 default: 548 throw new IllegalArgumentException(Arrays.toString(order)); 549 } 550 } 551 } 552 553 /** 554 * Updates the current date. 555 * 556 * @param year The year. 557 * @param month The month which is <strong>starting from zero</strong>. 558 * @param dayOfMonth The day of the month. 559 */ 560 public void updateDate(int year, int month, int dayOfMonth) { 561 if (!isNewDate(year, month, dayOfMonth)) { 562 return; 563 } 564 setDate(year, month, dayOfMonth); 565 updateSpinners(); 566 updateCalendarView(); 567 notifyDateChanged(); 568 } 569 570 // Override so we are in complete control of save / restore for this widget. 571 @Override 572 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 573 dispatchThawSelfOnly(container); 574 } 575 576 @Override 577 protected Parcelable onSaveInstanceState() { 578 Parcelable superState = super.onSaveInstanceState(); 579 return new SavedState(superState, getYear(), getMonth(), getDayOfMonth()); 580 } 581 582 @Override 583 protected void onRestoreInstanceState(Parcelable state) { 584 SavedState ss = (SavedState) state; 585 super.onRestoreInstanceState(ss.getSuperState()); 586 setDate(ss.mYear, ss.mMonth, ss.mDay); 587 updateSpinners(); 588 updateCalendarView(); 589 } 590 591 /** 592 * Initialize the state. If the provided values designate an inconsistent 593 * date the values are normalized before updating the spinners. 594 * 595 * @param year The initial year. 596 * @param monthOfYear The initial month <strong>starting from zero</strong>. 597 * @param dayOfMonth The initial day of the month. 598 * @param onDateChangedListener How user is notified date is changed by 599 * user, can be null. 600 */ 601 public void init(int year, int monthOfYear, int dayOfMonth, 602 OnDateChangedListener onDateChangedListener) { 603 setDate(year, monthOfYear, dayOfMonth); 604 updateSpinners(); 605 updateCalendarView(); 606 mOnDateChangedListener = onDateChangedListener; 607 } 608 609 /** 610 * Parses the given <code>date</code> and in case of success sets the result 611 * to the <code>outDate</code>. 612 * 613 * @return True if the date was parsed. 614 */ 615 private boolean parseDate(String date, Calendar outDate) { 616 try { 617 outDate.setTime(mDateFormat.parse(date)); 618 return true; 619 } catch (ParseException e) { 620 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); 621 return false; 622 } 623 } 624 625 private boolean isNewDate(int year, int month, int dayOfMonth) { 626 return (mCurrentDate.get(Calendar.YEAR) != year 627 || mCurrentDate.get(Calendar.MONTH) != dayOfMonth 628 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month); 629 } 630 631 private void setDate(int year, int month, int dayOfMonth) { 632 mCurrentDate.set(year, month, dayOfMonth); 633 if (mCurrentDate.before(mMinDate)) { 634 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); 635 } else if (mCurrentDate.after(mMaxDate)) { 636 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); 637 } 638 } 639 640 private void updateSpinners() { 641 // set the spinner ranges respecting the min and max dates 642 if (mCurrentDate.equals(mMinDate)) { 643 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 644 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 645 mDaySpinner.setWrapSelectorWheel(false); 646 mMonthSpinner.setDisplayedValues(null); 647 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH)); 648 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH)); 649 mMonthSpinner.setWrapSelectorWheel(false); 650 } else if (mCurrentDate.equals(mMaxDate)) { 651 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH)); 652 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 653 mDaySpinner.setWrapSelectorWheel(false); 654 mMonthSpinner.setDisplayedValues(null); 655 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH)); 656 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH)); 657 mMonthSpinner.setWrapSelectorWheel(false); 658 } else { 659 mDaySpinner.setMinValue(1); 660 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 661 mDaySpinner.setWrapSelectorWheel(true); 662 mMonthSpinner.setDisplayedValues(null); 663 mMonthSpinner.setMinValue(0); 664 mMonthSpinner.setMaxValue(11); 665 mMonthSpinner.setWrapSelectorWheel(true); 666 } 667 668 // make sure the month names are a zero based array 669 // with the months in the month spinner 670 String[] displayedValues = Arrays.copyOfRange(mShortMonths, 671 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1); 672 mMonthSpinner.setDisplayedValues(displayedValues); 673 674 // year spinner range does not change based on the current date 675 mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR)); 676 mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR)); 677 mYearSpinner.setWrapSelectorWheel(false); 678 679 // set the spinner values 680 mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR)); 681 mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH)); 682 mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 683 684 if (usingNumericMonths()) { 685 mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER); 686 } 687 } 688 689 /** 690 * Updates the calendar view with the current date. 691 */ 692 private void updateCalendarView() { 693 mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false); 694 } 695 696 /** 697 * @return The selected year. 698 */ 699 public int getYear() { 700 return mCurrentDate.get(Calendar.YEAR); 701 } 702 703 /** 704 * @return The selected month. 705 */ 706 public int getMonth() { 707 return mCurrentDate.get(Calendar.MONTH); 708 } 709 710 /** 711 * @return The selected day of month. 712 */ 713 public int getDayOfMonth() { 714 return mCurrentDate.get(Calendar.DAY_OF_MONTH); 715 } 716 717 /** 718 * Notifies the listener, if such, for a change in the selected date. 719 */ 720 private void notifyDateChanged() { 721 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 722 if (mOnDateChangedListener != null) { 723 mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth()); 724 } 725 } 726 727 /** 728 * Sets the IME options for a spinner based on its ordering. 729 * 730 * @param spinner The spinner. 731 * @param spinnerCount The total spinner count. 732 * @param spinnerIndex The index of the given spinner. 733 */ 734 private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) { 735 final int imeOptions; 736 if (spinnerIndex < spinnerCount - 1) { 737 imeOptions = EditorInfo.IME_ACTION_NEXT; 738 } else { 739 imeOptions = EditorInfo.IME_ACTION_DONE; 740 } 741 TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input); 742 input.setImeOptions(imeOptions); 743 } 744 745 private void setContentDescriptions() { 746 // Day 747 trySetContentDescription(mDaySpinner, R.id.increment, 748 R.string.date_picker_increment_day_button); 749 trySetContentDescription(mDaySpinner, R.id.decrement, 750 R.string.date_picker_decrement_day_button); 751 // Month 752 trySetContentDescription(mMonthSpinner, R.id.increment, 753 R.string.date_picker_increment_month_button); 754 trySetContentDescription(mMonthSpinner, R.id.decrement, 755 R.string.date_picker_decrement_month_button); 756 // Year 757 trySetContentDescription(mYearSpinner, R.id.increment, 758 R.string.date_picker_increment_year_button); 759 trySetContentDescription(mYearSpinner, R.id.decrement, 760 R.string.date_picker_decrement_year_button); 761 } 762 763 private void trySetContentDescription(View root, int viewId, int contDescResId) { 764 View target = root.findViewById(viewId); 765 if (target != null) { 766 target.setContentDescription(mContext.getString(contDescResId)); 767 } 768 } 769 770 private void updateInputState() { 771 // Make sure that if the user changes the value and the IME is active 772 // for one of the inputs if this widget, the IME is closed. If the user 773 // changed the value via the IME and there is a next input the IME will 774 // be shown, otherwise the user chose another means of changing the 775 // value and having the IME up makes no sense. 776 InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); 777 if (inputMethodManager != null) { 778 if (inputMethodManager.isActive(mYearSpinnerInput)) { 779 mYearSpinnerInput.clearFocus(); 780 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 781 } else if (inputMethodManager.isActive(mMonthSpinnerInput)) { 782 mMonthSpinnerInput.clearFocus(); 783 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 784 } else if (inputMethodManager.isActive(mDaySpinnerInput)) { 785 mDaySpinnerInput.clearFocus(); 786 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 787 } 788 } 789 } 790 791 /** 792 * Class for managing state storing/restoring. 793 */ 794 private static class SavedState extends BaseSavedState { 795 796 private final int mYear; 797 798 private final int mMonth; 799 800 private final int mDay; 801 802 /** 803 * Constructor called from {@link DatePicker#onSaveInstanceState()} 804 */ 805 private SavedState(Parcelable superState, int year, int month, int day) { 806 super(superState); 807 mYear = year; 808 mMonth = month; 809 mDay = day; 810 } 811 812 /** 813 * Constructor called from {@link #CREATOR} 814 */ 815 private SavedState(Parcel in) { 816 super(in); 817 mYear = in.readInt(); 818 mMonth = in.readInt(); 819 mDay = in.readInt(); 820 } 821 822 @Override 823 public void writeToParcel(Parcel dest, int flags) { 824 super.writeToParcel(dest, flags); 825 dest.writeInt(mYear); 826 dest.writeInt(mMonth); 827 dest.writeInt(mDay); 828 } 829 830 @SuppressWarnings("all") 831 // suppress unused and hiding 832 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { 833 834 public SavedState createFromParcel(Parcel in) { 835 return new SavedState(in); 836 } 837 838 public SavedState[] newArray(int size) { 839 return new SavedState[size]; 840 } 841 }; 842 } 843} 844