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 org.mozilla.gecko.widget;
18 
19 import java.text.SimpleDateFormat;
20 import java.util.Arrays;
21 import java.util.Calendar;
22 import java.util.Locale;
23 
24 import org.mozilla.gecko.AppConstants;
25 import org.mozilla.gecko.AppConstants.Versions;
26 import org.mozilla.gecko.R;
27 
28 import android.content.Context;
29 import android.text.format.DateFormat;
30 import android.text.format.DateUtils;
31 import android.util.DisplayMetrics;
32 import android.util.Log;
33 import android.util.TypedValue;
34 import android.view.Display;
35 import android.view.LayoutInflater;
36 import android.view.WindowManager;
37 import android.view.accessibility.AccessibilityEvent;
38 import android.view.inputmethod.InputMethodManager;
39 import android.widget.CalendarView;
40 import android.widget.EditText;
41 import android.widget.FrameLayout;
42 import android.widget.LinearLayout;
43 import android.widget.NumberPicker;
44 
45 public class DateTimePicker extends FrameLayout {
46     private static final boolean DEBUG = true;
47     private static final String LOGTAG = "GeckoDateTimePicker";
48     private static final int DEFAULT_START_YEAR = 1;
49     private static final int DEFAULT_END_YEAR = 9999;
50     private static final char DATE_FORMAT_DAY = 'd';
51     private static final char DATE_FORMAT_MONTH = 'M';
52     private static final char DATE_FORMAT_YEAR = 'y';
53 
54     boolean mYearEnabled = true;
55     boolean mMonthEnabled = true;
56     boolean mWeekEnabled;
57     boolean mDayEnabled = true;
58     boolean mHourEnabled = true;
59     boolean mMinuteEnabled = true;
60     boolean mIs12HourMode;
61     private boolean mCalendarEnabled;
62 
63     // Size of the screen in inches;
64     private final int mScreenWidth;
65     private final int mScreenHeight;
66     private final OnValueChangeListener mOnChangeListener;
67     private final LinearLayout mPickers;
68     private final LinearLayout mDateSpinners;
69     private final LinearLayout mTimeSpinners;
70 
71     final NumberPicker mDaySpinner;
72     final NumberPicker mMonthSpinner;
73     final NumberPicker mWeekSpinner;
74     final NumberPicker mYearSpinner;
75     final NumberPicker mHourSpinner;
76     final NumberPicker mMinuteSpinner;
77     final NumberPicker mAMPMSpinner;
78     private final CalendarView mCalendar;
79     private final EditText mDaySpinnerInput;
80     private final EditText mMonthSpinnerInput;
81     private final EditText mWeekSpinnerInput;
82     private final EditText mYearSpinnerInput;
83     private final EditText mHourSpinnerInput;
84     private final EditText mMinuteSpinnerInput;
85     private final EditText mAMPMSpinnerInput;
86     private Locale mCurrentLocale;
87     private String[] mShortMonths;
88     private String[] mShortAMPMs;
89     private int mNumberOfMonths;
90 
91     Calendar mTempDate;
92     Calendar mCurrentDate;
93     private Calendar mMinDate;
94     private Calendar mMaxDate;
95     private final PickersState mState;
96 
97     public static enum PickersState { DATE, MONTH, WEEK, TIME, DATETIME };
98 
99     public class OnValueChangeListener implements NumberPicker.OnValueChangeListener {
100         @Override
onValueChange(NumberPicker picker, int oldVal, int newVal)101         public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
102             updateInputState();
103             mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
104             if (DEBUG) {
105                 Log.d(LOGTAG, "SDK version > 10, using new behavior");
106             }
107 
108             // The native date picker widget on these SDKs increments
109             // the next field when one field reaches the maximum.
110             if (picker == mDaySpinner && mDayEnabled) {
111                 int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
112                 int old = mTempDate.get(Calendar.DAY_OF_MONTH);
113                 setTempDate(Calendar.DAY_OF_MONTH, old, newVal, 1, maxDayOfMonth);
114             } else if (picker == mMonthSpinner && mMonthEnabled) {
115                 int old = mTempDate.get(Calendar.MONTH);
116                 setTempDate(Calendar.MONTH, old, newVal, Calendar.JANUARY, Calendar.DECEMBER);
117             } else if (picker == mWeekSpinner) {
118                 int old = mTempDate.get(Calendar.WEEK_OF_YEAR);
119                 int maxWeekOfYear = mTempDate.getActualMaximum(Calendar.WEEK_OF_YEAR);
120                 setTempDate(Calendar.WEEK_OF_YEAR, old, newVal, 0, maxWeekOfYear);
121             } else if (picker == mYearSpinner && mYearEnabled) {
122                 int month = mTempDate.get(Calendar.MONTH);
123                 mTempDate.set(Calendar.YEAR, newVal);
124                 // Changing the year shouldn't change the month. (in case of non-leap year a Feb 29)
125                 // change the day instead;
126                 if (month != mTempDate.get(Calendar.MONTH)) {
127                     mTempDate.set(Calendar.MONTH, month);
128                     mTempDate.set(Calendar.DAY_OF_MONTH,
129                     mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
130                 }
131             } else if (picker == mHourSpinner && mHourEnabled) {
132                 if (mIs12HourMode) {
133                     setTempDate(Calendar.HOUR, oldVal, newVal, 1, 12);
134                 } else {
135                     setTempDate(Calendar.HOUR_OF_DAY, oldVal, newVal, 0, 23);
136                 }
137             } else if (picker == mMinuteSpinner && mMinuteEnabled) {
138                 setTempDate(Calendar.MINUTE, oldVal, newVal, 0, 59);
139             } else if (picker == mAMPMSpinner && mHourEnabled) {
140                 mTempDate.set(Calendar.AM_PM, newVal);
141             } else {
142                 throw new IllegalArgumentException();
143             }
144             setDate(mTempDate);
145             if (mDayEnabled) {
146                 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
147             }
148             if (mWeekEnabled) {
149                 mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR));
150             }
151             updateCalendar();
152             updateSpinners();
153             notifyDateChanged();
154         }
155 
setTempDate(int field, int oldVal, int newVal, int min, int max)156         private void setTempDate(int field, int oldVal, int newVal, int min, int max) {
157             if (oldVal == max && newVal == min) {
158                 mTempDate.add(field, 1);
159             } else if (oldVal == min && newVal == max) {
160                 mTempDate.add(field, -1);
161             } else {
162                 mTempDate.add(field, newVal - oldVal);
163             }
164         }
165     }
166 
167     private static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = new NumberPicker.Formatter() {
168         final StringBuilder mBuilder = new StringBuilder();
169 
170         final java.util.Formatter mFmt = new java.util.Formatter(mBuilder, java.util.Locale.US);
171 
172         final Object[] mArgs = new Object[1];
173 
174         @Override
175         public String format(int value) {
176             mArgs[0] = value;
177             mBuilder.delete(0, mBuilder.length());
178             mFmt.format("%02d", mArgs);
179             return mFmt.toString();
180         }
181     };
182 
displayPickers()183     private void displayPickers() {
184         setWeekShown(false);
185         set12HourShown(mIs12HourMode);
186         if (mState == PickersState.DATETIME) {
187             return;
188         }
189 
190         setHourShown(false);
191         setMinuteShown(false);
192         if (mState == PickersState.WEEK) {
193             setDayShown(false);
194             setMonthShown(false);
195             setWeekShown(true);
196         } else if (mState == PickersState.MONTH) {
197             setDayShown(false);
198         }
199     }
200 
DateTimePicker(Context context)201     public DateTimePicker(Context context) {
202         this(context, "", "", PickersState.DATE, null, null);
203     }
204 
DateTimePicker(Context context, String dateFormat, String dateTimeValue, PickersState state, String minDateValue, String maxDateValue)205     public DateTimePicker(Context context, String dateFormat, String dateTimeValue, PickersState state, String minDateValue, String maxDateValue) {
206         super(context);
207 
208         setCurrentLocale(Locale.getDefault());
209 
210         mState = state;
211         LayoutInflater inflater = LayoutInflater.from(context);
212         inflater.inflate(R.layout.datetime_picker, this, true);
213 
214         mOnChangeListener = new OnValueChangeListener();
215 
216         mDateSpinners = (LinearLayout)findViewById(R.id.date_spinners);
217         mTimeSpinners = (LinearLayout)findViewById(R.id.time_spinners);
218         mPickers = (LinearLayout)findViewById(R.id.datetime_picker);
219 
220         // We will display differently according to the screen size width.
221         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
222         Display display = wm.getDefaultDisplay();
223         DisplayMetrics dm = new DisplayMetrics();
224         display.getMetrics(dm);
225         mScreenWidth = display.getWidth() / dm.densityDpi;
226         mScreenHeight = display.getHeight() / dm.densityDpi;
227 
228         if (DEBUG) {
229             Log.d(LOGTAG, "screen width: " + mScreenWidth + " screen height: " + mScreenHeight);
230         }
231 
232         // Set the min / max attribute.
233         try {
234             if (minDateValue != null && !minDateValue.equals("")) {
235                 mMinDate.setTime(new SimpleDateFormat(dateFormat).parse(minDateValue));
236             } else {
237                 mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
238             }
239         } catch (Exception ex) {
240             Log.e(LOGTAG, "Error parsing format sting: " + ex);
241             mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
242         }
243 
244         try {
245             if (maxDateValue != null && !maxDateValue.equals("")) {
246                 mMaxDate.setTime(new SimpleDateFormat(dateFormat).parse(maxDateValue));
247             } else {
248                 mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
249             }
250         } catch (Exception ex) {
251             Log.e(LOGTAG, "Error parsing format string: " + ex);
252             mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
253         }
254 
255         // Find the initial date from the constructor arguments.
256         try {
257             if (!dateTimeValue.equals("")) {
258                 mTempDate.setTime(new SimpleDateFormat(dateFormat).parse(dateTimeValue));
259             } else {
260                 mTempDate.setTimeInMillis(System.currentTimeMillis());
261             }
262         } catch (Exception ex) {
263             Log.e(LOGTAG, "Error parsing format string: " + ex);
264             mTempDate.setTimeInMillis(System.currentTimeMillis());
265         }
266 
267         if (mMaxDate.before(mMinDate)) {
268             // If the input date range is illogical/garbage, we should not restrict the input range (i.e. allow the
269             // user to select any date). If we try to make any assumptions based on the illogical min/max date we could
270             // potentially prevent the user from selecting dates that are in the developers intended range, so it's best
271             // to allow anything.
272             mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
273             mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
274         }
275 
276         // mTempDate will either be a site-supplied value, or today's date otherwise. CalendarView implementations can
277         // crash if they're supplied an invalid date (i.e. a date not in the specified range), hence we need to set
278         // a sensible default date here.
279         if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
280             mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
281         }
282 
283         // If we're displaying a date, the screen is wide enough
284         // (and if we're using an SDK where the calendar view exists)
285         // then display a calendar.
286         if (mState == PickersState.DATE || mState == PickersState.DATETIME) {
287             mCalendar = new CalendarView(context);
288             mCalendar.setVisibility(GONE);
289 
290             // Modify the time of mMaxDate and mMinDate to the end of the date and the beginning of the date. (Bug 1339884)
291             mMaxDate.set(Calendar.HOUR, 23);
292             mMaxDate.set(Calendar.MINUTE, 59);
293             mMaxDate.set(Calendar.SECOND, 59);
294             mMaxDate.set(Calendar.MILLISECOND, 999);
295             mMinDate.set(Calendar.HOUR, 0);
296             mMinDate.set(Calendar.MINUTE, 0);
297             mMinDate.set(Calendar.SECOND, 0);
298             mMinDate.set(Calendar.MILLISECOND, 0);
299 
300             mCalendar.setFocusable(true);
301             mCalendar.setFocusableInTouchMode(true);
302             mCalendar.setMaxDate(mMaxDate.getTimeInMillis());
303             mCalendar.setMinDate(mMinDate.getTimeInMillis());
304             mCalendar.setDate(mTempDate.getTimeInMillis(), false, false);
305 
306             mCalendar.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
307                 @Override
308                 public void onSelectedDayChange(
309                     CalendarView view, int year, int month, int monthDay) {
310                     mTempDate.set(year, month, monthDay);
311                     setDate(mTempDate);
312                     notifyDateChanged();
313                 }
314             });
315 
316             final int height;
317             if (Versions.preLollipopMR1) {
318                 // Older versions of CalendarView don't request any height, resulting in
319                 // the whole dialog not appearing unless we manually request height.
320                 height =  (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());;
321             } else {
322                 height = LayoutParams.WRAP_CONTENT;
323             }
324 
325             mPickers.addView(mCalendar, LayoutParams.MATCH_PARENT, height);
326 
327         } else {
328             // If the screen is more wide than high, we are displaying day and
329             // time spinners, and if there is no calendar displayed, we should
330             // display the fields in one row.
331             if (mScreenWidth > mScreenHeight && mState == PickersState.DATETIME) {
332                 mPickers.setOrientation(LinearLayout.HORIZONTAL);
333             }
334             mCalendar = null;
335         }
336 
337         // Initialize all spinners.
338         mDaySpinner = setupSpinner(R.id.day, 1,
339                                    mTempDate.get(Calendar.DAY_OF_MONTH));
340         mDaySpinner.setFormatter(TWO_DIGIT_FORMATTER);
341         mDaySpinnerInput = (EditText) mDaySpinner.getChildAt(1);
342 
343         mMonthSpinner = setupSpinner(R.id.month, 1,
344                                      mTempDate.get(Calendar.MONTH) + 1); // Month is 0-based
345         mMonthSpinner.setFormatter(TWO_DIGIT_FORMATTER);
346         mMonthSpinner.setDisplayedValues(mShortMonths);
347         mMonthSpinnerInput = (EditText) mMonthSpinner.getChildAt(1);
348 
349         mWeekSpinner = setupSpinner(R.id.week, 1,
350                                     mTempDate.get(Calendar.WEEK_OF_YEAR));
351         mWeekSpinner.setFormatter(TWO_DIGIT_FORMATTER);
352         mWeekSpinnerInput = (EditText) mWeekSpinner.getChildAt(1);
353 
354         mYearSpinner = setupSpinner(R.id.year, DEFAULT_START_YEAR,
355                                     DEFAULT_END_YEAR);
356         mYearSpinnerInput = (EditText) mYearSpinner.getChildAt(1);
357 
358         mAMPMSpinner = setupSpinner(R.id.ampm, 0, 1);
359         mAMPMSpinner.setFormatter(TWO_DIGIT_FORMATTER);
360 
361         if (mIs12HourMode) {
362             mHourSpinner = setupSpinner(R.id.hour, 1, 12);
363             mAMPMSpinnerInput = (EditText) mAMPMSpinner.getChildAt(1);
364             mAMPMSpinner.setDisplayedValues(mShortAMPMs);
365         } else {
366             mHourSpinner = setupSpinner(R.id.hour, 0, 23);
367             mAMPMSpinnerInput = null;
368         }
369 
370         mHourSpinner.setFormatter(TWO_DIGIT_FORMATTER);
371         mHourSpinnerInput = (EditText) mHourSpinner.getChildAt(1);
372 
373         mMinuteSpinner = setupSpinner(R.id.minute, 0, 59);
374         mMinuteSpinner.setFormatter(TWO_DIGIT_FORMATTER);
375         mMinuteSpinnerInput = (EditText) mMinuteSpinner.getChildAt(1);
376 
377         // The order in which the spinners are displayed are locale-dependent
378         reorderDateSpinners();
379 
380         // Set the date to the initial date. Since this date can come from the user,
381         // it can fire an exception (out-of-bound date)
382         try {
383           updateDate(mTempDate);
384         } catch (Exception ex) {
385         }
386 
387         // Display only the pickers needed for the current state.
388         displayPickers();
389     }
390 
setupSpinner(int id, int min, int max)391     public NumberPicker setupSpinner(int id, int min, int max) {
392         NumberPicker mSpinner = (NumberPicker) findViewById(id);
393         mSpinner.setMinValue(min);
394         mSpinner.setMaxValue(max);
395         mSpinner.setOnValueChangedListener(mOnChangeListener);
396         mSpinner.setOnLongPressUpdateInterval(100);
397         return mSpinner;
398     }
399 
getTimeInMillis()400     public long getTimeInMillis() {
401         return mCurrentDate.getTimeInMillis();
402     }
403 
reorderDateSpinners()404     private void reorderDateSpinners() {
405         mDateSpinners.removeAllViews();
406         char[] order = DateFormat.getDateFormatOrder(getContext());
407         final int spinnerCount = order.length;
408 
409         for (int i = 0; i < spinnerCount; i++) {
410             switch (order[i]) {
411                 case DATE_FORMAT_DAY:
412                     mDateSpinners.addView(mDaySpinner);
413                     break;
414                 case DATE_FORMAT_MONTH:
415                     mDateSpinners.addView(mMonthSpinner);
416                     break;
417                 case DATE_FORMAT_YEAR:
418                     mDateSpinners.addView(mYearSpinner);
419                     break;
420                 default:
421                     throw new IllegalArgumentException();
422             }
423         }
424 
425         mDateSpinners.addView(mWeekSpinner);
426     }
427 
setDate(Calendar calendar)428     void setDate(Calendar calendar) {
429         mCurrentDate = mTempDate;
430         if (mCurrentDate.before(mMinDate)) {
431             mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
432         } else if (mCurrentDate.after(mMaxDate)) {
433             mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
434         }
435     }
436 
updateInputState()437     void updateInputState() {
438         InputMethodManager inputMethodManager = (InputMethodManager)
439           getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
440         if (mYearEnabled && inputMethodManager.isActive(mYearSpinnerInput)) {
441             mYearSpinnerInput.clearFocus();
442             inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
443         } else if (mMonthEnabled && inputMethodManager.isActive(mMonthSpinnerInput)) {
444             mMonthSpinnerInput.clearFocus();
445             inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
446         } else if (mDayEnabled && inputMethodManager.isActive(mDaySpinnerInput)) {
447             mDaySpinnerInput.clearFocus();
448             inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
449         } else if (mHourEnabled && inputMethodManager.isActive(mHourSpinnerInput)) {
450             mHourSpinnerInput.clearFocus();
451             inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
452         } else if (mMinuteEnabled && inputMethodManager.isActive(mMinuteSpinnerInput)) {
453             mMinuteSpinnerInput.clearFocus();
454             inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
455         }
456     }
457 
updateSpinners()458     void updateSpinners() {
459         if (mDayEnabled) {
460             if (mCurrentDate.equals(mMinDate)) {
461                 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
462                 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
463             } else if (mCurrentDate.equals(mMaxDate)) {
464                 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
465                 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
466             } else {
467                 mDaySpinner.setMinValue(1);
468                 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
469             }
470             mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
471         }
472 
473         if (mWeekEnabled) {
474             mWeekSpinner.setMinValue(1);
475             mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR));
476             mWeekSpinner.setValue(mCurrentDate.get(Calendar.WEEK_OF_YEAR));
477         }
478 
479         if (mMonthEnabled) {
480             mMonthSpinner.setDisplayedValues(null);
481             if (mCurrentDate.equals(mMinDate)) {
482                 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
483                 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
484             } else if (mCurrentDate.equals(mMaxDate)) {
485                 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
486                 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
487             } else {
488                 mMonthSpinner.setMinValue(Calendar.JANUARY);
489                 mMonthSpinner.setMaxValue(Calendar.DECEMBER);
490             }
491 
492             String[] displayedValues = Arrays.copyOfRange(mShortMonths,
493                     mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
494             mMonthSpinner.setDisplayedValues(displayedValues);
495             mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
496         }
497 
498         if (mYearEnabled) {
499             mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
500             mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
501             mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
502         }
503 
504         if (mHourEnabled) {
505             if (mIs12HourMode) {
506                 mHourSpinner.setValue(mCurrentDate.get(Calendar.HOUR));
507                 mAMPMSpinner.setValue(mCurrentDate.get(Calendar.AM_PM));
508                 mAMPMSpinner.setDisplayedValues(mShortAMPMs);
509             } else {
510                 mHourSpinner.setValue(mCurrentDate.get(Calendar.HOUR_OF_DAY));
511             }
512         }
513         if (mMinuteEnabled) {
514             mMinuteSpinner.setValue(mCurrentDate.get(Calendar.MINUTE));
515         }
516     }
517 
updateCalendar()518     void updateCalendar() {
519         if (mCalendarEnabled) {
520             mCalendar.setDate(mCurrentDate.getTimeInMillis(), false, false);
521         }
522     }
523 
notifyDateChanged()524     void notifyDateChanged() {
525         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
526     }
527 
toggleCalendar(boolean shown)528     public void toggleCalendar(boolean shown) {
529         if ((mState != PickersState.DATE && mState != PickersState.DATETIME)) {
530             return;
531         }
532 
533         if (shown) {
534             mCalendarEnabled = true;
535             mCalendar.setVisibility(VISIBLE);
536             setYearShown(false);
537             setWeekShown(false);
538             setMonthShown(false);
539             setDayShown(false);
540         } else {
541             mCalendar.setVisibility(GONE);
542             setYearShown(true);
543             setMonthShown(true);
544             setDayShown(true);
545             mPickers.setOrientation(LinearLayout.HORIZONTAL);
546             mCalendarEnabled = false;
547         }
548     }
549 
setYearShown(boolean shown)550     private void setYearShown(boolean shown) {
551         if (shown) {
552             toggleCalendar(false);
553             mYearSpinner.setVisibility(VISIBLE);
554             mYearEnabled = true;
555         } else {
556             mYearSpinner.setVisibility(GONE);
557             mYearEnabled = false;
558         }
559     }
560 
setWeekShown(boolean shown)561     private void setWeekShown(boolean shown) {
562         if (shown) {
563             toggleCalendar(false);
564             mWeekSpinner.setVisibility(VISIBLE);
565             mWeekEnabled = true;
566         } else {
567             mWeekSpinner.setVisibility(GONE);
568             mWeekEnabled = false;
569         }
570     }
571 
setMonthShown(boolean shown)572     private void setMonthShown(boolean shown) {
573         if (shown) {
574             toggleCalendar(false);
575             mMonthSpinner.setVisibility(VISIBLE);
576             mMonthEnabled = true;
577         } else {
578             mMonthSpinner.setVisibility(GONE);
579             mMonthEnabled = false;
580         }
581     }
582 
setDayShown(boolean shown)583     private void setDayShown(boolean shown) {
584         if (shown) {
585             toggleCalendar(false);
586             mDaySpinner.setVisibility(VISIBLE);
587             mDayEnabled = true;
588         } else {
589             mDaySpinner.setVisibility(GONE);
590             mDayEnabled = false;
591         }
592     }
593 
set12HourShown(boolean shown)594     private void set12HourShown(boolean shown) {
595         if (shown) {
596             mAMPMSpinner.setVisibility(VISIBLE);
597         } else {
598             mAMPMSpinner.setVisibility(GONE);
599         }
600     }
601 
setHourShown(boolean shown)602     private void setHourShown(boolean shown) {
603         if (shown) {
604             mHourSpinner.setVisibility(VISIBLE);
605             mHourEnabled = true;
606         } else {
607             mHourSpinner.setVisibility(GONE);
608             mAMPMSpinner.setVisibility(GONE);
609             mTimeSpinners.setVisibility(GONE);
610             mHourEnabled = false;
611         }
612     }
613 
setMinuteShown(boolean shown)614     private void setMinuteShown(boolean shown) {
615         if (shown) {
616             mMinuteSpinner.setVisibility(VISIBLE);
617             mTimeSpinners.findViewById(R.id.mincolon).setVisibility(VISIBLE);
618             mMinuteEnabled = true;
619         } else {
620             mMinuteSpinner.setVisibility(GONE);
621             mTimeSpinners.findViewById(R.id.mincolon).setVisibility(GONE);
622             mMinuteEnabled = false;
623         }
624     }
625 
setCurrentLocale(Locale locale)626     private void setCurrentLocale(Locale locale) {
627         if (locale.equals(mCurrentLocale)) {
628             return;
629         }
630 
631         mCurrentLocale = locale;
632         mIs12HourMode = !DateFormat.is24HourFormat(getContext());
633         mTempDate = getCalendarForLocale(mTempDate, locale);
634         mMinDate = getCalendarForLocale(mMinDate, locale);
635         mMaxDate = getCalendarForLocale(mMaxDate, locale);
636         mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
637 
638         mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
639 
640         mShortAMPMs = new String[2];
641         mShortAMPMs[0] = DateUtils.getAMPMString(Calendar.AM);
642         mShortAMPMs[1] = DateUtils.getAMPMString(Calendar.PM);
643 
644         mShortMonths = new String[mNumberOfMonths];
645         for (int i = 0; i < mNumberOfMonths; i++) {
646             mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
647                     DateUtils.LENGTH_MEDIUM);
648         }
649     }
650 
getCalendarForLocale(Calendar oldCalendar, Locale locale)651     private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
652         if (oldCalendar == null) {
653             return Calendar.getInstance(locale);
654         }
655 
656         final long currentTimeMillis = oldCalendar.getTimeInMillis();
657         Calendar newCalendar = Calendar.getInstance(locale);
658         newCalendar.setTimeInMillis(currentTimeMillis);
659         return newCalendar;
660     }
661 
updateDate(Calendar calendar)662     public void updateDate(Calendar calendar) {
663         if (mCurrentDate.equals(calendar)) {
664             return;
665         }
666         mCurrentDate.setTimeInMillis(calendar.getTimeInMillis());
667         if (mCurrentDate.before(mMinDate)) {
668             mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
669         } else if (mCurrentDate.after(mMaxDate)) {
670             mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
671         }
672         updateSpinners();
673         notifyDateChanged();
674     }
675 }
676