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