• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.Nullable;
20 import android.content.Context;
21 import android.content.res.ColorStateList;
22 import android.content.res.Configuration;
23 import android.content.res.Resources;
24 import android.content.res.TypedArray;
25 import android.icu.text.DateFormat;
26 import android.icu.text.DisplayContext;
27 import android.icu.util.Calendar;
28 import android.os.Parcelable;
29 import android.util.AttributeSet;
30 import android.util.StateSet;
31 import android.view.HapticFeedbackConstants;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.View.OnClickListener;
35 import android.view.ViewGroup;
36 import android.view.accessibility.AccessibilityEvent;
37 import android.view.accessibility.AccessibilityNodeInfo;
38 import android.widget.DayPickerView.OnDaySelectedListener;
39 import android.widget.YearPickerView.OnYearSelectedListener;
40 
41 import com.android.internal.R;
42 
43 import java.util.Locale;
44 
45 /**
46  * A delegate for picking up a date (day / month / year).
47  */
48 class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
49     private static final int USE_LOCALE = 0;
50 
51     private static final int UNINITIALIZED = -1;
52     private static final int VIEW_MONTH_DAY = 0;
53     private static final int VIEW_YEAR = 1;
54 
55     private static final int DEFAULT_START_YEAR = 1900;
56     private static final int DEFAULT_END_YEAR = 2100;
57 
58     private static final int ANIMATION_DURATION = 300;
59 
60     private static final int[] ATTRS_TEXT_COLOR = new int[] {
61             com.android.internal.R.attr.textColor};
62     private static final int[] ATTRS_DISABLED_ALPHA = new int[] {
63             com.android.internal.R.attr.disabledAlpha};
64 
65     private DateFormat mYearFormat;
66     private DateFormat mMonthDayFormat;
67 
68     // Top-level container.
69     private ViewGroup mContainer;
70 
71     // Header views.
72     private TextView mHeaderYear;
73     private TextView mHeaderMonthDay;
74 
75     // Picker views.
76     private ViewAnimator mAnimator;
77     private DayPickerView mDayPickerView;
78     private YearPickerView mYearPickerView;
79 
80     private int mCurrentView = UNINITIALIZED;
81 
82     private final Calendar mTempDate;
83     private final Calendar mMinDate;
84     private final Calendar mMaxDate;
85 
86     private int mFirstDayOfWeek = USE_LOCALE;
87 
DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)88     public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs,
89             int defStyleAttr, int defStyleRes) {
90         super(delegator, context);
91 
92         final Locale locale = mCurrentLocale;
93         mCurrentDate = Calendar.getInstance(locale);
94         mTempDate = Calendar.getInstance(locale);
95         mMinDate = Calendar.getInstance(locale);
96         mMaxDate = Calendar.getInstance(locale);
97 
98         mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
99         mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
100 
101         final Resources res = mDelegator.getResources();
102         final TypedArray a = mContext.obtainStyledAttributes(attrs,
103                 R.styleable.DatePicker, defStyleAttr, defStyleRes);
104         final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
105                 Context.LAYOUT_INFLATER_SERVICE);
106         final int layoutResourceId = a.getResourceId(
107                 R.styleable.DatePicker_internalLayout, R.layout.date_picker_material);
108 
109         // Set up and attach container.
110         mContainer = (ViewGroup) inflater.inflate(layoutResourceId, mDelegator, false);
111         mContainer.setSaveFromParentEnabled(false);
112         mDelegator.addView(mContainer);
113 
114         // Set up header views.
115         final ViewGroup header = mContainer.findViewById(R.id.date_picker_header);
116         mHeaderYear = header.findViewById(R.id.date_picker_header_year);
117         mHeaderYear.setOnClickListener(mOnHeaderClickListener);
118         mHeaderYear.setAccessibilityDelegate(
119                 new ClickActionDelegate(context, R.string.select_year));
120         mHeaderYear.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
121 
122         mHeaderMonthDay = header.findViewById(R.id.date_picker_header_date);
123         mHeaderMonthDay.setOnClickListener(mOnHeaderClickListener);
124         mHeaderMonthDay.setAccessibilityDelegate(
125                 new ClickActionDelegate(context, R.string.select_day));
126         mHeaderMonthDay.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
127 
128         // For the sake of backwards compatibility, attempt to extract the text
129         // color from the header month text appearance. If it's set, we'll let
130         // that override the "real" header text color.
131         ColorStateList headerTextColor = null;
132 
133         @SuppressWarnings("deprecation")
134         final int monthHeaderTextAppearance = a.getResourceId(
135                 R.styleable.DatePicker_headerMonthTextAppearance, 0);
136         if (monthHeaderTextAppearance != 0) {
137             final TypedArray textAppearance = mContext.obtainStyledAttributes(null,
138                     ATTRS_TEXT_COLOR, 0, monthHeaderTextAppearance);
139             final ColorStateList legacyHeaderTextColor = textAppearance.getColorStateList(0);
140             headerTextColor = applyLegacyColorFixes(legacyHeaderTextColor);
141             textAppearance.recycle();
142         }
143 
144         if (headerTextColor == null) {
145             headerTextColor = a.getColorStateList(R.styleable.DatePicker_headerTextColor);
146         }
147 
148         if (headerTextColor != null) {
149             mHeaderYear.setTextColor(headerTextColor);
150             mHeaderMonthDay.setTextColor(headerTextColor);
151         }
152 
153         // Set up header background, if available.
154         if (a.hasValueOrEmpty(R.styleable.DatePicker_headerBackground)) {
155             header.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground));
156         }
157 
158         a.recycle();
159 
160         // Set up picker container.
161         mAnimator = mContainer.findViewById(R.id.animator);
162 
163         // Set up day picker view.
164         mDayPickerView = mAnimator.findViewById(R.id.date_picker_day_picker);
165         mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek);
166         mDayPickerView.setMinDate(mMinDate.getTimeInMillis());
167         mDayPickerView.setMaxDate(mMaxDate.getTimeInMillis());
168         mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
169         mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener);
170 
171         // Set up year picker view.
172         mYearPickerView = mAnimator.findViewById(R.id.date_picker_year_picker);
173         mYearPickerView.setRange(mMinDate, mMaxDate);
174         mYearPickerView.setYear(mCurrentDate.get(Calendar.YEAR));
175         mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener);
176 
177         // Initialize for current locale. This also initializes the date, so no
178         // need to call onDateChanged.
179         onLocaleChanged(mCurrentLocale);
180 
181         setCurrentView(VIEW_MONTH_DAY);
182     }
183 
184     /**
185      * The legacy text color might have been poorly defined. Ensures that it
186      * has an appropriate activated state, using the selected state if one
187      * exists or modifying the default text color otherwise.
188      *
189      * @param color a legacy text color, or {@code null}
190      * @return a color state list with an appropriate activated state, or
191      *         {@code null} if a valid activated state could not be generated
192      */
193     @Nullable
applyLegacyColorFixes(@ullable ColorStateList color)194     private ColorStateList applyLegacyColorFixes(@Nullable ColorStateList color) {
195         if (color == null || color.hasState(R.attr.state_activated)) {
196             return color;
197         }
198 
199         final int activatedColor;
200         final int defaultColor;
201         if (color.hasState(R.attr.state_selected)) {
202             activatedColor = color.getColorForState(StateSet.get(
203                     StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_SELECTED), 0);
204             defaultColor = color.getColorForState(StateSet.get(
205                     StateSet.VIEW_STATE_ENABLED), 0);
206         } else {
207             activatedColor = color.getDefaultColor();
208 
209             // Generate a non-activated color using the disabled alpha.
210             final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA);
211             final float disabledAlpha = ta.getFloat(0, 0.30f);
212             ta.recycle();
213             defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha);
214         }
215 
216         if (activatedColor == 0 || defaultColor == 0) {
217             // We somehow failed to obtain the colors.
218             return null;
219         }
220 
221         final int[][] stateSet = new int[][] {{ R.attr.state_activated }, {}};
222         final int[] colors = new int[] { activatedColor, defaultColor };
223         return new ColorStateList(stateSet, colors);
224     }
225 
multiplyAlphaComponent(int color, float alphaMod)226     private int multiplyAlphaComponent(int color, float alphaMod) {
227         final int srcRgb = color & 0xFFFFFF;
228         final int srcAlpha = (color >> 24) & 0xFF;
229         final int dstAlpha = (int) (srcAlpha * alphaMod + 0.5f);
230         return srcRgb | (dstAlpha << 24);
231     }
232 
233     private static class ClickActionDelegate extends View.AccessibilityDelegate {
234         private final AccessibilityNodeInfo.AccessibilityAction mClickAction;
235 
ClickActionDelegate(Context context, int resId)236         ClickActionDelegate(Context context, int resId) {
237             mClickAction = new AccessibilityNodeInfo.AccessibilityAction(
238                     AccessibilityNodeInfo.ACTION_CLICK, context.getString(resId));
239         }
240 
241         @Override
onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)242         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
243             super.onInitializeAccessibilityNodeInfo(host, info);
244 
245             info.addAction(mClickAction);
246         }
247     }
248 
249     /**
250      * Listener called when the user selects a day in the day picker view.
251      */
252     private final OnDaySelectedListener mOnDaySelectedListener = new OnDaySelectedListener() {
253         @Override
254         public void onDaySelected(DayPickerView view, Calendar day) {
255             mCurrentDate.setTimeInMillis(day.getTimeInMillis());
256             onDateChanged(true, true);
257         }
258     };
259 
260     /**
261      * Listener called when the user selects a year in the year picker view.
262      */
263     private final OnYearSelectedListener mOnYearSelectedListener = new OnYearSelectedListener() {
264         @Override
265         public void onYearChanged(YearPickerView view, int year) {
266             // If the newly selected month / year does not contain the
267             // currently selected day number, change the selected day number
268             // to the last day of the selected month or year.
269             // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
270             // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
271             final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
272             final int month = mCurrentDate.get(Calendar.MONTH);
273             final int daysInMonth = getDaysInMonth(month, year);
274             if (day > daysInMonth) {
275                 mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth);
276             }
277 
278             mCurrentDate.set(Calendar.YEAR, year);
279             if (mCurrentDate.compareTo(mMinDate) < 0) {
280                 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
281             } else if (mCurrentDate.compareTo(mMaxDate) > 0) {
282                 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
283             }
284             onDateChanged(true, true);
285 
286             // Automatically switch to day picker.
287             setCurrentView(VIEW_MONTH_DAY);
288 
289             // Switch focus back to the year text.
290             mHeaderYear.requestFocus();
291         }
292     };
293 
294     /**
295      * Listener called when the user clicks on a header item.
296      */
297     private final OnClickListener mOnHeaderClickListener = v -> {
298         tryVibrate();
299 
300         switch (v.getId()) {
301             case R.id.date_picker_header_year:
302                 setCurrentView(VIEW_YEAR);
303                 break;
304             case R.id.date_picker_header_date:
305                 setCurrentView(VIEW_MONTH_DAY);
306                 break;
307         }
308     };
309 
310     @Override
onLocaleChanged(Locale locale)311     protected void onLocaleChanged(Locale locale) {
312         final TextView headerYear = mHeaderYear;
313         if (headerYear == null) {
314             // Abort, we haven't initialized yet. This method will get called
315             // again later after everything has been set up.
316             return;
317         }
318 
319         // Update the date formatter.
320         mMonthDayFormat = DateFormat.getInstanceForSkeleton("EMMMd", locale);
321         // The use of CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE instead of
322         // CAPITALIZATION_FOR_STANDALONE is to address
323         // https://unicode-org.atlassian.net/browse/ICU-21631
324         // TODO(b/229287642): Switch back to CAPITALIZATION_FOR_STANDALONE
325         mMonthDayFormat.setContext(DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE);
326         mYearFormat = DateFormat.getInstanceForSkeleton("y", locale);
327 
328         // Update the header text.
329         onCurrentDateChanged();
330     }
331 
onCurrentDateChanged()332     private void onCurrentDateChanged() {
333         if (mHeaderYear == null) {
334             // Abort, we haven't initialized yet. This method will get called
335             // again later after everything has been set up.
336             return;
337         }
338 
339         final String year = mYearFormat.format(mCurrentDate.getTime());
340         mHeaderYear.setText(year);
341 
342         final String monthDay = mMonthDayFormat.format(mCurrentDate.getTime());
343         mHeaderMonthDay.setText(monthDay);
344     }
345 
setCurrentView(final int viewIndex)346     private void setCurrentView(final int viewIndex) {
347         switch (viewIndex) {
348             case VIEW_MONTH_DAY:
349                 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
350 
351                 if (mCurrentView != viewIndex) {
352                     mHeaderMonthDay.setActivated(true);
353                     mHeaderYear.setActivated(false);
354                     mAnimator.setDisplayedChild(VIEW_MONTH_DAY);
355                     mCurrentView = viewIndex;
356                 }
357                 break;
358             case VIEW_YEAR:
359                 final int year = mCurrentDate.get(Calendar.YEAR);
360                 mYearPickerView.setYear(year);
361                 mYearPickerView.post(() -> {
362                     mYearPickerView.requestFocus();
363                     final View selected = mYearPickerView.getSelectedView();
364                     if (selected != null) {
365                         selected.requestFocus();
366                     }
367                 });
368 
369                 if (mCurrentView != viewIndex) {
370                     mHeaderMonthDay.setActivated(false);
371                     mHeaderYear.setActivated(true);
372                     mAnimator.setDisplayedChild(VIEW_YEAR);
373                     mCurrentView = viewIndex;
374                 }
375 
376                 break;
377         }
378     }
379 
380     @Override
init(int year, int month, int dayOfMonth, DatePicker.OnDateChangedListener callBack)381     public void init(int year, int month, int dayOfMonth,
382             DatePicker.OnDateChangedListener callBack) {
383         setDate(year, month, dayOfMonth);
384         onDateChanged(false, false);
385 
386         mOnDateChangedListener = callBack;
387     }
388 
389     @Override
updateDate(int year, int month, int dayOfMonth)390     public void updateDate(int year, int month, int dayOfMonth) {
391         setDate(year, month, dayOfMonth);
392         onDateChanged(false, true);
393     }
394 
setDate(int year, int month, int dayOfMonth)395     private void setDate(int year, int month, int dayOfMonth) {
396         mCurrentDate.set(Calendar.YEAR, year);
397         mCurrentDate.set(Calendar.MONTH, month);
398         mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
399         resetAutofilledValue();
400     }
401 
onDateChanged(boolean fromUser, boolean callbackToClient)402     private void onDateChanged(boolean fromUser, boolean callbackToClient) {
403         final int year = mCurrentDate.get(Calendar.YEAR);
404 
405         if (callbackToClient
406                 && (mOnDateChangedListener != null || mAutoFillChangeListener != null)) {
407             final int monthOfYear = mCurrentDate.get(Calendar.MONTH);
408             final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH);
409             if (mOnDateChangedListener != null) {
410                 mOnDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
411             }
412             if (mAutoFillChangeListener != null) {
413                 mAutoFillChangeListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
414             }
415         }
416 
417         mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
418         mYearPickerView.setYear(year);
419 
420         onCurrentDateChanged();
421 
422         if (fromUser) {
423             tryVibrate();
424         }
425     }
426 
427     @Override
getYear()428     public int getYear() {
429         return mCurrentDate.get(Calendar.YEAR);
430     }
431 
432     @Override
getMonth()433     public int getMonth() {
434         return mCurrentDate.get(Calendar.MONTH);
435     }
436 
437     @Override
getDayOfMonth()438     public int getDayOfMonth() {
439         return mCurrentDate.get(Calendar.DAY_OF_MONTH);
440     }
441 
442     @Override
setMinDate(long minDate)443     public void setMinDate(long minDate) {
444         mTempDate.setTimeInMillis(minDate);
445         if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
446                 && mTempDate.get(Calendar.DAY_OF_YEAR) == mMinDate.get(Calendar.DAY_OF_YEAR)) {
447             // Same day, no-op.
448             return;
449         }
450         if (mCurrentDate.before(mTempDate)) {
451             mCurrentDate.setTimeInMillis(minDate);
452             onDateChanged(false, true);
453         }
454         mMinDate.setTimeInMillis(minDate);
455         mDayPickerView.setMinDate(minDate);
456         mYearPickerView.setRange(mMinDate, mMaxDate);
457     }
458 
459     @Override
getMinDate()460     public Calendar getMinDate() {
461         return mMinDate;
462     }
463 
464     @Override
setMaxDate(long maxDate)465     public void setMaxDate(long maxDate) {
466         mTempDate.setTimeInMillis(maxDate);
467         if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
468                 && mTempDate.get(Calendar.DAY_OF_YEAR) == mMaxDate.get(Calendar.DAY_OF_YEAR)) {
469             // Same day, no-op.
470             return;
471         }
472         if (mCurrentDate.after(mTempDate)) {
473             mCurrentDate.setTimeInMillis(maxDate);
474             onDateChanged(false, true);
475         }
476         mMaxDate.setTimeInMillis(maxDate);
477         mDayPickerView.setMaxDate(maxDate);
478         mYearPickerView.setRange(mMinDate, mMaxDate);
479     }
480 
481     @Override
getMaxDate()482     public Calendar getMaxDate() {
483         return mMaxDate;
484     }
485 
486     @Override
setFirstDayOfWeek(int firstDayOfWeek)487     public void setFirstDayOfWeek(int firstDayOfWeek) {
488         mFirstDayOfWeek = firstDayOfWeek;
489 
490         mDayPickerView.setFirstDayOfWeek(firstDayOfWeek);
491     }
492 
493     @Override
getFirstDayOfWeek()494     public int getFirstDayOfWeek() {
495         if (mFirstDayOfWeek != USE_LOCALE) {
496             return mFirstDayOfWeek;
497         }
498         return mCurrentDate.getFirstDayOfWeek();
499     }
500 
501     @Override
setEnabled(boolean enabled)502     public void setEnabled(boolean enabled) {
503         mContainer.setEnabled(enabled);
504         mDayPickerView.setEnabled(enabled);
505         mYearPickerView.setEnabled(enabled);
506         mHeaderYear.setEnabled(enabled);
507         mHeaderMonthDay.setEnabled(enabled);
508     }
509 
510     @Override
isEnabled()511     public boolean isEnabled() {
512         return mContainer.isEnabled();
513     }
514 
515     @Override
getCalendarView()516     public CalendarView getCalendarView() {
517         throw new UnsupportedOperationException("Not supported by calendar-mode DatePicker");
518     }
519 
520     @Override
setCalendarViewShown(boolean shown)521     public void setCalendarViewShown(boolean shown) {
522         // No-op for compatibility with the old DatePicker.
523     }
524 
525     @Override
getCalendarViewShown()526     public boolean getCalendarViewShown() {
527         return false;
528     }
529 
530     @Override
setSpinnersShown(boolean shown)531     public void setSpinnersShown(boolean shown) {
532         // No-op for compatibility with the old DatePicker.
533     }
534 
535     @Override
getSpinnersShown()536     public boolean getSpinnersShown() {
537         return false;
538     }
539 
540     @Override
onConfigurationChanged(Configuration newConfig)541     public void onConfigurationChanged(Configuration newConfig) {
542         setCurrentLocale(newConfig.locale);
543     }
544 
545     @Override
onSaveInstanceState(Parcelable superState)546     public Parcelable onSaveInstanceState(Parcelable superState) {
547         final int year = mCurrentDate.get(Calendar.YEAR);
548         final int month = mCurrentDate.get(Calendar.MONTH);
549         final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
550 
551         int listPosition = -1;
552         int listPositionOffset = -1;
553 
554         if (mCurrentView == VIEW_MONTH_DAY) {
555             listPosition = mDayPickerView.getMostVisiblePosition();
556         } else if (mCurrentView == VIEW_YEAR) {
557             listPosition = mYearPickerView.getFirstVisiblePosition();
558             listPositionOffset = mYearPickerView.getFirstPositionOffset();
559         }
560 
561         return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
562                 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
563     }
564 
565     @Override
onRestoreInstanceState(Parcelable state)566     public void onRestoreInstanceState(Parcelable state) {
567         if (state instanceof SavedState) {
568             final SavedState ss = (SavedState) state;
569 
570             // TODO: Move instance state into DayPickerView, YearPickerView.
571             mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay());
572             mMinDate.setTimeInMillis(ss.getMinDate());
573             mMaxDate.setTimeInMillis(ss.getMaxDate());
574 
575             onCurrentDateChanged();
576 
577             final int currentView = ss.getCurrentView();
578             setCurrentView(currentView);
579 
580             final int listPosition = ss.getListPosition();
581             if (listPosition != -1) {
582                 if (currentView == VIEW_MONTH_DAY) {
583                     mDayPickerView.setPosition(listPosition);
584                 } else if (currentView == VIEW_YEAR) {
585                     final int listPositionOffset = ss.getListPositionOffset();
586                     mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset);
587                 }
588             }
589         }
590     }
591 
592     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)593     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
594         onPopulateAccessibilityEvent(event);
595         return true;
596     }
597 
getAccessibilityClassName()598     public CharSequence getAccessibilityClassName() {
599         return DatePicker.class.getName();
600     }
601 
getDaysInMonth(int month, int year)602     private static int getDaysInMonth(int month, int year) {
603         switch (month) {
604             case Calendar.JANUARY:
605             case Calendar.MARCH:
606             case Calendar.MAY:
607             case Calendar.JULY:
608             case Calendar.AUGUST:
609             case Calendar.OCTOBER:
610             case Calendar.DECEMBER:
611                 return 31;
612             case Calendar.APRIL:
613             case Calendar.JUNE:
614             case Calendar.SEPTEMBER:
615             case Calendar.NOVEMBER:
616                 return 30;
617             case Calendar.FEBRUARY:
618                 return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 29 : 28;
619             default:
620                 throw new IllegalArgumentException("Invalid Month");
621         }
622     }
623 
tryVibrate()624     private void tryVibrate() {
625         mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
626     }
627 }
628