1 /* 2 * Copyright (C) 2014 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.content.Context; 20 import android.content.res.ColorStateList; 21 import android.content.res.Configuration; 22 import android.content.res.Resources; 23 import android.content.res.TypedArray; 24 import android.graphics.drawable.Drawable; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.text.format.DateFormat; 28 import android.text.format.DateUtils; 29 import android.util.AttributeSet; 30 import android.util.SparseArray; 31 import android.view.HapticFeedbackConstants; 32 import android.view.LayoutInflater; 33 import android.view.View; 34 import android.view.accessibility.AccessibilityEvent; 35 import android.view.accessibility.AccessibilityNodeInfo; 36 import android.view.animation.AlphaAnimation; 37 import android.view.animation.Animation; 38 39 import com.android.internal.R; 40 import com.android.internal.widget.AccessibleDateAnimator; 41 42 import java.text.SimpleDateFormat; 43 import java.util.Calendar; 44 import java.util.HashSet; 45 import java.util.Locale; 46 47 /** 48 * A delegate for picking up a date (day / month / year). 49 */ 50 class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate implements 51 View.OnClickListener, DatePickerController { 52 private static final int USE_LOCALE = 0; 53 54 private static final int UNINITIALIZED = -1; 55 private static final int MONTH_AND_DAY_VIEW = 0; 56 private static final int YEAR_VIEW = 1; 57 58 private static final int DEFAULT_START_YEAR = 1900; 59 private static final int DEFAULT_END_YEAR = 2100; 60 61 private static final int ANIMATION_DURATION = 300; 62 63 private static final int MONTH_INDEX = 0; 64 private static final int DAY_INDEX = 1; 65 private static final int YEAR_INDEX = 2; 66 67 private SimpleDateFormat mYearFormat = new SimpleDateFormat("y", Locale.getDefault()); 68 private SimpleDateFormat mDayFormat = new SimpleDateFormat("d", Locale.getDefault()); 69 70 private TextView mDayOfWeekView; 71 72 /** Layout that contains the current month, day, and year. */ 73 private LinearLayout mMonthDayYearLayout; 74 75 /** Clickable layout that contains the current day and year. */ 76 private LinearLayout mMonthAndDayLayout; 77 78 private TextView mHeaderMonthTextView; 79 private TextView mHeaderDayOfMonthTextView; 80 private TextView mHeaderYearTextView; 81 private DayPickerView mDayPickerView; 82 private YearPickerView mYearPickerView; 83 84 private boolean mIsEnabled = true; 85 86 // Accessibility strings. 87 private String mDayPickerDescription; 88 private String mSelectDay; 89 private String mYearPickerDescription; 90 private String mSelectYear; 91 92 private AccessibleDateAnimator mAnimator; 93 94 private DatePicker.OnDateChangedListener mDateChangedListener; 95 96 private int mCurrentView = UNINITIALIZED; 97 98 private Calendar mCurrentDate; 99 private Calendar mTempDate; 100 private Calendar mMinDate; 101 private Calendar mMaxDate; 102 103 private int mFirstDayOfWeek = USE_LOCALE; 104 105 private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>(); 106 107 public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs, 108 int defStyleAttr, int defStyleRes) { 109 super(delegator, context); 110 111 final Locale locale = Locale.getDefault(); 112 mMinDate = getCalendarForLocale(mMinDate, locale); 113 mMaxDate = getCalendarForLocale(mMaxDate, locale); 114 mTempDate = getCalendarForLocale(mMaxDate, locale); 115 mCurrentDate = getCalendarForLocale(mCurrentDate, locale); 116 117 mMinDate.set(DEFAULT_START_YEAR, 1, 1); 118 mMaxDate.set(DEFAULT_END_YEAR, 12, 31); 119 120 final Resources res = mDelegator.getResources(); 121 final TypedArray a = mContext.obtainStyledAttributes(attrs, 122 R.styleable.DatePicker, defStyleAttr, defStyleRes); 123 final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( 124 Context.LAYOUT_INFLATER_SERVICE); 125 final int layoutResourceId = a.getResourceId( 126 R.styleable.DatePicker_internalLayout, R.layout.date_picker_holo); 127 final View mainView = inflater.inflate(layoutResourceId, null); 128 mDelegator.addView(mainView); 129 130 mDayOfWeekView = (TextView) mainView.findViewById(R.id.date_picker_header); 131 132 // Layout that contains the current date and day name header. 133 final LinearLayout dateLayout = (LinearLayout) mainView.findViewById( 134 R.id.day_picker_selector_layout); 135 mMonthDayYearLayout = (LinearLayout) mainView.findViewById( 136 R.id.date_picker_month_day_year_layout); 137 mMonthAndDayLayout = (LinearLayout) mainView.findViewById( 138 R.id.date_picker_month_and_day_layout); 139 mMonthAndDayLayout.setOnClickListener(this); 140 mHeaderMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_month); 141 mHeaderDayOfMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_day); 142 mHeaderYearTextView = (TextView) mainView.findViewById(R.id.date_picker_year); 143 mHeaderYearTextView.setOnClickListener(this); 144 145 // Obtain default highlight color from the theme. 146 final int defaultHighlightColor = mHeaderYearTextView.getHighlightColor(); 147 148 // Use Theme attributes if possible 149 final int dayOfWeekTextAppearanceResId = a.getResourceId( 150 R.styleable.DatePicker_dayOfWeekTextAppearance, -1); 151 if (dayOfWeekTextAppearanceResId != -1) { 152 mDayOfWeekView.setTextAppearance(context, dayOfWeekTextAppearanceResId); 153 } 154 155 mDayOfWeekView.setBackground(a.getDrawable(R.styleable.DatePicker_dayOfWeekBackground)); 156 157 dateLayout.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground)); 158 159 final int headerSelectedTextColor = a.getColor( 160 R.styleable.DatePicker_headerSelectedTextColor, defaultHighlightColor); 161 final int monthTextAppearanceResId = a.getResourceId( 162 R.styleable.DatePicker_headerMonthTextAppearance, -1); 163 if (monthTextAppearanceResId != -1) { 164 mHeaderMonthTextView.setTextAppearance(context, monthTextAppearanceResId); 165 } 166 mHeaderMonthTextView.setTextColor(ColorStateList.addFirstIfMissing( 167 mHeaderMonthTextView.getTextColors(), R.attr.state_selected, 168 headerSelectedTextColor)); 169 170 final int dayOfMonthTextAppearanceResId = a.getResourceId( 171 R.styleable.DatePicker_headerDayOfMonthTextAppearance, -1); 172 if (dayOfMonthTextAppearanceResId != -1) { 173 mHeaderDayOfMonthTextView.setTextAppearance(context, dayOfMonthTextAppearanceResId); 174 } 175 mHeaderDayOfMonthTextView.setTextColor(ColorStateList.addFirstIfMissing( 176 mHeaderDayOfMonthTextView.getTextColors(), R.attr.state_selected, 177 headerSelectedTextColor)); 178 179 final int yearTextAppearanceResId = a.getResourceId( 180 R.styleable.DatePicker_headerYearTextAppearance, -1); 181 if (yearTextAppearanceResId != -1) { 182 mHeaderYearTextView.setTextAppearance(context, yearTextAppearanceResId); 183 } 184 mHeaderYearTextView.setTextColor(ColorStateList.addFirstIfMissing( 185 mHeaderYearTextView.getTextColors(), R.attr.state_selected, 186 headerSelectedTextColor)); 187 188 mDayPickerView = new DayPickerView(mContext, this); 189 mYearPickerView = new YearPickerView(mContext); 190 mYearPickerView.init(this); 191 192 final int yearSelectedCircleColor = a.getColor(R.styleable.DatePicker_yearListSelectorColor, 193 defaultHighlightColor); 194 mYearPickerView.setYearSelectedCircleColor(yearSelectedCircleColor); 195 196 final ColorStateList calendarTextColor = a.getColorStateList( 197 R.styleable.DatePicker_calendarTextColor); 198 final int calendarSelectedTextColor = a.getColor( 199 R.styleable.DatePicker_calendarSelectedTextColor, defaultHighlightColor); 200 mDayPickerView.setCalendarTextColor(ColorStateList.addFirstIfMissing( 201 calendarTextColor, R.attr.state_selected, calendarSelectedTextColor)); 202 203 mDayPickerDescription = res.getString(R.string.day_picker_description); 204 mSelectDay = res.getString(R.string.select_day); 205 mYearPickerDescription = res.getString(R.string.year_picker_description); 206 mSelectYear = res.getString(R.string.select_year); 207 208 mAnimator = (AccessibleDateAnimator) mainView.findViewById(R.id.animator); 209 mAnimator.addView(mDayPickerView); 210 mAnimator.addView(mYearPickerView); 211 mAnimator.setDateMillis(mCurrentDate.getTimeInMillis()); 212 213 final Animation animation = new AlphaAnimation(0.0f, 1.0f); 214 animation.setDuration(ANIMATION_DURATION); 215 mAnimator.setInAnimation(animation); 216 217 final Animation animation2 = new AlphaAnimation(1.0f, 0.0f); 218 animation2.setDuration(ANIMATION_DURATION); 219 mAnimator.setOutAnimation(animation2); 220 221 updateDisplay(false); 222 setCurrentView(MONTH_AND_DAY_VIEW); 223 } 224 225 /** 226 * Gets a calendar for locale bootstrapped with the value of a given calendar. 227 * 228 * @param oldCalendar The old calendar. 229 * @param locale The locale. 230 */ 231 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { 232 if (oldCalendar == null) { 233 return Calendar.getInstance(locale); 234 } else { 235 final long currentTimeMillis = oldCalendar.getTimeInMillis(); 236 Calendar newCalendar = Calendar.getInstance(locale); 237 newCalendar.setTimeInMillis(currentTimeMillis); 238 return newCalendar; 239 } 240 } 241 242 /** 243 * Compute the array representing the order of Month / Day / Year views in their layout. 244 * Will be used for I18N purpose as the order of them depends on the Locale. 245 */ 246 private int[] getMonthDayYearIndexes(String pattern) { 247 int[] result = new int[3]; 248 249 final String filteredPattern = pattern.replaceAll("'.*?'", ""); 250 251 final int dayIndex = filteredPattern.indexOf('d'); 252 final int monthMIndex = filteredPattern.indexOf("M"); 253 final int monthIndex = (monthMIndex != -1) ? monthMIndex : filteredPattern.indexOf("L"); 254 final int yearIndex = filteredPattern.indexOf("y"); 255 256 if (yearIndex < monthIndex) { 257 result[YEAR_INDEX] = 0; 258 259 if (monthIndex < dayIndex) { 260 result[MONTH_INDEX] = 1; 261 result[DAY_INDEX] = 2; 262 } else { 263 result[MONTH_INDEX] = 2; 264 result[DAY_INDEX] = 1; 265 } 266 } else { 267 result[YEAR_INDEX] = 2; 268 269 if (monthIndex < dayIndex) { 270 result[MONTH_INDEX] = 0; 271 result[DAY_INDEX] = 1; 272 } else { 273 result[MONTH_INDEX] = 1; 274 result[DAY_INDEX] = 0; 275 } 276 } 277 return result; 278 } 279 280 private void updateDisplay(boolean announce) { 281 if (mDayOfWeekView != null) { 282 mDayOfWeekView.setText(mCurrentDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, 283 Locale.getDefault())); 284 } 285 286 // Compute indices of Month, Day and Year views 287 final String bestDateTimePattern = 288 DateFormat.getBestDateTimePattern(mCurrentLocale, "yMMMd"); 289 final int[] viewIndices = getMonthDayYearIndexes(bestDateTimePattern); 290 291 // Position the Year and MonthAndDay views within the header. 292 mMonthDayYearLayout.removeAllViews(); 293 if (viewIndices[YEAR_INDEX] == 0) { 294 mMonthDayYearLayout.addView(mHeaderYearTextView); 295 mMonthDayYearLayout.addView(mMonthAndDayLayout); 296 } else { 297 mMonthDayYearLayout.addView(mMonthAndDayLayout); 298 mMonthDayYearLayout.addView(mHeaderYearTextView); 299 } 300 301 // Position Day and Month views within the MonthAndDay view. 302 mMonthAndDayLayout.removeAllViews(); 303 if (viewIndices[MONTH_INDEX] > viewIndices[DAY_INDEX]) { 304 mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView); 305 mMonthAndDayLayout.addView(mHeaderMonthTextView); 306 } else { 307 mMonthAndDayLayout.addView(mHeaderMonthTextView); 308 mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView); 309 } 310 311 mHeaderMonthTextView.setText(mCurrentDate.getDisplayName(Calendar.MONTH, Calendar.SHORT, 312 Locale.getDefault()).toUpperCase(Locale.getDefault())); 313 mHeaderDayOfMonthTextView.setText(mDayFormat.format(mCurrentDate.getTime())); 314 mHeaderYearTextView.setText(mYearFormat.format(mCurrentDate.getTime())); 315 316 // Accessibility. 317 long millis = mCurrentDate.getTimeInMillis(); 318 mAnimator.setDateMillis(millis); 319 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR; 320 String monthAndDayText = DateUtils.formatDateTime(mContext, millis, flags); 321 mMonthAndDayLayout.setContentDescription(monthAndDayText); 322 323 if (announce) { 324 flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; 325 String fullDateText = DateUtils.formatDateTime(mContext, millis, flags); 326 mAnimator.announceForAccessibility(fullDateText); 327 } 328 updatePickers(); 329 } 330 331 private void setCurrentView(final int viewIndex) { 332 long millis = mCurrentDate.getTimeInMillis(); 333 334 switch (viewIndex) { 335 case MONTH_AND_DAY_VIEW: 336 mDayPickerView.onDateChanged(); 337 if (mCurrentView != viewIndex) { 338 mMonthAndDayLayout.setSelected(true); 339 mHeaderYearTextView.setSelected(false); 340 mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW); 341 mCurrentView = viewIndex; 342 } 343 344 final int flags = DateUtils.FORMAT_SHOW_DATE; 345 final String dayString = DateUtils.formatDateTime(mContext, millis, flags); 346 mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString); 347 mAnimator.announceForAccessibility(mSelectDay); 348 break; 349 case YEAR_VIEW: 350 mYearPickerView.onDateChanged(); 351 if (mCurrentView != viewIndex) { 352 mMonthAndDayLayout.setSelected(false); 353 mHeaderYearTextView.setSelected(true); 354 mAnimator.setDisplayedChild(YEAR_VIEW); 355 mCurrentView = viewIndex; 356 } 357 358 final CharSequence yearString = mYearFormat.format(millis); 359 mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString); 360 mAnimator.announceForAccessibility(mSelectYear); 361 break; 362 } 363 } 364 365 @Override 366 public void init(int year, int monthOfYear, int dayOfMonth, 367 DatePicker.OnDateChangedListener callBack) { 368 mDateChangedListener = callBack; 369 mCurrentDate.set(Calendar.YEAR, year); 370 mCurrentDate.set(Calendar.MONTH, monthOfYear); 371 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); 372 updateDisplay(false); 373 } 374 375 @Override 376 public void updateDate(int year, int month, int dayOfMonth) { 377 mCurrentDate.set(Calendar.YEAR, year); 378 mCurrentDate.set(Calendar.MONTH, month); 379 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); 380 if (mDateChangedListener != null) { 381 mDateChangedListener.onDateChanged(mDelegator, year, month, dayOfMonth); 382 } 383 updateDisplay(false); 384 } 385 386 @Override 387 public int getYear() { 388 return mCurrentDate.get(Calendar.YEAR); 389 } 390 391 @Override 392 public int getMonth() { 393 return mCurrentDate.get(Calendar.MONTH); 394 } 395 396 @Override 397 public int getDayOfMonth() { 398 return mCurrentDate.get(Calendar.DAY_OF_MONTH); 399 } 400 401 @Override 402 public void setMinDate(long minDate) { 403 mTempDate.setTimeInMillis(minDate); 404 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) 405 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { 406 return; 407 } 408 if (mCurrentDate.before(mTempDate)) { 409 mCurrentDate.setTimeInMillis(minDate); 410 updatePickers(); 411 updateDisplay(false); 412 } 413 mMinDate.setTimeInMillis(minDate); 414 mDayPickerView.goTo(getSelectedDay(), false, true, true); 415 } 416 417 @Override 418 public Calendar getMinDate() { 419 return mMinDate; 420 } 421 422 @Override 423 public void setMaxDate(long maxDate) { 424 mTempDate.setTimeInMillis(maxDate); 425 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) 426 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) { 427 return; 428 } 429 if (mCurrentDate.after(mTempDate)) { 430 mCurrentDate.setTimeInMillis(maxDate); 431 updatePickers(); 432 updateDisplay(false); 433 } 434 mMaxDate.setTimeInMillis(maxDate); 435 mDayPickerView.goTo(getSelectedDay(), false, true, true); 436 } 437 438 @Override 439 public Calendar getMaxDate() { 440 return mMaxDate; 441 } 442 443 @Override 444 public void setFirstDayOfWeek(int firstDayOfWeek) { 445 mFirstDayOfWeek = firstDayOfWeek; 446 } 447 448 @Override 449 public int getFirstDayOfWeek() { 450 if (mFirstDayOfWeek != USE_LOCALE) { 451 return mFirstDayOfWeek; 452 } 453 return mCurrentDate.getFirstDayOfWeek(); 454 } 455 456 @Override 457 public int getMinYear() { 458 return mMinDate.get(Calendar.YEAR); 459 } 460 461 @Override 462 public int getMaxYear() { 463 return mMaxDate.get(Calendar.YEAR); 464 } 465 466 @Override 467 public int getMinMonth() { 468 return mMinDate.get(Calendar.MONTH); 469 } 470 471 @Override 472 public int getMaxMonth() { 473 return mMaxDate.get(Calendar.MONTH); 474 } 475 476 @Override 477 public int getMinDay() { 478 return mMinDate.get(Calendar.DAY_OF_MONTH); 479 } 480 481 @Override 482 public int getMaxDay() { 483 return mMaxDate.get(Calendar.DAY_OF_MONTH); 484 } 485 486 @Override 487 public void setEnabled(boolean enabled) { 488 mMonthAndDayLayout.setEnabled(enabled); 489 mHeaderYearTextView.setEnabled(enabled); 490 mAnimator.setEnabled(enabled); 491 mIsEnabled = enabled; 492 } 493 494 @Override 495 public boolean isEnabled() { 496 return mIsEnabled; 497 } 498 499 @Override 500 public CalendarView getCalendarView() { 501 throw new UnsupportedOperationException( 502 "CalendarView does not exists for the new DatePicker"); 503 } 504 505 @Override 506 public void setCalendarViewShown(boolean shown) { 507 // No-op for compatibility with the old DatePicker. 508 } 509 510 @Override 511 public boolean getCalendarViewShown() { 512 return false; 513 } 514 515 @Override 516 public void setSpinnersShown(boolean shown) { 517 // No-op for compatibility with the old DatePicker. 518 } 519 520 @Override 521 public boolean getSpinnersShown() { 522 return false; 523 } 524 525 @Override 526 public void onConfigurationChanged(Configuration newConfig) { 527 mYearFormat = new SimpleDateFormat("y", newConfig.locale); 528 mDayFormat = new SimpleDateFormat("d", newConfig.locale); 529 } 530 531 @Override 532 public Parcelable onSaveInstanceState(Parcelable superState) { 533 final int year = mCurrentDate.get(Calendar.YEAR); 534 final int month = mCurrentDate.get(Calendar.MONTH); 535 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH); 536 537 int listPosition = -1; 538 int listPositionOffset = -1; 539 540 if (mCurrentView == MONTH_AND_DAY_VIEW) { 541 listPosition = mDayPickerView.getMostVisiblePosition(); 542 } else if (mCurrentView == YEAR_VIEW) { 543 listPosition = mYearPickerView.getFirstVisiblePosition(); 544 listPositionOffset = mYearPickerView.getFirstPositionOffset(); 545 } 546 547 return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(), 548 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset); 549 } 550 551 @Override 552 public void onRestoreInstanceState(Parcelable state) { 553 SavedState ss = (SavedState) state; 554 555 mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay()); 556 mCurrentView = ss.getCurrentView(); 557 mMinDate.setTimeInMillis(ss.getMinDate()); 558 mMaxDate.setTimeInMillis(ss.getMaxDate()); 559 560 updateDisplay(false); 561 setCurrentView(mCurrentView); 562 563 final int listPosition = ss.getListPosition(); 564 if (listPosition != -1) { 565 if (mCurrentView == MONTH_AND_DAY_VIEW) { 566 mDayPickerView.postSetSelection(listPosition); 567 } else if (mCurrentView == YEAR_VIEW) { 568 mYearPickerView.postSetSelectionFromTop(listPosition, ss.getListPositionOffset()); 569 } 570 } 571 } 572 573 @Override 574 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 575 onPopulateAccessibilityEvent(event); 576 return true; 577 } 578 579 @Override 580 public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 581 event.getText().add(mCurrentDate.getTime().toString()); 582 } 583 584 @Override 585 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 586 event.setClassName(DatePicker.class.getName()); 587 } 588 589 @Override 590 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 591 info.setClassName(DatePicker.class.getName()); 592 } 593 594 @Override 595 public void onYearSelected(int year) { 596 adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year); 597 mCurrentDate.set(Calendar.YEAR, year); 598 updatePickers(); 599 setCurrentView(MONTH_AND_DAY_VIEW); 600 updateDisplay(true); 601 } 602 603 // If the newly selected month / year does not contain the currently selected day number, 604 // change the selected day number to the last day of the selected month or year. 605 // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30 606 // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013 607 private void adjustDayInMonthIfNeeded(int month, int year) { 608 int day = mCurrentDate.get(Calendar.DAY_OF_MONTH); 609 int daysInMonth = getDaysInMonth(month, year); 610 if (day > daysInMonth) { 611 mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth); 612 } 613 } 614 615 public static int getDaysInMonth(int month, int year) { 616 switch (month) { 617 case Calendar.JANUARY: 618 case Calendar.MARCH: 619 case Calendar.MAY: 620 case Calendar.JULY: 621 case Calendar.AUGUST: 622 case Calendar.OCTOBER: 623 case Calendar.DECEMBER: 624 return 31; 625 case Calendar.APRIL: 626 case Calendar.JUNE: 627 case Calendar.SEPTEMBER: 628 case Calendar.NOVEMBER: 629 return 30; 630 case Calendar.FEBRUARY: 631 return (year % 4 == 0) ? 29 : 28; 632 default: 633 throw new IllegalArgumentException("Invalid Month"); 634 } 635 } 636 637 @Override 638 public void onDayOfMonthSelected(int year, int month, int day) { 639 mCurrentDate.set(Calendar.YEAR, year); 640 mCurrentDate.set(Calendar.MONTH, month); 641 mCurrentDate.set(Calendar.DAY_OF_MONTH, day); 642 updatePickers(); 643 updateDisplay(true); 644 } 645 646 private void updatePickers() { 647 for (OnDateChangedListener listener : mListeners) { 648 listener.onDateChanged(); 649 } 650 } 651 652 @Override 653 public void registerOnDateChangedListener(OnDateChangedListener listener) { 654 mListeners.add(listener); 655 } 656 657 @Override 658 public void unregisterOnDateChangedListener(OnDateChangedListener listener) { 659 mListeners.remove(listener); 660 } 661 662 @Override 663 public Calendar getSelectedDay() { 664 return mCurrentDate; 665 } 666 667 @Override 668 public void tryVibrate() { 669 mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE); 670 } 671 672 @Override 673 public void onClick(View v) { 674 tryVibrate(); 675 if (v.getId() == R.id.date_picker_year) { 676 setCurrentView(YEAR_VIEW); 677 } else if (v.getId() == R.id.date_picker_month_and_day_layout) { 678 setCurrentView(MONTH_AND_DAY_VIEW); 679 } 680 } 681 682 /** 683 * Class for managing state storing/restoring. 684 */ 685 private static class SavedState extends View.BaseSavedState { 686 687 private final int mSelectedYear; 688 private final int mSelectedMonth; 689 private final int mSelectedDay; 690 private final long mMinDate; 691 private final long mMaxDate; 692 private final int mCurrentView; 693 private final int mListPosition; 694 private final int mListPositionOffset; 695 696 /** 697 * Constructor called from {@link DatePicker#onSaveInstanceState()} 698 */ 699 private SavedState(Parcelable superState, int year, int month, int day, 700 long minDate, long maxDate, int currentView, int listPosition, 701 int listPositionOffset) { 702 super(superState); 703 mSelectedYear = year; 704 mSelectedMonth = month; 705 mSelectedDay = day; 706 mMinDate = minDate; 707 mMaxDate = maxDate; 708 mCurrentView = currentView; 709 mListPosition = listPosition; 710 mListPositionOffset = listPositionOffset; 711 } 712 713 /** 714 * Constructor called from {@link #CREATOR} 715 */ 716 private SavedState(Parcel in) { 717 super(in); 718 mSelectedYear = in.readInt(); 719 mSelectedMonth = in.readInt(); 720 mSelectedDay = in.readInt(); 721 mMinDate = in.readLong(); 722 mMaxDate = in.readLong(); 723 mCurrentView = in.readInt(); 724 mListPosition = in.readInt(); 725 mListPositionOffset = in.readInt(); 726 } 727 728 @Override 729 public void writeToParcel(Parcel dest, int flags) { 730 super.writeToParcel(dest, flags); 731 dest.writeInt(mSelectedYear); 732 dest.writeInt(mSelectedMonth); 733 dest.writeInt(mSelectedDay); 734 dest.writeLong(mMinDate); 735 dest.writeLong(mMaxDate); 736 dest.writeInt(mCurrentView); 737 dest.writeInt(mListPosition); 738 dest.writeInt(mListPositionOffset); 739 } 740 741 public int getSelectedDay() { 742 return mSelectedDay; 743 } 744 745 public int getSelectedMonth() { 746 return mSelectedMonth; 747 } 748 749 public int getSelectedYear() { 750 return mSelectedYear; 751 } 752 753 public long getMinDate() { 754 return mMinDate; 755 } 756 757 public long getMaxDate() { 758 return mMaxDate; 759 } 760 761 public int getCurrentView() { 762 return mCurrentView; 763 } 764 765 public int getListPosition() { 766 return mListPosition; 767 } 768 769 public int getListPositionOffset() { 770 return mListPositionOffset; 771 } 772 773 @SuppressWarnings("all") 774 // suppress unused and hiding 775 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { 776 777 public SavedState createFromParcel(Parcel in) { 778 return new SavedState(in); 779 } 780 781 public SavedState[] newArray(int size) { 782 return new SavedState[size]; 783 } 784 }; 785 } 786 } 787