blob: 265dbcd3effe1c4b77c78b4f64603d56bbd984bb [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.widget;
18
19import android.annotation.Widget;
20import android.content.Context;
Svetoslav Ganovf5926962011-07-12 12:26:20 -070021import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.res.TypedArray;
23import android.os.Parcel;
24import android.os.Parcelable;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080025import android.text.TextUtils;
Hyejin Kime9a74a12013-03-20 18:14:00 +090026import android.text.InputType;
Kenny Rootdddda8d2010-11-15 14:38:51 -080027import android.text.format.DateUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.util.AttributeSet;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080029import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.util.SparseArray;
31import android.view.LayoutInflater;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070032import android.view.View;
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -080033import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080034import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav Ganova53efe92011-09-08 18:08:36 -070035import android.view.inputmethod.EditorInfo;
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -070036import android.view.inputmethod.InputMethodManager;
Svetoslav Ganovcedc4462011-01-19 19:25:46 -080037import android.widget.NumberPicker.OnValueChangeListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038
Svetoslav Ganovf5926962011-07-12 12:26:20 -070039import com.android.internal.R;
40
Elliott Hughes949e9df2013-04-30 13:41:06 -070041import java.text.DateFormatSymbols;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080042import java.text.ParseException;
Eric Fischer03a80172009-07-23 18:32:42 -070043import java.text.SimpleDateFormat;
Svetoslav Ganov156f2092011-01-24 02:13:36 -080044import java.util.Arrays;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import java.util.Calendar;
Kenny Rootdddda8d2010-11-15 14:38:51 -080046import java.util.Locale;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080047import java.util.TimeZone;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048
Elliott Hughes659f1452013-04-30 11:11:53 -070049import libcore.icu.ICU;
50
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051/**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080052 * This class is a widget for selecting a date. The date can be selected by a
53 * year, month, and day spinners or a {@link CalendarView}. The set of spinners
54 * and the calendar view are automatically synchronized. The client can
55 * customize whether only the spinners, or only the calendar view, or both to be
56 * displayed. Also the minimal and maximal date from which dates to be selected
57 * can be customized.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080058 * <p>
Scott Main4c359b72012-07-24 15:51:27 -070059 * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
60 * guide.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080061 * </p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080062 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 * For a dialog using this view, see {@link android.app.DatePickerDialog}.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080064 * </p>
65 *
66 * @attr ref android.R.styleable#DatePicker_startYear
67 * @attr ref android.R.styleable#DatePicker_endYear
68 * @attr ref android.R.styleable#DatePicker_maxDate
69 * @attr ref android.R.styleable#DatePicker_minDate
70 * @attr ref android.R.styleable#DatePicker_spinnersShown
71 * @attr ref android.R.styleable#DatePicker_calendarViewShown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 */
73@Widget
74public class DatePicker extends FrameLayout {
75
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080076 private static final String LOG_TAG = DatePicker.class.getSimpleName();
77
Fabrice Di Meglio039a7842013-08-28 17:41:26 -070078 private DatePickerDelegate mDelegate;
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -080079
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080081 * The callback used to indicate the user changes\d the date.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 */
83 public interface OnDateChangedListener {
84
85 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080086 * Called upon a date change.
87 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 * @param view The view associated with this listener.
89 * @param year The year that was set.
90 * @param monthOfYear The month that was set (0-11) for compatibility
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080091 * with {@link java.util.Calendar}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080092 * @param dayOfMonth The day of the month that was set.
93 */
94 void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
95 }
96
97 public DatePicker(Context context) {
98 this(context, null);
99 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800100
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 public DatePicker(Context context, AttributeSet attrs) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800102 this(context, attrs, R.attr.datePickerStyle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 }
104
Alan Viverette617feb92013-09-09 18:09:13 -0700105 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) {
106 this(context, attrs, defStyleAttr, 0);
107 }
108
109 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
110 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700112 mDelegate = new LegacyDatePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800113 }
114
115 /**
116 * Initialize the state. If the provided values designate an inconsistent
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800117 * date the values are normalized before updating the spinners.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800118 *
119 * @param year The initial year.
120 * @param monthOfYear The initial month <strong>starting from zero</strong>.
121 * @param dayOfMonth The initial day of the month.
122 * @param onDateChangedListener How user is notified date is changed by
123 * user, can be null.
124 */
125 public void init(int year, int monthOfYear, int dayOfMonth,
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700126 OnDateChangedListener onDateChangedListener) {
127 mDelegate.init(year, monthOfYear, dayOfMonth, onDateChangedListener);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800128 }
129
130 /**
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700131 * Updates the current date.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800132 *
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700133 * @param year The year.
134 * @param month The month which is <strong>starting from zero</strong>.
135 * @param dayOfMonth The day of the month.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800136 */
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700137 public void updateDate(int year, int month, int dayOfMonth) {
138 mDelegate.updateDate(year, month, dayOfMonth);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800139 }
140
141 /**
142 * @return The selected year.
143 */
144 public int getYear() {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700145 return mDelegate.getYear();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800146 }
147
148 /**
149 * @return The selected month.
150 */
151 public int getMonth() {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700152 return mDelegate.getMonth();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800153 }
154
155 /**
156 * @return The selected day of month.
157 */
158 public int getDayOfMonth() {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700159 return mDelegate.getDayOfMonth();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800160 }
161
162 /**
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700163 * Gets the minimal date supported by this {@link DatePicker} in
164 * milliseconds since January 1, 1970 00:00:00 in
165 * {@link TimeZone#getDefault()} time zone.
166 * <p>
167 * Note: The default minimal date is 01/01/1900.
168 * <p>
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700169 *
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700170 * @return The minimal supported date.
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700171 */
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700172 public long getMinDate() {
173 return mDelegate.getMinDate();
174 }
175
176 /**
177 * Sets the minimal date supported by this {@link NumberPicker} in
178 * milliseconds since January 1, 1970 00:00:00 in
179 * {@link TimeZone#getDefault()} time zone.
180 *
181 * @param minDate The minimal supported date.
182 */
183 public void setMinDate(long minDate) {
184 mDelegate.setMinDate(minDate);
185 }
186
187 /**
188 * Gets the maximal date supported by this {@link DatePicker} in
189 * milliseconds since January 1, 1970 00:00:00 in
190 * {@link TimeZone#getDefault()} time zone.
191 * <p>
192 * Note: The default maximal date is 12/31/2100.
193 * <p>
194 *
195 * @return The maximal supported date.
196 */
197 public long getMaxDate() {
198 return mDelegate.getMaxDate();
199 }
200
201 /**
202 * Sets the maximal date supported by this {@link DatePicker} in
203 * milliseconds since January 1, 1970 00:00:00 in
204 * {@link TimeZone#getDefault()} time zone.
205 *
206 * @param maxDate The maximal supported date.
207 */
208 public void setMaxDate(long maxDate) {
209 mDelegate.setMaxDate(maxDate);
210 }
211
212 @Override
213 public void setEnabled(boolean enabled) {
214 if (mDelegate.isEnabled() == enabled) {
215 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700216 }
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700217 super.setEnabled(enabled);
218 mDelegate.setEnabled(enabled);
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700219 }
220
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700221 @Override
222 public boolean isEnabled() {
223 return mDelegate.isEnabled();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700224 }
225
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700226 @Override
227 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
228 return mDelegate.dispatchPopulateAccessibilityEvent(event);
229 }
230
231 @Override
232 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
233 super.onPopulateAccessibilityEvent(event);
234 mDelegate.onPopulateAccessibilityEvent(event);
235 }
236
237 @Override
238 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
239 super.onInitializeAccessibilityEvent(event);
240 mDelegate.onInitializeAccessibilityEvent(event);
241 }
242
243 @Override
244 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
245 super.onInitializeAccessibilityNodeInfo(info);
246 mDelegate.onInitializeAccessibilityNodeInfo(info);
247 }
248
249 @Override
250 protected void onConfigurationChanged(Configuration newConfig) {
251 super.onConfigurationChanged(newConfig);
252 mDelegate.onConfigurationChanged(newConfig);
253 }
254
255 /**
256 * Gets whether the {@link CalendarView} is shown.
257 *
258 * @return True if the calendar view is shown.
259 * @see #getCalendarView()
260 */
261 public boolean getCalendarViewShown() {
262 return mDelegate.getCalendarViewShown();
263 }
264
265 /**
266 * Gets the {@link CalendarView}.
267 *
268 * @return The calendar view.
269 * @see #getCalendarViewShown()
270 */
271 public CalendarView getCalendarView () {
272 return mDelegate.getCalendarView();
273 }
274
275 /**
276 * Sets whether the {@link CalendarView} is shown.
277 *
278 * @param shown True if the calendar view is to be shown.
279 */
280 public void setCalendarViewShown(boolean shown) {
281 mDelegate.setCalendarViewShown(shown);
282 }
283
284 /**
285 * Gets whether the spinners are shown.
286 *
287 * @return True if the spinners are shown.
288 */
289 public boolean getSpinnersShown() {
290 return mDelegate.getSpinnersShown();
291 }
292
293 /**
294 * Sets whether the spinners are shown.
295 *
296 * @param shown True if the spinners are to be shown.
297 */
298 public void setSpinnersShown(boolean shown) {
299 mDelegate.setSpinnersShown(shown);
300 }
301
302 // Override so we are in complete control of save / restore for this widget.
303 @Override
304 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
305 mDelegate.dispatchRestoreInstanceState(container);
306 }
307
308 @Override
309 protected Parcelable onSaveInstanceState() {
310 Parcelable superState = super.onSaveInstanceState();
311 return mDelegate.onSaveInstanceState(superState);
312 }
313
314 @Override
315 protected void onRestoreInstanceState(Parcelable state) {
316 SavedState ss = (SavedState) state;
317 super.onRestoreInstanceState(ss.getSuperState());
318 mDelegate.onRestoreInstanceState(ss);
319 }
320
321 /**
322 * A delegate interface that defined the public API of the DatePicker. Allows different
323 * DatePicker implementations. This would need to be implemented by the DatePicker delegates
324 * for the real behavior.
325 */
326 interface DatePickerDelegate {
327 void init(int year, int monthOfYear, int dayOfMonth,
328 OnDateChangedListener onDateChangedListener);
329
330 void updateDate(int year, int month, int dayOfMonth);
331
332 int getYear();
333 int getMonth();
334 int getDayOfMonth();
335
336 void setMinDate(long minDate);
337 long getMinDate();
338
339 void setMaxDate(long maxDate);
340 long getMaxDate();
341
342 void setEnabled(boolean enabled);
343 boolean isEnabled();
344
345 CalendarView getCalendarView ();
346
347 void setCalendarViewShown(boolean shown);
348 boolean getCalendarViewShown();
349
350 void setSpinnersShown(boolean shown);
351 boolean getSpinnersShown();
352
353 void onConfigurationChanged(Configuration newConfig);
354
355 void dispatchRestoreInstanceState(SparseArray<Parcelable> container);
356 Parcelable onSaveInstanceState(Parcelable superState);
357 void onRestoreInstanceState(Parcelable state);
358
359 boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
360 void onPopulateAccessibilityEvent(AccessibilityEvent event);
361 void onInitializeAccessibilityEvent(AccessibilityEvent event);
362 void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
363 }
364
365 /**
366 * An abstract class which can be used as a start for DatePicker implementations
367 */
368 abstract static class AbstractTimePickerDelegate implements DatePickerDelegate {
369 // The delegator
370 protected DatePicker mDelegator;
371
372 // The context
373 protected Context mContext;
374
375 // The current locale
376 protected Locale mCurrentLocale;
377
378 // Callbacks
379 protected OnDateChangedListener mOnDateChangedListener;
380
381 public AbstractTimePickerDelegate(DatePicker delegator, Context context) {
382 mDelegator = delegator;
383 mContext = context;
384
385 // initialization based on locale
386 setCurrentLocale(Locale.getDefault());
387 }
388
389 protected void setCurrentLocale(Locale locale) {
390 if (locale.equals(mCurrentLocale)) {
391 return;
392 }
393 mCurrentLocale = locale;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700394 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700395 }
396
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700397 /**
398 * A delegate implementing the basic DatePicker
399 */
400 private static class LegacyDatePickerDelegate extends AbstractTimePickerDelegate {
401
402 private static final String DATE_FORMAT = "MM/dd/yyyy";
403
404 private static final int DEFAULT_START_YEAR = 1900;
405
406 private static final int DEFAULT_END_YEAR = 2100;
407
408 private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
409
410 private static final boolean DEFAULT_SPINNERS_SHOWN = true;
411
412 private static final boolean DEFAULT_ENABLED_STATE = true;
413
414 private final LinearLayout mSpinners;
415
416 private final NumberPicker mDaySpinner;
417
418 private final NumberPicker mMonthSpinner;
419
420 private final NumberPicker mYearSpinner;
421
422 private final EditText mDaySpinnerInput;
423
424 private final EditText mMonthSpinnerInput;
425
426 private final EditText mYearSpinnerInput;
427
428 private final CalendarView mCalendarView;
429
430 private String[] mShortMonths;
431
432 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
433
434 private int mNumberOfMonths;
435
436 private Calendar mTempDate;
437
438 private Calendar mMinDate;
439
440 private Calendar mMaxDate;
441
442 private Calendar mCurrentDate;
443
444 private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
445
446 LegacyDatePickerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
447 int defStyleAttr, int defStyleRes) {
448 super(delegator, context);
449
450 mDelegator = delegator;
451 mContext = context;
452
453 // initialization based on locale
454 setCurrentLocale(Locale.getDefault());
455
456 final TypedArray attributesArray = context.obtainStyledAttributes(attrs,
457 R.styleable.DatePicker, defStyleAttr, defStyleRes);
458 boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown,
459 DEFAULT_SPINNERS_SHOWN);
460 boolean calendarViewShown = attributesArray.getBoolean(
461 R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
462 int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear,
463 DEFAULT_START_YEAR);
464 int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
465 String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
466 String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
467 int layoutResourceId = attributesArray.getResourceId(
468 R.styleable.DatePicker_internalLayout, R.layout.date_picker);
469 attributesArray.recycle();
470
471 LayoutInflater inflater = (LayoutInflater) context
472 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
473 inflater.inflate(layoutResourceId, mDelegator, true);
474
475 OnValueChangeListener onChangeListener = new OnValueChangeListener() {
476 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
477 updateInputState();
478 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
479 // take care of wrapping of days and months to update greater fields
480 if (picker == mDaySpinner) {
481 int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
482 if (oldVal == maxDayOfMonth && newVal == 1) {
483 mTempDate.add(Calendar.DAY_OF_MONTH, 1);
484 } else if (oldVal == 1 && newVal == maxDayOfMonth) {
485 mTempDate.add(Calendar.DAY_OF_MONTH, -1);
486 } else {
487 mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
488 }
489 } else if (picker == mMonthSpinner) {
490 if (oldVal == 11 && newVal == 0) {
491 mTempDate.add(Calendar.MONTH, 1);
492 } else if (oldVal == 0 && newVal == 11) {
493 mTempDate.add(Calendar.MONTH, -1);
494 } else {
495 mTempDate.add(Calendar.MONTH, newVal - oldVal);
496 }
497 } else if (picker == mYearSpinner) {
498 mTempDate.set(Calendar.YEAR, newVal);
499 } else {
500 throw new IllegalArgumentException();
501 }
502 // now set the date to the adjusted one
503 setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
504 mTempDate.get(Calendar.DAY_OF_MONTH));
505 updateSpinners();
506 updateCalendarView();
507 notifyDateChanged();
508 }
509 };
510
511 mSpinners = (LinearLayout) mDelegator.findViewById(R.id.pickers);
512
513 // calendar view day-picker
514 mCalendarView = (CalendarView) mDelegator.findViewById(R.id.calendar_view);
515 mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
516 public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
517 setDate(year, month, monthDay);
518 updateSpinners();
519 notifyDateChanged();
520 }
521 });
522
523 // day
524 mDaySpinner = (NumberPicker) mDelegator.findViewById(R.id.day);
525 mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
526 mDaySpinner.setOnLongPressUpdateInterval(100);
527 mDaySpinner.setOnValueChangedListener(onChangeListener);
528 mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input);
529
530 // month
531 mMonthSpinner = (NumberPicker) mDelegator.findViewById(R.id.month);
532 mMonthSpinner.setMinValue(0);
533 mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
534 mMonthSpinner.setDisplayedValues(mShortMonths);
535 mMonthSpinner.setOnLongPressUpdateInterval(200);
536 mMonthSpinner.setOnValueChangedListener(onChangeListener);
537 mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input);
538
539 // year
540 mYearSpinner = (NumberPicker) mDelegator.findViewById(R.id.year);
541 mYearSpinner.setOnLongPressUpdateInterval(100);
542 mYearSpinner.setOnValueChangedListener(onChangeListener);
543 mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input);
544
545 // show only what the user required but make sure we
546 // show something and the spinners have higher priority
547 if (!spinnersShown && !calendarViewShown) {
548 setSpinnersShown(true);
549 } else {
550 setSpinnersShown(spinnersShown);
551 setCalendarViewShown(calendarViewShown);
552 }
553
554 // set the min date giving priority of the minDate over startYear
555 mTempDate.clear();
556 if (!TextUtils.isEmpty(minDate)) {
557 if (!parseDate(minDate, mTempDate)) {
558 mTempDate.set(startYear, 0, 1);
559 }
560 } else {
561 mTempDate.set(startYear, 0, 1);
562 }
563 setMinDate(mTempDate.getTimeInMillis());
564
565 // set the max date giving priority of the maxDate over endYear
566 mTempDate.clear();
567 if (!TextUtils.isEmpty(maxDate)) {
568 if (!parseDate(maxDate, mTempDate)) {
569 mTempDate.set(endYear, 11, 31);
570 }
571 } else {
572 mTempDate.set(endYear, 11, 31);
573 }
574 setMaxDate(mTempDate.getTimeInMillis());
575
576 // initialize to current date
577 mCurrentDate.setTimeInMillis(System.currentTimeMillis());
578 init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
579 .get(Calendar.DAY_OF_MONTH), null);
580
581 // re-order the number spinners to match the current date format
582 reorderSpinners();
583
584 // accessibility
585 setContentDescriptions();
586
587 // If not explicitly specified this view is important for accessibility.
588 if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
589 mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
590 }
591 }
592
593 @Override
594 public void init(int year, int monthOfYear, int dayOfMonth,
595 OnDateChangedListener onDateChangedListener) {
596 setDate(year, monthOfYear, dayOfMonth);
597 updateSpinners();
598 updateCalendarView();
599 mOnDateChangedListener = onDateChangedListener;
600 }
601
602 @Override
603 public void updateDate(int year, int month, int dayOfMonth) {
604 if (!isNewDate(year, month, dayOfMonth)) {
605 return;
606 }
607 setDate(year, month, dayOfMonth);
608 updateSpinners();
609 updateCalendarView();
610 notifyDateChanged();
611 }
612
613 @Override
614 public int getYear() {
615 return mCurrentDate.get(Calendar.YEAR);
616 }
617
618 @Override
619 public int getMonth() {
620 return mCurrentDate.get(Calendar.MONTH);
621 }
622
623 @Override
624 public int getDayOfMonth() {
625 return mCurrentDate.get(Calendar.DAY_OF_MONTH);
626 }
627
628 @Override
629 public void setMinDate(long minDate) {
630 mTempDate.setTimeInMillis(minDate);
631 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
632 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
633 return;
634 }
635 mMinDate.setTimeInMillis(minDate);
636 mCalendarView.setMinDate(minDate);
637 if (mCurrentDate.before(mMinDate)) {
638 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
639 updateCalendarView();
640 }
641 updateSpinners();
642 }
643
644 @Override
645 public long getMinDate() {
646 return mCalendarView.getMinDate();
647 }
648
649 @Override
650 public void setMaxDate(long maxDate) {
651 mTempDate.setTimeInMillis(maxDate);
652 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
653 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
654 return;
655 }
656 mMaxDate.setTimeInMillis(maxDate);
657 mCalendarView.setMaxDate(maxDate);
658 if (mCurrentDate.after(mMaxDate)) {
659 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
660 updateCalendarView();
661 }
662 updateSpinners();
663 }
664
665 @Override
666 public long getMaxDate() {
667 return mCalendarView.getMaxDate();
668 }
669
670 @Override
671 public void setEnabled(boolean enabled) {
672 mDaySpinner.setEnabled(enabled);
673 mMonthSpinner.setEnabled(enabled);
674 mYearSpinner.setEnabled(enabled);
675 mCalendarView.setEnabled(enabled);
676 mIsEnabled = enabled;
677 }
678
679 @Override
680 public boolean isEnabled() {
681 return mIsEnabled;
682 }
683
684 @Override
685 public CalendarView getCalendarView() {
686 return mCalendarView;
687 }
688
689 @Override
690 public void setCalendarViewShown(boolean shown) {
691 mCalendarView.setVisibility(shown ? VISIBLE : GONE);
692 }
693
694 @Override
695 public boolean getCalendarViewShown() {
696 return (mCalendarView.getVisibility() == View.VISIBLE);
697 }
698
699 @Override
700 public void setSpinnersShown(boolean shown) {
701 mSpinners.setVisibility(shown ? VISIBLE : GONE);
702 }
703
704 @Override
705 public boolean getSpinnersShown() {
706 return mSpinners.isShown();
707 }
708
709 @Override
710 public void onConfigurationChanged(Configuration newConfig) {
711 setCurrentLocale(newConfig.locale);
712 }
713
714 @Override
715 public void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
716 mDelegator.dispatchThawSelfOnly(container);
717 }
718
719 @Override
720 public Parcelable onSaveInstanceState(Parcelable superState) {
721 return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
722 }
723
724 @Override
725 public void onRestoreInstanceState(Parcelable state) {
726 SavedState ss = (SavedState) state;
727 setDate(ss.mYear, ss.mMonth, ss.mDay);
728 updateSpinners();
729 updateCalendarView();
730 }
731
732 @Override
733 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
734 onPopulateAccessibilityEvent(event);
735 return true;
736 }
737
738 @Override
739 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
740 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
741 String selectedDateUtterance = DateUtils.formatDateTime(mContext,
742 mCurrentDate.getTimeInMillis(), flags);
743 event.getText().add(selectedDateUtterance);
744 }
745
746 @Override
747 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
748 event.setClassName(DatePicker.class.getName());
749 }
750
751 @Override
752 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
753 info.setClassName(DatePicker.class.getName());
754 }
755
756 /**
757 * Sets the current locale.
758 *
759 * @param locale The current locale.
760 */
761 @Override
762 protected void setCurrentLocale(Locale locale) {
763 super.setCurrentLocale(locale);
764
765 mTempDate = getCalendarForLocale(mTempDate, locale);
766 mMinDate = getCalendarForLocale(mMinDate, locale);
767 mMaxDate = getCalendarForLocale(mMaxDate, locale);
768 mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
769
770 mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
771 mShortMonths = new DateFormatSymbols().getShortMonths();
772
773 if (usingNumericMonths()) {
774 // We're in a locale where a date should either be all-numeric, or all-text.
775 // All-text would require custom NumberPicker formatters for day and year.
776 mShortMonths = new String[mNumberOfMonths];
777 for (int i = 0; i < mNumberOfMonths; ++i) {
778 mShortMonths[i] = String.format("%d", i + 1);
779 }
780 }
781 }
782
783 /**
784 * Tests whether the current locale is one where there are no real month names,
785 * such as Chinese, Japanese, or Korean locales.
786 */
787 private boolean usingNumericMonths() {
788 return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0));
789 }
790
791 /**
792 * Gets a calendar for locale bootstrapped with the value of a given calendar.
793 *
794 * @param oldCalendar The old calendar.
795 * @param locale The locale.
796 */
797 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
798 if (oldCalendar == null) {
799 return Calendar.getInstance(locale);
800 } else {
801 final long currentTimeMillis = oldCalendar.getTimeInMillis();
802 Calendar newCalendar = Calendar.getInstance(locale);
803 newCalendar.setTimeInMillis(currentTimeMillis);
804 return newCalendar;
805 }
806 }
807
808 /**
809 * Reorders the spinners according to the date format that is
810 * explicitly set by the user and if no such is set fall back
811 * to the current locale's default format.
812 */
813 private void reorderSpinners() {
814 mSpinners.removeAllViews();
815 // We use numeric spinners for year and day, but textual months. Ask icu4c what
816 // order the user's locale uses for that combination. http://b/7207103.
817 String pattern = ICU.getBestDateTimePattern("yyyyMMMdd",
818 Locale.getDefault().toString());
819 char[] order = ICU.getDateFormatOrder(pattern);
820 final int spinnerCount = order.length;
821 for (int i = 0; i < spinnerCount; i++) {
822 switch (order[i]) {
823 case 'd':
824 mSpinners.addView(mDaySpinner);
825 setImeOptions(mDaySpinner, spinnerCount, i);
826 break;
827 case 'M':
828 mSpinners.addView(mMonthSpinner);
829 setImeOptions(mMonthSpinner, spinnerCount, i);
830 break;
831 case 'y':
832 mSpinners.addView(mYearSpinner);
833 setImeOptions(mYearSpinner, spinnerCount, i);
834 break;
835 default:
836 throw new IllegalArgumentException(Arrays.toString(order));
837 }
838 }
839 }
840
841 /**
842 * Parses the given <code>date</code> and in case of success sets the result
843 * to the <code>outDate</code>.
844 *
845 * @return True if the date was parsed.
846 */
847 private boolean parseDate(String date, Calendar outDate) {
848 try {
849 outDate.setTime(mDateFormat.parse(date));
850 return true;
851 } catch (ParseException e) {
852 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
853 return false;
854 }
855 }
856
857 private boolean isNewDate(int year, int month, int dayOfMonth) {
858 return (mCurrentDate.get(Calendar.YEAR) != year
859 || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
860 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
861 }
862
863 private void setDate(int year, int month, int dayOfMonth) {
864 mCurrentDate.set(year, month, dayOfMonth);
865 if (mCurrentDate.before(mMinDate)) {
866 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
867 } else if (mCurrentDate.after(mMaxDate)) {
868 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
869 }
870 }
871
872 private void updateSpinners() {
873 // set the spinner ranges respecting the min and max dates
874 if (mCurrentDate.equals(mMinDate)) {
875 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
876 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
877 mDaySpinner.setWrapSelectorWheel(false);
878 mMonthSpinner.setDisplayedValues(null);
879 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
880 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
881 mMonthSpinner.setWrapSelectorWheel(false);
882 } else if (mCurrentDate.equals(mMaxDate)) {
883 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
884 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
885 mDaySpinner.setWrapSelectorWheel(false);
886 mMonthSpinner.setDisplayedValues(null);
887 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
888 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
889 mMonthSpinner.setWrapSelectorWheel(false);
890 } else {
891 mDaySpinner.setMinValue(1);
892 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
893 mDaySpinner.setWrapSelectorWheel(true);
894 mMonthSpinner.setDisplayedValues(null);
895 mMonthSpinner.setMinValue(0);
896 mMonthSpinner.setMaxValue(11);
897 mMonthSpinner.setWrapSelectorWheel(true);
898 }
899
900 // make sure the month names are a zero based array
901 // with the months in the month spinner
902 String[] displayedValues = Arrays.copyOfRange(mShortMonths,
903 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
904 mMonthSpinner.setDisplayedValues(displayedValues);
905
906 // year spinner range does not change based on the current date
907 mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
908 mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
909 mYearSpinner.setWrapSelectorWheel(false);
910
911 // set the spinner values
912 mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
913 mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
914 mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
915
916 if (usingNumericMonths()) {
917 mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER);
918 }
919 }
920
921 /**
922 * Updates the calendar view with the current date.
923 */
924 private void updateCalendarView() {
925 mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
926 }
927
928
929 /**
930 * Notifies the listener, if such, for a change in the selected date.
931 */
932 private void notifyDateChanged() {
933 mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
934 if (mOnDateChangedListener != null) {
935 mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(),
936 getDayOfMonth());
937 }
938 }
939
940 /**
941 * Sets the IME options for a spinner based on its ordering.
942 *
943 * @param spinner The spinner.
944 * @param spinnerCount The total spinner count.
945 * @param spinnerIndex The index of the given spinner.
946 */
947 private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
948 final int imeOptions;
949 if (spinnerIndex < spinnerCount - 1) {
950 imeOptions = EditorInfo.IME_ACTION_NEXT;
951 } else {
952 imeOptions = EditorInfo.IME_ACTION_DONE;
953 }
954 TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input);
955 input.setImeOptions(imeOptions);
956 }
957
958 private void setContentDescriptions() {
959 // Day
960 trySetContentDescription(mDaySpinner, R.id.increment,
961 R.string.date_picker_increment_day_button);
962 trySetContentDescription(mDaySpinner, R.id.decrement,
963 R.string.date_picker_decrement_day_button);
964 // Month
965 trySetContentDescription(mMonthSpinner, R.id.increment,
966 R.string.date_picker_increment_month_button);
967 trySetContentDescription(mMonthSpinner, R.id.decrement,
968 R.string.date_picker_decrement_month_button);
969 // Year
970 trySetContentDescription(mYearSpinner, R.id.increment,
971 R.string.date_picker_increment_year_button);
972 trySetContentDescription(mYearSpinner, R.id.decrement,
973 R.string.date_picker_decrement_year_button);
974 }
975
976 private void trySetContentDescription(View root, int viewId, int contDescResId) {
977 View target = root.findViewById(viewId);
978 if (target != null) {
979 target.setContentDescription(mContext.getString(contDescResId));
980 }
981 }
982
983 private void updateInputState() {
984 // Make sure that if the user changes the value and the IME is active
985 // for one of the inputs if this widget, the IME is closed. If the user
986 // changed the value via the IME and there is a next input the IME will
987 // be shown, otherwise the user chose another means of changing the
988 // value and having the IME up makes no sense.
989 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
990 if (inputMethodManager != null) {
991 if (inputMethodManager.isActive(mYearSpinnerInput)) {
992 mYearSpinnerInput.clearFocus();
993 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
994 } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
995 mMonthSpinnerInput.clearFocus();
996 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
997 } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
998 mDaySpinnerInput.clearFocus();
999 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
1000 }
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001001 }
1002 }
1003 }
1004
Svetoslav Ganova53efe92011-09-08 18:08:36 -07001005 /**
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001006 * Class for managing state storing/restoring.
1007 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001008 private static class SavedState extends BaseSavedState {
1009
1010 private final int mYear;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001011
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 private final int mMonth;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001013
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001014 private final int mDay;
1015
1016 /**
1017 * Constructor called from {@link DatePicker#onSaveInstanceState()}
1018 */
1019 private SavedState(Parcelable superState, int year, int month, int day) {
1020 super(superState);
1021 mYear = year;
1022 mMonth = month;
1023 mDay = day;
1024 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001025
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001026 /**
1027 * Constructor called from {@link #CREATOR}
1028 */
1029 private SavedState(Parcel in) {
1030 super(in);
1031 mYear = in.readInt();
1032 mMonth = in.readInt();
1033 mDay = in.readInt();
1034 }
1035
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001036 @Override
1037 public void writeToParcel(Parcel dest, int flags) {
1038 super.writeToParcel(dest, flags);
1039 dest.writeInt(mYear);
1040 dest.writeInt(mMonth);
1041 dest.writeInt(mDay);
1042 }
1043
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001044 @SuppressWarnings("all")
1045 // suppress unused and hiding
1046 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001047
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001048 public SavedState createFromParcel(Parcel in) {
1049 return new SavedState(in);
1050 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001051
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001052 public SavedState[] newArray(int size) {
1053 return new SavedState[size];
1054 }
1055 };
Kenneth Anderssone3491b62010-03-05 09:16:24 +01001056 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07001057}