blob: 26c1f960ff573aa772770ac3ced476d58e06f183 [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
Alan Viverette518ff0d2014-08-15 14:20:35 -070019import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.annotation.Widget;
21import android.content.Context;
Svetoslav Ganovf5926962011-07-12 12:26:20 -070022import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.content.res.TypedArray;
24import android.os.Parcel;
25import android.os.Parcelable;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080026import android.text.TextUtils;
Hyejin Kime9a74a12013-03-20 18:14:00 +090027import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.text.format.DateFormat;
Kenny Rootdddda8d2010-11-15 14:38:51 -080029import android.text.format.DateUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.util.AttributeSet;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080031import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.util.SparseArray;
33import android.view.LayoutInflater;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070034import android.view.View;
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -080035import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080036import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav Ganova53efe92011-09-08 18:08:36 -070037import android.view.inputmethod.EditorInfo;
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -070038import android.view.inputmethod.InputMethodManager;
Svetoslav Ganovcedc4462011-01-19 19:25:46 -080039import android.widget.NumberPicker.OnValueChangeListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
Svetoslav Ganovf5926962011-07-12 12:26:20 -070041import com.android.internal.R;
42
Elliott Hughes949e9df2013-04-30 13:41:06 -070043import java.text.DateFormatSymbols;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080044import java.text.ParseException;
Eric Fischer03a80172009-07-23 18:32:42 -070045import java.text.SimpleDateFormat;
Svetoslav Ganov156f2092011-01-24 02:13:36 -080046import java.util.Arrays;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import java.util.Calendar;
Kenny Rootdddda8d2010-11-15 14:38:51 -080048import java.util.Locale;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080049import java.util.TimeZone;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050
Elliott Hughes659f1452013-04-30 11:11:53 -070051import libcore.icu.ICU;
52
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053/**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080054 * This class is a widget for selecting a date. The date can be selected by a
55 * year, month, and day spinners or a {@link CalendarView}. The set of spinners
56 * and the calendar view are automatically synchronized. The client can
57 * customize whether only the spinners, or only the calendar view, or both to be
58 * displayed. Also the minimal and maximal date from which dates to be selected
59 * can be customized.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080060 * <p>
Scott Main4c359b72012-07-24 15:51:27 -070061 * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
62 * guide.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080063 * </p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080064 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 * For a dialog using this view, see {@link android.app.DatePickerDialog}.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080066 * </p>
67 *
68 * @attr ref android.R.styleable#DatePicker_startYear
69 * @attr ref android.R.styleable#DatePicker_endYear
70 * @attr ref android.R.styleable#DatePicker_maxDate
71 * @attr ref android.R.styleable#DatePicker_minDate
72 * @attr ref android.R.styleable#DatePicker_spinnersShown
73 * @attr ref android.R.styleable#DatePicker_calendarViewShown
Alan Viverette60727e02014-07-28 16:56:32 -070074 * @attr ref android.R.styleable#DatePicker_dayOfWeekBackgroundColor
75 * @attr ref android.R.styleable#DatePicker_dayOfWeekTextAppearance
76 * @attr ref android.R.styleable#DatePicker_headerBackgroundColor
77 * @attr ref android.R.styleable#DatePicker_headerMonthTextAppearance
78 * @attr ref android.R.styleable#DatePicker_headerDayOfMonthTextAppearance
79 * @attr ref android.R.styleable#DatePicker_headerYearTextAppearance
80 * @attr ref android.R.styleable#DatePicker_yearListItemTextAppearance
81 * @attr ref android.R.styleable#DatePicker_yearListSelectorColor
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070082 * @attr ref android.R.styleable#DatePicker_calendarTextColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083 */
84@Widget
85public class DatePicker extends FrameLayout {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080086 private static final String LOG_TAG = DatePicker.class.getSimpleName();
87
Chet Haase3053b2f2014-08-06 07:51:50 -070088 private static final int MODE_SPINNER = 1;
89 private static final int MODE_CALENDAR = 2;
90
Alan Viverette60727e02014-07-28 16:56:32 -070091 private final DatePickerDelegate mDelegate;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070092
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080094 * The callback used to indicate the user changes\d the date.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 */
96 public interface OnDateChangedListener {
97
98 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080099 * Called upon a date change.
100 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 * @param view The view associated with this listener.
102 * @param year The year that was set.
103 * @param monthOfYear The month that was set (0-11) for compatibility
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800104 * with {@link java.util.Calendar}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 * @param dayOfMonth The day of the month that was set.
106 */
107 void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
108 }
109
110 public DatePicker(Context context) {
111 this(context, null);
112 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800113
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 public DatePicker(Context context, AttributeSet attrs) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800115 this(context, attrs, R.attr.datePickerStyle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 }
117
Alan Viverette617feb92013-09-09 18:09:13 -0700118 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) {
119 this(context, attrs, defStyleAttr, 0);
120 }
121
122 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
123 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124
Alan Viverette60727e02014-07-28 16:56:32 -0700125 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
126 defStyleAttr, defStyleRes);
Alan Viverette518ff0d2014-08-15 14:20:35 -0700127 final int mode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700128 a.recycle();
129
Chet Haase3053b2f2014-08-06 07:51:50 -0700130 switch (mode) {
131 case MODE_CALENDAR:
132 mDelegate = createCalendarUIDelegate(context, attrs, defStyleAttr, defStyleRes);
133 break;
134 case MODE_SPINNER:
135 default:
136 mDelegate = createSpinnerUIDelegate(context, attrs, defStyleAttr, defStyleRes);
137 break;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700138 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700139 }
140
Chet Haase3053b2f2014-08-06 07:51:50 -0700141 private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700142 int defStyleAttr, int defStyleRes) {
Chet Haase3053b2f2014-08-06 07:51:50 -0700143 return new DatePickerSpinnerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700144 }
145
Chet Haase3053b2f2014-08-06 07:51:50 -0700146 private DatePickerDelegate createCalendarUIDelegate(Context context, AttributeSet attrs,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700147 int defStyleAttr, int defStyleRes) {
Chet Haase3053b2f2014-08-06 07:51:50 -0700148 return new DatePickerCalendarDelegate(this, context, attrs, defStyleAttr,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700149 defStyleRes);
150 }
151
152 /**
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800153 * Initialize the state. If the provided values designate an inconsistent
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800154 * date the values are normalized before updating the spinners.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800155 *
156 * @param year The initial year.
157 * @param monthOfYear The initial month <strong>starting from zero</strong>.
158 * @param dayOfMonth The initial day of the month.
159 * @param onDateChangedListener How user is notified date is changed by
160 * user, can be null.
161 */
162 public void init(int year, int monthOfYear, int dayOfMonth,
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700163 OnDateChangedListener onDateChangedListener) {
164 mDelegate.init(year, monthOfYear, dayOfMonth, onDateChangedListener);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800165 }
166
167 /**
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700168 * Update the current date.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800169 *
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700170 * @param year The year.
171 * @param month The month which is <strong>starting from zero</strong>.
172 * @param dayOfMonth The day of the month.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800173 */
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700174 public void updateDate(int year, int month, int dayOfMonth) {
175 mDelegate.updateDate(year, month, dayOfMonth);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800176 }
177
178 /**
179 * @return The selected year.
180 */
181 public int getYear() {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700182 return mDelegate.getYear();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800183 }
184
185 /**
186 * @return The selected month.
187 */
188 public int getMonth() {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700189 return mDelegate.getMonth();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800190 }
191
192 /**
193 * @return The selected day of month.
194 */
195 public int getDayOfMonth() {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700196 return mDelegate.getDayOfMonth();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800197 }
198
199 /**
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700200 * Gets the minimal date supported by this {@link DatePicker} in
201 * milliseconds since January 1, 1970 00:00:00 in
202 * {@link TimeZone#getDefault()} time zone.
203 * <p>
204 * Note: The default minimal date is 01/01/1900.
205 * <p>
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700206 *
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700207 * @return The minimal supported date.
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700208 */
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700209 public long getMinDate() {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700210 return mDelegate.getMinDate().getTimeInMillis();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700211 }
212
213 /**
214 * Sets the minimal date supported by this {@link NumberPicker} in
215 * milliseconds since January 1, 1970 00:00:00 in
216 * {@link TimeZone#getDefault()} time zone.
217 *
218 * @param minDate The minimal supported date.
219 */
220 public void setMinDate(long minDate) {
221 mDelegate.setMinDate(minDate);
222 }
223
224 /**
225 * Gets the maximal date supported by this {@link DatePicker} in
226 * milliseconds since January 1, 1970 00:00:00 in
227 * {@link TimeZone#getDefault()} time zone.
228 * <p>
229 * Note: The default maximal date is 12/31/2100.
230 * <p>
231 *
232 * @return The maximal supported date.
233 */
234 public long getMaxDate() {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700235 return mDelegate.getMaxDate().getTimeInMillis();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700236 }
237
238 /**
239 * Sets the maximal date supported by this {@link DatePicker} in
240 * milliseconds since January 1, 1970 00:00:00 in
241 * {@link TimeZone#getDefault()} time zone.
242 *
243 * @param maxDate The maximal supported date.
244 */
245 public void setMaxDate(long maxDate) {
246 mDelegate.setMaxDate(maxDate);
247 }
248
Alan Viverette518ff0d2014-08-15 14:20:35 -0700249 /**
250 * Sets the callback that indicates the current date is valid.
251 *
252 * @param callback the callback, may be null
253 * @hide
254 */
255 public void setValidationCallback(@Nullable ValidationCallback callback) {
256 mDelegate.setValidationCallback(callback);
257 }
258
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700259 @Override
260 public void setEnabled(boolean enabled) {
261 if (mDelegate.isEnabled() == enabled) {
262 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700263 }
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700264 super.setEnabled(enabled);
265 mDelegate.setEnabled(enabled);
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700266 }
267
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700268 @Override
269 public boolean isEnabled() {
270 return mDelegate.isEnabled();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700271 }
272
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700273 @Override
274 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
275 return mDelegate.dispatchPopulateAccessibilityEvent(event);
276 }
277
278 @Override
279 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
280 super.onPopulateAccessibilityEvent(event);
281 mDelegate.onPopulateAccessibilityEvent(event);
282 }
283
284 @Override
285 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
286 super.onInitializeAccessibilityEvent(event);
287 mDelegate.onInitializeAccessibilityEvent(event);
288 }
289
290 @Override
291 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
292 super.onInitializeAccessibilityNodeInfo(info);
293 mDelegate.onInitializeAccessibilityNodeInfo(info);
294 }
295
296 @Override
297 protected void onConfigurationChanged(Configuration newConfig) {
298 super.onConfigurationChanged(newConfig);
299 mDelegate.onConfigurationChanged(newConfig);
300 }
301
302 /**
303 * Gets whether the {@link CalendarView} is shown.
304 *
305 * @return True if the calendar view is shown.
306 * @see #getCalendarView()
307 */
308 public boolean getCalendarViewShown() {
309 return mDelegate.getCalendarViewShown();
310 }
311
312 /**
313 * Gets the {@link CalendarView}.
314 *
315 * @return The calendar view.
316 * @see #getCalendarViewShown()
317 */
318 public CalendarView getCalendarView () {
319 return mDelegate.getCalendarView();
320 }
321
322 /**
323 * Sets whether the {@link CalendarView} is shown.
324 *
325 * @param shown True if the calendar view is to be shown.
326 */
327 public void setCalendarViewShown(boolean shown) {
328 mDelegate.setCalendarViewShown(shown);
329 }
330
331 /**
332 * Gets whether the spinners are shown.
333 *
334 * @return True if the spinners are shown.
335 */
336 public boolean getSpinnersShown() {
337 return mDelegate.getSpinnersShown();
338 }
339
340 /**
341 * Sets whether the spinners are shown.
342 *
343 * @param shown True if the spinners are to be shown.
344 */
345 public void setSpinnersShown(boolean shown) {
346 mDelegate.setSpinnersShown(shown);
347 }
348
349 // Override so we are in complete control of save / restore for this widget.
350 @Override
351 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
352 mDelegate.dispatchRestoreInstanceState(container);
353 }
354
355 @Override
356 protected Parcelable onSaveInstanceState() {
357 Parcelable superState = super.onSaveInstanceState();
358 return mDelegate.onSaveInstanceState(superState);
359 }
360
361 @Override
362 protected void onRestoreInstanceState(Parcelable state) {
363 SavedState ss = (SavedState) state;
364 super.onRestoreInstanceState(ss.getSuperState());
365 mDelegate.onRestoreInstanceState(ss);
366 }
367
368 /**
369 * A delegate interface that defined the public API of the DatePicker. Allows different
370 * DatePicker implementations. This would need to be implemented by the DatePicker delegates
371 * for the real behavior.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700372 *
373 * @hide
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700374 */
375 interface DatePickerDelegate {
376 void init(int year, int monthOfYear, int dayOfMonth,
377 OnDateChangedListener onDateChangedListener);
378
379 void updateDate(int year, int month, int dayOfMonth);
380
381 int getYear();
382 int getMonth();
383 int getDayOfMonth();
384
385 void setMinDate(long minDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700386 Calendar getMinDate();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700387
388 void setMaxDate(long maxDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700389 Calendar getMaxDate();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700390
391 void setEnabled(boolean enabled);
392 boolean isEnabled();
393
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700394 CalendarView getCalendarView();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700395
396 void setCalendarViewShown(boolean shown);
397 boolean getCalendarViewShown();
398
399 void setSpinnersShown(boolean shown);
400 boolean getSpinnersShown();
401
Alan Viverette518ff0d2014-08-15 14:20:35 -0700402 void setValidationCallback(ValidationCallback callback);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700403
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700404 void onConfigurationChanged(Configuration newConfig);
405
406 void dispatchRestoreInstanceState(SparseArray<Parcelable> container);
407 Parcelable onSaveInstanceState(Parcelable superState);
408 void onRestoreInstanceState(Parcelable state);
409
410 boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
411 void onPopulateAccessibilityEvent(AccessibilityEvent event);
412 void onInitializeAccessibilityEvent(AccessibilityEvent event);
413 void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
414 }
415
416 /**
417 * An abstract class which can be used as a start for DatePicker implementations
418 */
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700419 abstract static class AbstractDatePickerDelegate implements DatePickerDelegate {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700420 // The delegator
421 protected DatePicker mDelegator;
422
423 // The context
424 protected Context mContext;
425
426 // The current locale
427 protected Locale mCurrentLocale;
428
429 // Callbacks
Alan Viverette518ff0d2014-08-15 14:20:35 -0700430 protected OnDateChangedListener mOnDateChangedListener;
431 protected ValidationCallback mValidationCallback;
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700432
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700433 public AbstractDatePickerDelegate(DatePicker delegator, Context context) {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700434 mDelegator = delegator;
435 mContext = context;
436
437 // initialization based on locale
438 setCurrentLocale(Locale.getDefault());
439 }
440
441 protected void setCurrentLocale(Locale locale) {
442 if (locale.equals(mCurrentLocale)) {
443 return;
444 }
445 mCurrentLocale = locale;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700446 }
Alan Viverette518ff0d2014-08-15 14:20:35 -0700447
448 @Override
449 public void setValidationCallback(ValidationCallback callback) {
450 mValidationCallback = callback;
451 }
452
453 protected void onValidationChanged(boolean valid) {
454 if (mValidationCallback != null) {
455 mValidationCallback.onValidationChanged(valid);
456 }
457 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700458 }
459
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700460 /**
Alan Viverette518ff0d2014-08-15 14:20:35 -0700461 * A callback interface for updating input validity when the date picker
462 * when included into a dialog.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700463 *
464 * @hide
465 */
Alan Viverette518ff0d2014-08-15 14:20:35 -0700466 public static interface ValidationCallback {
467 void onValidationChanged(boolean valid);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700468 }
469
470 /**
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700471 * A delegate implementing the basic DatePicker
472 */
Chet Haase3053b2f2014-08-06 07:51:50 -0700473 private static class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700474
475 private static final String DATE_FORMAT = "MM/dd/yyyy";
476
477 private static final int DEFAULT_START_YEAR = 1900;
478
479 private static final int DEFAULT_END_YEAR = 2100;
480
481 private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
482
483 private static final boolean DEFAULT_SPINNERS_SHOWN = true;
484
485 private static final boolean DEFAULT_ENABLED_STATE = true;
486
487 private final LinearLayout mSpinners;
488
489 private final NumberPicker mDaySpinner;
490
491 private final NumberPicker mMonthSpinner;
492
493 private final NumberPicker mYearSpinner;
494
495 private final EditText mDaySpinnerInput;
496
497 private final EditText mMonthSpinnerInput;
498
499 private final EditText mYearSpinnerInput;
500
501 private final CalendarView mCalendarView;
502
503 private String[] mShortMonths;
504
505 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
506
507 private int mNumberOfMonths;
508
509 private Calendar mTempDate;
510
511 private Calendar mMinDate;
512
513 private Calendar mMaxDate;
514
515 private Calendar mCurrentDate;
516
517 private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
518
Chet Haase3053b2f2014-08-06 07:51:50 -0700519 DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700520 int defStyleAttr, int defStyleRes) {
521 super(delegator, context);
522
523 mDelegator = delegator;
524 mContext = context;
525
526 // initialization based on locale
527 setCurrentLocale(Locale.getDefault());
528
529 final TypedArray attributesArray = context.obtainStyledAttributes(attrs,
530 R.styleable.DatePicker, defStyleAttr, defStyleRes);
531 boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown,
532 DEFAULT_SPINNERS_SHOWN);
533 boolean calendarViewShown = attributesArray.getBoolean(
534 R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
535 int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear,
536 DEFAULT_START_YEAR);
537 int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
538 String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
539 String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
540 int layoutResourceId = attributesArray.getResourceId(
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700541 R.styleable.DatePicker_legacyLayout, R.layout.date_picker_legacy);
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700542 attributesArray.recycle();
543
544 LayoutInflater inflater = (LayoutInflater) context
545 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
546 inflater.inflate(layoutResourceId, mDelegator, true);
547
548 OnValueChangeListener onChangeListener = new OnValueChangeListener() {
549 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
550 updateInputState();
551 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
552 // take care of wrapping of days and months to update greater fields
553 if (picker == mDaySpinner) {
554 int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
555 if (oldVal == maxDayOfMonth && newVal == 1) {
556 mTempDate.add(Calendar.DAY_OF_MONTH, 1);
557 } else if (oldVal == 1 && newVal == maxDayOfMonth) {
558 mTempDate.add(Calendar.DAY_OF_MONTH, -1);
559 } else {
560 mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
561 }
562 } else if (picker == mMonthSpinner) {
563 if (oldVal == 11 && newVal == 0) {
564 mTempDate.add(Calendar.MONTH, 1);
565 } else if (oldVal == 0 && newVal == 11) {
566 mTempDate.add(Calendar.MONTH, -1);
567 } else {
568 mTempDate.add(Calendar.MONTH, newVal - oldVal);
569 }
570 } else if (picker == mYearSpinner) {
571 mTempDate.set(Calendar.YEAR, newVal);
572 } else {
573 throw new IllegalArgumentException();
574 }
575 // now set the date to the adjusted one
576 setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
577 mTempDate.get(Calendar.DAY_OF_MONTH));
578 updateSpinners();
579 updateCalendarView();
580 notifyDateChanged();
581 }
582 };
583
584 mSpinners = (LinearLayout) mDelegator.findViewById(R.id.pickers);
585
586 // calendar view day-picker
587 mCalendarView = (CalendarView) mDelegator.findViewById(R.id.calendar_view);
588 mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
589 public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
590 setDate(year, month, monthDay);
591 updateSpinners();
592 notifyDateChanged();
593 }
594 });
595
596 // day
597 mDaySpinner = (NumberPicker) mDelegator.findViewById(R.id.day);
598 mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
599 mDaySpinner.setOnLongPressUpdateInterval(100);
600 mDaySpinner.setOnValueChangedListener(onChangeListener);
601 mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input);
602
603 // month
604 mMonthSpinner = (NumberPicker) mDelegator.findViewById(R.id.month);
605 mMonthSpinner.setMinValue(0);
606 mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
607 mMonthSpinner.setDisplayedValues(mShortMonths);
608 mMonthSpinner.setOnLongPressUpdateInterval(200);
609 mMonthSpinner.setOnValueChangedListener(onChangeListener);
610 mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input);
611
612 // year
613 mYearSpinner = (NumberPicker) mDelegator.findViewById(R.id.year);
614 mYearSpinner.setOnLongPressUpdateInterval(100);
615 mYearSpinner.setOnValueChangedListener(onChangeListener);
616 mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input);
617
618 // show only what the user required but make sure we
619 // show something and the spinners have higher priority
620 if (!spinnersShown && !calendarViewShown) {
621 setSpinnersShown(true);
622 } else {
623 setSpinnersShown(spinnersShown);
624 setCalendarViewShown(calendarViewShown);
625 }
626
627 // set the min date giving priority of the minDate over startYear
628 mTempDate.clear();
629 if (!TextUtils.isEmpty(minDate)) {
630 if (!parseDate(minDate, mTempDate)) {
631 mTempDate.set(startYear, 0, 1);
632 }
633 } else {
634 mTempDate.set(startYear, 0, 1);
635 }
636 setMinDate(mTempDate.getTimeInMillis());
637
638 // set the max date giving priority of the maxDate over endYear
639 mTempDate.clear();
640 if (!TextUtils.isEmpty(maxDate)) {
641 if (!parseDate(maxDate, mTempDate)) {
642 mTempDate.set(endYear, 11, 31);
643 }
644 } else {
645 mTempDate.set(endYear, 11, 31);
646 }
647 setMaxDate(mTempDate.getTimeInMillis());
648
649 // initialize to current date
650 mCurrentDate.setTimeInMillis(System.currentTimeMillis());
651 init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
652 .get(Calendar.DAY_OF_MONTH), null);
653
654 // re-order the number spinners to match the current date format
655 reorderSpinners();
656
657 // accessibility
658 setContentDescriptions();
659
660 // If not explicitly specified this view is important for accessibility.
661 if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
662 mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
663 }
664 }
665
666 @Override
667 public void init(int year, int monthOfYear, int dayOfMonth,
668 OnDateChangedListener onDateChangedListener) {
669 setDate(year, monthOfYear, dayOfMonth);
670 updateSpinners();
671 updateCalendarView();
672 mOnDateChangedListener = onDateChangedListener;
673 }
674
675 @Override
676 public void updateDate(int year, int month, int dayOfMonth) {
677 if (!isNewDate(year, month, dayOfMonth)) {
678 return;
679 }
680 setDate(year, month, dayOfMonth);
681 updateSpinners();
682 updateCalendarView();
683 notifyDateChanged();
684 }
685
686 @Override
687 public int getYear() {
688 return mCurrentDate.get(Calendar.YEAR);
689 }
690
691 @Override
692 public int getMonth() {
693 return mCurrentDate.get(Calendar.MONTH);
694 }
695
696 @Override
697 public int getDayOfMonth() {
698 return mCurrentDate.get(Calendar.DAY_OF_MONTH);
699 }
700
701 @Override
702 public void setMinDate(long minDate) {
703 mTempDate.setTimeInMillis(minDate);
704 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
705 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
706 return;
707 }
708 mMinDate.setTimeInMillis(minDate);
709 mCalendarView.setMinDate(minDate);
710 if (mCurrentDate.before(mMinDate)) {
711 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
712 updateCalendarView();
713 }
714 updateSpinners();
715 }
716
717 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700718 public Calendar getMinDate() {
719 final Calendar minDate = Calendar.getInstance();
720 minDate.setTimeInMillis(mCalendarView.getMinDate());
721 return minDate;
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700722 }
723
724 @Override
725 public void setMaxDate(long maxDate) {
726 mTempDate.setTimeInMillis(maxDate);
727 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
728 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
729 return;
730 }
731 mMaxDate.setTimeInMillis(maxDate);
732 mCalendarView.setMaxDate(maxDate);
733 if (mCurrentDate.after(mMaxDate)) {
734 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
735 updateCalendarView();
736 }
737 updateSpinners();
738 }
739
740 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700741 public Calendar getMaxDate() {
742 final Calendar maxDate = Calendar.getInstance();
743 maxDate.setTimeInMillis(mCalendarView.getMaxDate());
744 return maxDate;
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700745 }
746
747 @Override
748 public void setEnabled(boolean enabled) {
749 mDaySpinner.setEnabled(enabled);
750 mMonthSpinner.setEnabled(enabled);
751 mYearSpinner.setEnabled(enabled);
752 mCalendarView.setEnabled(enabled);
753 mIsEnabled = enabled;
754 }
755
756 @Override
757 public boolean isEnabled() {
758 return mIsEnabled;
759 }
760
761 @Override
762 public CalendarView getCalendarView() {
763 return mCalendarView;
764 }
765
766 @Override
767 public void setCalendarViewShown(boolean shown) {
768 mCalendarView.setVisibility(shown ? VISIBLE : GONE);
769 }
770
771 @Override
772 public boolean getCalendarViewShown() {
773 return (mCalendarView.getVisibility() == View.VISIBLE);
774 }
775
776 @Override
777 public void setSpinnersShown(boolean shown) {
778 mSpinners.setVisibility(shown ? VISIBLE : GONE);
779 }
780
781 @Override
782 public boolean getSpinnersShown() {
783 return mSpinners.isShown();
784 }
785
786 @Override
787 public void onConfigurationChanged(Configuration newConfig) {
788 setCurrentLocale(newConfig.locale);
789 }
790
791 @Override
792 public void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
793 mDelegator.dispatchThawSelfOnly(container);
794 }
795
796 @Override
797 public Parcelable onSaveInstanceState(Parcelable superState) {
798 return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
799 }
800
801 @Override
802 public void onRestoreInstanceState(Parcelable state) {
803 SavedState ss = (SavedState) state;
804 setDate(ss.mYear, ss.mMonth, ss.mDay);
805 updateSpinners();
806 updateCalendarView();
807 }
808
809 @Override
810 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
811 onPopulateAccessibilityEvent(event);
812 return true;
813 }
814
815 @Override
816 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
817 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
818 String selectedDateUtterance = DateUtils.formatDateTime(mContext,
819 mCurrentDate.getTimeInMillis(), flags);
820 event.getText().add(selectedDateUtterance);
821 }
822
823 @Override
824 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
825 event.setClassName(DatePicker.class.getName());
826 }
827
828 @Override
829 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
830 info.setClassName(DatePicker.class.getName());
831 }
832
833 /**
834 * Sets the current locale.
835 *
836 * @param locale The current locale.
837 */
838 @Override
839 protected void setCurrentLocale(Locale locale) {
840 super.setCurrentLocale(locale);
841
842 mTempDate = getCalendarForLocale(mTempDate, locale);
843 mMinDate = getCalendarForLocale(mMinDate, locale);
844 mMaxDate = getCalendarForLocale(mMaxDate, locale);
845 mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
846
847 mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
848 mShortMonths = new DateFormatSymbols().getShortMonths();
849
850 if (usingNumericMonths()) {
851 // We're in a locale where a date should either be all-numeric, or all-text.
852 // All-text would require custom NumberPicker formatters for day and year.
853 mShortMonths = new String[mNumberOfMonths];
854 for (int i = 0; i < mNumberOfMonths; ++i) {
855 mShortMonths[i] = String.format("%d", i + 1);
856 }
857 }
858 }
859
860 /**
861 * Tests whether the current locale is one where there are no real month names,
862 * such as Chinese, Japanese, or Korean locales.
863 */
864 private boolean usingNumericMonths() {
865 return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0));
866 }
867
868 /**
869 * Gets a calendar for locale bootstrapped with the value of a given calendar.
870 *
871 * @param oldCalendar The old calendar.
872 * @param locale The locale.
873 */
874 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
875 if (oldCalendar == null) {
876 return Calendar.getInstance(locale);
877 } else {
878 final long currentTimeMillis = oldCalendar.getTimeInMillis();
879 Calendar newCalendar = Calendar.getInstance(locale);
880 newCalendar.setTimeInMillis(currentTimeMillis);
881 return newCalendar;
882 }
883 }
884
885 /**
886 * Reorders the spinners according to the date format that is
887 * explicitly set by the user and if no such is set fall back
888 * to the current locale's default format.
889 */
890 private void reorderSpinners() {
891 mSpinners.removeAllViews();
892 // We use numeric spinners for year and day, but textual months. Ask icu4c what
893 // order the user's locale uses for that combination. http://b/7207103.
Elliott Hughesf8abeb82014-05-30 20:13:15 -0700894 String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700895 char[] order = ICU.getDateFormatOrder(pattern);
896 final int spinnerCount = order.length;
897 for (int i = 0; i < spinnerCount; i++) {
898 switch (order[i]) {
899 case 'd':
900 mSpinners.addView(mDaySpinner);
901 setImeOptions(mDaySpinner, spinnerCount, i);
902 break;
903 case 'M':
904 mSpinners.addView(mMonthSpinner);
905 setImeOptions(mMonthSpinner, spinnerCount, i);
906 break;
907 case 'y':
908 mSpinners.addView(mYearSpinner);
909 setImeOptions(mYearSpinner, spinnerCount, i);
910 break;
911 default:
912 throw new IllegalArgumentException(Arrays.toString(order));
913 }
914 }
915 }
916
917 /**
918 * Parses the given <code>date</code> and in case of success sets the result
919 * to the <code>outDate</code>.
920 *
921 * @return True if the date was parsed.
922 */
923 private boolean parseDate(String date, Calendar outDate) {
924 try {
925 outDate.setTime(mDateFormat.parse(date));
926 return true;
927 } catch (ParseException e) {
928 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
929 return false;
930 }
931 }
932
933 private boolean isNewDate(int year, int month, int dayOfMonth) {
934 return (mCurrentDate.get(Calendar.YEAR) != year
935 || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
936 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
937 }
938
939 private void setDate(int year, int month, int dayOfMonth) {
940 mCurrentDate.set(year, month, dayOfMonth);
941 if (mCurrentDate.before(mMinDate)) {
942 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
943 } else if (mCurrentDate.after(mMaxDate)) {
944 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
945 }
946 }
947
948 private void updateSpinners() {
949 // set the spinner ranges respecting the min and max dates
950 if (mCurrentDate.equals(mMinDate)) {
951 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
952 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
953 mDaySpinner.setWrapSelectorWheel(false);
954 mMonthSpinner.setDisplayedValues(null);
955 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
956 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
957 mMonthSpinner.setWrapSelectorWheel(false);
958 } else if (mCurrentDate.equals(mMaxDate)) {
959 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
960 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
961 mDaySpinner.setWrapSelectorWheel(false);
962 mMonthSpinner.setDisplayedValues(null);
963 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
964 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
965 mMonthSpinner.setWrapSelectorWheel(false);
966 } else {
967 mDaySpinner.setMinValue(1);
968 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
969 mDaySpinner.setWrapSelectorWheel(true);
970 mMonthSpinner.setDisplayedValues(null);
971 mMonthSpinner.setMinValue(0);
972 mMonthSpinner.setMaxValue(11);
973 mMonthSpinner.setWrapSelectorWheel(true);
974 }
975
976 // make sure the month names are a zero based array
977 // with the months in the month spinner
978 String[] displayedValues = Arrays.copyOfRange(mShortMonths,
979 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
980 mMonthSpinner.setDisplayedValues(displayedValues);
981
982 // year spinner range does not change based on the current date
983 mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
984 mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
985 mYearSpinner.setWrapSelectorWheel(false);
986
987 // set the spinner values
988 mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
989 mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
990 mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
991
992 if (usingNumericMonths()) {
993 mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER);
994 }
995 }
996
997 /**
998 * Updates the calendar view with the current date.
999 */
1000 private void updateCalendarView() {
1001 mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
1002 }
1003
1004
1005 /**
1006 * Notifies the listener, if such, for a change in the selected date.
1007 */
1008 private void notifyDateChanged() {
1009 mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1010 if (mOnDateChangedListener != null) {
1011 mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(),
1012 getDayOfMonth());
1013 }
1014 }
1015
1016 /**
1017 * Sets the IME options for a spinner based on its ordering.
1018 *
1019 * @param spinner The spinner.
1020 * @param spinnerCount The total spinner count.
1021 * @param spinnerIndex The index of the given spinner.
1022 */
1023 private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
1024 final int imeOptions;
1025 if (spinnerIndex < spinnerCount - 1) {
1026 imeOptions = EditorInfo.IME_ACTION_NEXT;
1027 } else {
1028 imeOptions = EditorInfo.IME_ACTION_DONE;
1029 }
1030 TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input);
1031 input.setImeOptions(imeOptions);
1032 }
1033
1034 private void setContentDescriptions() {
1035 // Day
1036 trySetContentDescription(mDaySpinner, R.id.increment,
1037 R.string.date_picker_increment_day_button);
1038 trySetContentDescription(mDaySpinner, R.id.decrement,
1039 R.string.date_picker_decrement_day_button);
1040 // Month
1041 trySetContentDescription(mMonthSpinner, R.id.increment,
1042 R.string.date_picker_increment_month_button);
1043 trySetContentDescription(mMonthSpinner, R.id.decrement,
1044 R.string.date_picker_decrement_month_button);
1045 // Year
1046 trySetContentDescription(mYearSpinner, R.id.increment,
1047 R.string.date_picker_increment_year_button);
1048 trySetContentDescription(mYearSpinner, R.id.decrement,
1049 R.string.date_picker_decrement_year_button);
1050 }
1051
1052 private void trySetContentDescription(View root, int viewId, int contDescResId) {
1053 View target = root.findViewById(viewId);
1054 if (target != null) {
1055 target.setContentDescription(mContext.getString(contDescResId));
1056 }
1057 }
1058
1059 private void updateInputState() {
1060 // Make sure that if the user changes the value and the IME is active
1061 // for one of the inputs if this widget, the IME is closed. If the user
1062 // changed the value via the IME and there is a next input the IME will
1063 // be shown, otherwise the user chose another means of changing the
1064 // value and having the IME up makes no sense.
1065 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
1066 if (inputMethodManager != null) {
1067 if (inputMethodManager.isActive(mYearSpinnerInput)) {
1068 mYearSpinnerInput.clearFocus();
1069 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
1070 } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
1071 mMonthSpinnerInput.clearFocus();
1072 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
1073 } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
1074 mDaySpinnerInput.clearFocus();
1075 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
1076 }
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001077 }
1078 }
1079 }
1080
Svetoslav Ganova53efe92011-09-08 18:08:36 -07001081 /**
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001082 * Class for managing state storing/restoring.
1083 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001084 private static class SavedState extends BaseSavedState {
1085
1086 private final int mYear;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001087
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001088 private final int mMonth;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001089
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001090 private final int mDay;
1091
1092 /**
1093 * Constructor called from {@link DatePicker#onSaveInstanceState()}
1094 */
1095 private SavedState(Parcelable superState, int year, int month, int day) {
1096 super(superState);
1097 mYear = year;
1098 mMonth = month;
1099 mDay = day;
1100 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001101
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001102 /**
1103 * Constructor called from {@link #CREATOR}
1104 */
1105 private SavedState(Parcel in) {
1106 super(in);
1107 mYear = in.readInt();
1108 mMonth = in.readInt();
1109 mDay = in.readInt();
1110 }
1111
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001112 @Override
1113 public void writeToParcel(Parcel dest, int flags) {
1114 super.writeToParcel(dest, flags);
1115 dest.writeInt(mYear);
1116 dest.writeInt(mMonth);
1117 dest.writeInt(mDay);
1118 }
1119
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001120 @SuppressWarnings("all")
1121 // suppress unused and hiding
1122 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001123
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001124 public SavedState createFromParcel(Parcel in) {
1125 return new SavedState(in);
1126 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001127
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001128 public SavedState[] newArray(int size) {
1129 return new SavedState[size];
1130 }
1131 };
Kenneth Anderssone3491b62010-03-05 09:16:24 +01001132 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07001133}