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