blob: 87bee44611d8dc65f0581aab0064640eef61fe42 [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 Ganova53efe92011-09-08 18:08:36 -070036import android.view.inputmethod.EditorInfo;
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -070037import android.view.inputmethod.InputMethodManager;
Svetoslav Ganovcedc4462011-01-19 19:25:46 -080038import android.widget.NumberPicker.OnValueChangeListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039
Svetoslav Ganovf5926962011-07-12 12:26:20 -070040import com.android.internal.R;
41
Elliott Hughes949e9df2013-04-30 13:41:06 -070042import java.text.DateFormatSymbols;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080043import java.text.ParseException;
Eric Fischer03a80172009-07-23 18:32:42 -070044import java.text.SimpleDateFormat;
Svetoslav Ganov156f2092011-01-24 02:13:36 -080045import java.util.Arrays;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import java.util.Calendar;
Kenny Rootdddda8d2010-11-15 14:38:51 -080047import java.util.Locale;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080048import java.util.TimeZone;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049
Elliott Hughes659f1452013-04-30 11:11:53 -070050import libcore.icu.ICU;
51
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052/**
Alan Viveretted25eb9f2014-11-24 14:07:24 -080053 * Provides a widget for selecting a date.
54 * <p>
55 * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is
56 * set to {@code spinner}, the date can be selected using year, month, and day
57 * spinners or a {@link CalendarView}. The set of spinners and the calendar
58 * view are automatically synchronized. The client can customize whether only
59 * the spinners, or only the calendar view, or both to be displayed.
60 * </p>
61 * <p>
62 * When the {@link android.R.styleable#DatePicker_datePickerMode} attribute is
63 * set to {@code calendar}, the month and day can be selected using a
64 * calendar-style view while the year can be selected separately using a list.
65 * </p>
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080066 * <p>
Scott Main4c359b72012-07-24 15:51:27 -070067 * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
68 * guide.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080069 * </p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080070 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071 * For a dialog using this view, see {@link android.app.DatePickerDialog}.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080072 * </p>
73 *
74 * @attr ref android.R.styleable#DatePicker_startYear
75 * @attr ref android.R.styleable#DatePicker_endYear
76 * @attr ref android.R.styleable#DatePicker_maxDate
77 * @attr ref android.R.styleable#DatePicker_minDate
78 * @attr ref android.R.styleable#DatePicker_spinnersShown
79 * @attr ref android.R.styleable#DatePicker_calendarViewShown
Alan Viveretteba9bf412014-09-03 20:14:21 -070080 * @attr ref android.R.styleable#DatePicker_dayOfWeekBackground
Alan Viverette60727e02014-07-28 16:56:32 -070081 * @attr ref android.R.styleable#DatePicker_dayOfWeekTextAppearance
Alan Viveretteba9bf412014-09-03 20:14:21 -070082 * @attr ref android.R.styleable#DatePicker_headerBackground
Alan Viverette60727e02014-07-28 16:56:32 -070083 * @attr ref android.R.styleable#DatePicker_headerMonthTextAppearance
84 * @attr ref android.R.styleable#DatePicker_headerDayOfMonthTextAppearance
85 * @attr ref android.R.styleable#DatePicker_headerYearTextAppearance
86 * @attr ref android.R.styleable#DatePicker_yearListItemTextAppearance
87 * @attr ref android.R.styleable#DatePicker_yearListSelectorColor
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070088 * @attr ref android.R.styleable#DatePicker_calendarTextColor
Alan Viveretted25eb9f2014-11-24 14:07:24 -080089 * @attr ref android.R.styleable#DatePicker_datePickerMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 */
91@Widget
92public class DatePicker extends FrameLayout {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080093 private static final String LOG_TAG = DatePicker.class.getSimpleName();
94
Chet Haase3053b2f2014-08-06 07:51:50 -070095 private static final int MODE_SPINNER = 1;
96 private static final int MODE_CALENDAR = 2;
97
Alan Viverette60727e02014-07-28 16:56:32 -070098 private final DatePickerDelegate mDelegate;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070099
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800100 /**
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700101 * The callback used to indicate the user changed the date.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 */
103 public interface OnDateChangedListener {
104
105 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800106 * Called upon a date change.
107 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 * @param view The view associated with this listener.
109 * @param year The year that was set.
110 * @param monthOfYear The month that was set (0-11) for compatibility
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800111 * with {@link java.util.Calendar}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 * @param dayOfMonth The day of the month that was set.
113 */
114 void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
115 }
116
117 public DatePicker(Context context) {
118 this(context, null);
119 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800120
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 public DatePicker(Context context, AttributeSet attrs) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800122 this(context, attrs, R.attr.datePickerStyle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123 }
124
Alan Viverette617feb92013-09-09 18:09:13 -0700125 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) {
126 this(context, attrs, defStyleAttr, 0);
127 }
128
129 public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
130 super(context, attrs, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131
Alan Viverette60727e02014-07-28 16:56:32 -0700132 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
133 defStyleAttr, defStyleRes);
Alan Viverette518ff0d2014-08-15 14:20:35 -0700134 final int mode = a.getInt(R.styleable.DatePicker_datePickerMode, MODE_SPINNER);
Alan Viverette0a04bb02014-09-03 20:48:02 -0700135 final int firstDayOfWeek = a.getInt(R.styleable.DatePicker_firstDayOfWeek, 0);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700136 a.recycle();
137
Chet Haase3053b2f2014-08-06 07:51:50 -0700138 switch (mode) {
139 case MODE_CALENDAR:
140 mDelegate = createCalendarUIDelegate(context, attrs, defStyleAttr, defStyleRes);
141 break;
142 case MODE_SPINNER:
143 default:
144 mDelegate = createSpinnerUIDelegate(context, attrs, defStyleAttr, defStyleRes);
145 break;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700146 }
Alan Viverette0a04bb02014-09-03 20:48:02 -0700147
148 if (firstDayOfWeek != 0) {
149 setFirstDayOfWeek(firstDayOfWeek);
150 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700151 }
152
Chet Haase3053b2f2014-08-06 07:51:50 -0700153 private DatePickerDelegate createSpinnerUIDelegate(Context context, AttributeSet attrs,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700154 int defStyleAttr, int defStyleRes) {
Chet Haase3053b2f2014-08-06 07:51:50 -0700155 return new DatePickerSpinnerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700156 }
157
Chet Haase3053b2f2014-08-06 07:51:50 -0700158 private DatePickerDelegate createCalendarUIDelegate(Context context, AttributeSet attrs,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700159 int defStyleAttr, int defStyleRes) {
Chet Haase3053b2f2014-08-06 07:51:50 -0700160 return new DatePickerCalendarDelegate(this, context, attrs, defStyleAttr,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700161 defStyleRes);
162 }
163
164 /**
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800165 * Initialize the state. If the provided values designate an inconsistent
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800166 * date the values are normalized before updating the spinners.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800167 *
168 * @param year The initial year.
169 * @param monthOfYear The initial month <strong>starting from zero</strong>.
170 * @param dayOfMonth The initial day of the month.
171 * @param onDateChangedListener How user is notified date is changed by
172 * user, can be null.
173 */
174 public void init(int year, int monthOfYear, int dayOfMonth,
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700175 OnDateChangedListener onDateChangedListener) {
176 mDelegate.init(year, monthOfYear, dayOfMonth, onDateChangedListener);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800177 }
178
179 /**
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700180 * Update the current date.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800181 *
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700182 * @param year The year.
183 * @param month The month which is <strong>starting from zero</strong>.
184 * @param dayOfMonth The day of the month.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800185 */
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700186 public void updateDate(int year, int month, int dayOfMonth) {
187 mDelegate.updateDate(year, month, dayOfMonth);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800188 }
189
190 /**
191 * @return The selected year.
192 */
193 public int getYear() {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700194 return mDelegate.getYear();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800195 }
196
197 /**
198 * @return The selected month.
199 */
200 public int getMonth() {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700201 return mDelegate.getMonth();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800202 }
203
204 /**
205 * @return The selected day of month.
206 */
207 public int getDayOfMonth() {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700208 return mDelegate.getDayOfMonth();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800209 }
210
211 /**
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700212 * Gets the minimal date supported by this {@link DatePicker} in
213 * milliseconds since January 1, 1970 00:00:00 in
214 * {@link TimeZone#getDefault()} time zone.
215 * <p>
216 * Note: The default minimal date is 01/01/1900.
217 * <p>
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700218 *
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700219 * @return The minimal supported date.
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700220 */
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700221 public long getMinDate() {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700222 return mDelegate.getMinDate().getTimeInMillis();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700223 }
224
225 /**
226 * Sets the minimal date supported by this {@link NumberPicker} in
227 * milliseconds since January 1, 1970 00:00:00 in
228 * {@link TimeZone#getDefault()} time zone.
229 *
230 * @param minDate The minimal supported date.
231 */
232 public void setMinDate(long minDate) {
233 mDelegate.setMinDate(minDate);
234 }
235
236 /**
237 * Gets the maximal date supported by this {@link DatePicker} in
238 * milliseconds since January 1, 1970 00:00:00 in
239 * {@link TimeZone#getDefault()} time zone.
240 * <p>
241 * Note: The default maximal date is 12/31/2100.
242 * <p>
243 *
244 * @return The maximal supported date.
245 */
246 public long getMaxDate() {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700247 return mDelegate.getMaxDate().getTimeInMillis();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700248 }
249
250 /**
251 * Sets the maximal date supported by this {@link DatePicker} in
252 * milliseconds since January 1, 1970 00:00:00 in
253 * {@link TimeZone#getDefault()} time zone.
254 *
255 * @param maxDate The maximal supported date.
256 */
257 public void setMaxDate(long maxDate) {
258 mDelegate.setMaxDate(maxDate);
259 }
260
Alan Viverette518ff0d2014-08-15 14:20:35 -0700261 /**
262 * Sets the callback that indicates the current date is valid.
263 *
264 * @param callback the callback, may be null
265 * @hide
266 */
267 public void setValidationCallback(@Nullable ValidationCallback callback) {
268 mDelegate.setValidationCallback(callback);
269 }
270
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700271 @Override
272 public void setEnabled(boolean enabled) {
273 if (mDelegate.isEnabled() == enabled) {
274 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700275 }
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700276 super.setEnabled(enabled);
277 mDelegate.setEnabled(enabled);
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700278 }
279
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700280 @Override
281 public boolean isEnabled() {
282 return mDelegate.isEnabled();
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700283 }
284
Alan Viverettea54956a2015-01-07 16:05:02 -0800285 /** @hide */
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700286 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800287 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700288 return mDelegate.dispatchPopulateAccessibilityEvent(event);
289 }
290
Alan Viverettea54956a2015-01-07 16:05:02 -0800291 /** @hide */
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700292 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800293 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
294 super.onPopulateAccessibilityEventInternal(event);
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700295 mDelegate.onPopulateAccessibilityEvent(event);
296 }
297
298 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800299 public CharSequence getAccessibilityClassName() {
300 return DatePicker.class.getName();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700301 }
302
303 @Override
304 protected void onConfigurationChanged(Configuration newConfig) {
305 super.onConfigurationChanged(newConfig);
306 mDelegate.onConfigurationChanged(newConfig);
307 }
308
309 /**
Alan Viverette0a04bb02014-09-03 20:48:02 -0700310 * Sets the first day of week.
311 *
312 * @param firstDayOfWeek The first day of the week conforming to the
313 * {@link CalendarView} APIs.
314 * @see Calendar#SUNDAY
315 * @see Calendar#MONDAY
316 * @see Calendar#TUESDAY
317 * @see Calendar#WEDNESDAY
318 * @see Calendar#THURSDAY
319 * @see Calendar#FRIDAY
320 * @see Calendar#SATURDAY
321 *
322 * @attr ref android.R.styleable#DatePicker_firstDayOfWeek
323 */
324 public void setFirstDayOfWeek(int firstDayOfWeek) {
325 if (firstDayOfWeek < Calendar.SUNDAY || firstDayOfWeek > Calendar.SATURDAY) {
326 throw new IllegalArgumentException("firstDayOfWeek must be between 1 and 7");
327 }
328 mDelegate.setFirstDayOfWeek(firstDayOfWeek);
329 }
330
331 /**
332 * Gets the first day of week.
333 *
334 * @return The first day of the week conforming to the {@link CalendarView}
335 * APIs.
336 * @see Calendar#SUNDAY
337 * @see Calendar#MONDAY
338 * @see Calendar#TUESDAY
339 * @see Calendar#WEDNESDAY
340 * @see Calendar#THURSDAY
341 * @see Calendar#FRIDAY
342 * @see Calendar#SATURDAY
343 *
344 * @attr ref android.R.styleable#DatePicker_firstDayOfWeek
345 */
346 public int getFirstDayOfWeek() {
347 return mDelegate.getFirstDayOfWeek();
348 }
349
350 /**
Alan Viverette452fe342015-03-23 14:26:09 -0700351 * Returns whether the {@link CalendarView} is shown.
352 * <p>
353 * <strong>Note:</strong> This method returns {@code false} when the
354 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
355 * to {@code calendar}.
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700356 *
Alan Viverette452fe342015-03-23 14:26:09 -0700357 * @return {@code true} if the calendar view is shown
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700358 * @see #getCalendarView()
359 */
360 public boolean getCalendarViewShown() {
361 return mDelegate.getCalendarViewShown();
362 }
363
364 /**
Alan Viverette452fe342015-03-23 14:26:09 -0700365 * Returns the {@link CalendarView} used by this picker.
Alan Viveretted25eb9f2014-11-24 14:07:24 -0800366 * <p>
Alan Viverette452fe342015-03-23 14:26:09 -0700367 * <strong>Note:</strong> This method returns {@code null} when the
Alan Viveretted25eb9f2014-11-24 14:07:24 -0800368 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
369 * to {@code calendar}.
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700370 *
Alan Viverette452fe342015-03-23 14:26:09 -0700371 * @return the calendar view
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700372 * @see #getCalendarViewShown()
373 */
Alan Viverette0a04bb02014-09-03 20:48:02 -0700374 public CalendarView getCalendarView() {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700375 return mDelegate.getCalendarView();
376 }
377
378 /**
379 * Sets whether the {@link CalendarView} is shown.
Alan Viveretted25eb9f2014-11-24 14:07:24 -0800380 * <p>
Alan Viverette452fe342015-03-23 14:26:09 -0700381 * <strong>Note:</strong> Calling this method has no effect when the
Alan Viveretted25eb9f2014-11-24 14:07:24 -0800382 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
383 * to {@code calendar}.
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700384 *
Alan Viverette452fe342015-03-23 14:26:09 -0700385 * @param shown {@code true} to show the calendar view, {@code false} to
386 * hide it
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700387 */
388 public void setCalendarViewShown(boolean shown) {
389 mDelegate.setCalendarViewShown(shown);
390 }
391
392 /**
Alan Viverette452fe342015-03-23 14:26:09 -0700393 * Returns whether the spinners are shown.
394 * <p>
Alan Viverette835d7982015-03-23 14:54:14 -0700395 * <strong>Note:</strong> his method returns {@code false} when the
Alan Viverette452fe342015-03-23 14:26:09 -0700396 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
397 * to {@code calendar}.
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700398 *
Alan Viverette452fe342015-03-23 14:26:09 -0700399 * @return {@code true} if the spinners are shown
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700400 */
401 public boolean getSpinnersShown() {
402 return mDelegate.getSpinnersShown();
403 }
404
405 /**
406 * Sets whether the spinners are shown.
Alan Viverette452fe342015-03-23 14:26:09 -0700407 * <p>
408 * Calling this method has no effect when the
409 * {@link android.R.styleable#DatePicker_datePickerMode} attribute is set
410 * to {@code calendar}.
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700411 *
Alan Viverette452fe342015-03-23 14:26:09 -0700412 * @param shown {@code true} to show the spinners, {@code false} to hide
413 * them
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700414 */
415 public void setSpinnersShown(boolean shown) {
416 mDelegate.setSpinnersShown(shown);
417 }
418
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700419 @Override
420 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
Alan Viveretted015e342014-09-15 19:27:55 -0700421 dispatchThawSelfOnly(container);
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700422 }
423
424 @Override
425 protected Parcelable onSaveInstanceState() {
426 Parcelable superState = super.onSaveInstanceState();
427 return mDelegate.onSaveInstanceState(superState);
428 }
429
430 @Override
431 protected void onRestoreInstanceState(Parcelable state) {
Craig Mautnera67d9092014-09-16 15:38:47 -0700432 BaseSavedState ss = (BaseSavedState) state;
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700433 super.onRestoreInstanceState(ss.getSuperState());
434 mDelegate.onRestoreInstanceState(ss);
435 }
436
437 /**
438 * A delegate interface that defined the public API of the DatePicker. Allows different
439 * DatePicker implementations. This would need to be implemented by the DatePicker delegates
440 * for the real behavior.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700441 *
442 * @hide
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700443 */
444 interface DatePickerDelegate {
445 void init(int year, int monthOfYear, int dayOfMonth,
446 OnDateChangedListener onDateChangedListener);
447
448 void updateDate(int year, int month, int dayOfMonth);
449
450 int getYear();
451 int getMonth();
452 int getDayOfMonth();
453
Alan Viverette0a04bb02014-09-03 20:48:02 -0700454 void setFirstDayOfWeek(int firstDayOfWeek);
455 int getFirstDayOfWeek();
456
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700457 void setMinDate(long minDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700458 Calendar getMinDate();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700459
460 void setMaxDate(long maxDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700461 Calendar getMaxDate();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700462
463 void setEnabled(boolean enabled);
464 boolean isEnabled();
465
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700466 CalendarView getCalendarView();
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700467
468 void setCalendarViewShown(boolean shown);
469 boolean getCalendarViewShown();
470
471 void setSpinnersShown(boolean shown);
472 boolean getSpinnersShown();
473
Alan Viverette518ff0d2014-08-15 14:20:35 -0700474 void setValidationCallback(ValidationCallback callback);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700475
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700476 void onConfigurationChanged(Configuration newConfig);
477
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700478 Parcelable onSaveInstanceState(Parcelable superState);
479 void onRestoreInstanceState(Parcelable state);
480
481 boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
482 void onPopulateAccessibilityEvent(AccessibilityEvent event);
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700483 }
484
485 /**
486 * An abstract class which can be used as a start for DatePicker implementations
487 */
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700488 abstract static class AbstractDatePickerDelegate implements DatePickerDelegate {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700489 // The delegator
490 protected DatePicker mDelegator;
491
492 // The context
493 protected Context mContext;
494
495 // The current locale
496 protected Locale mCurrentLocale;
497
498 // Callbacks
Alan Viverette518ff0d2014-08-15 14:20:35 -0700499 protected OnDateChangedListener mOnDateChangedListener;
500 protected ValidationCallback mValidationCallback;
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700501
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700502 public AbstractDatePickerDelegate(DatePicker delegator, Context context) {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700503 mDelegator = delegator;
504 mContext = context;
505
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700506 setCurrentLocale(Locale.getDefault());
507 }
508
509 protected void setCurrentLocale(Locale locale) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700510 if (!locale.equals(mCurrentLocale)) {
511 mCurrentLocale = locale;
512 onLocaleChanged(locale);
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700513 }
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700514 }
Alan Viverette518ff0d2014-08-15 14:20:35 -0700515
516 @Override
517 public void setValidationCallback(ValidationCallback callback) {
518 mValidationCallback = callback;
519 }
520
521 protected void onValidationChanged(boolean valid) {
522 if (mValidationCallback != null) {
523 mValidationCallback.onValidationChanged(valid);
524 }
525 }
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700526
527 protected void onLocaleChanged(Locale locale) {
528 // Stub.
529 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700530 }
531
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700532 /**
Alan Viverette518ff0d2014-08-15 14:20:35 -0700533 * A callback interface for updating input validity when the date picker
534 * when included into a dialog.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700535 *
536 * @hide
537 */
Alan Viverette518ff0d2014-08-15 14:20:35 -0700538 public static interface ValidationCallback {
539 void onValidationChanged(boolean valid);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700540 }
541
542 /**
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700543 * A delegate implementing the basic DatePicker
544 */
Chet Haase3053b2f2014-08-06 07:51:50 -0700545 private static class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate {
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700546
547 private static final String DATE_FORMAT = "MM/dd/yyyy";
548
549 private static final int DEFAULT_START_YEAR = 1900;
550
551 private static final int DEFAULT_END_YEAR = 2100;
552
553 private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
554
555 private static final boolean DEFAULT_SPINNERS_SHOWN = true;
556
557 private static final boolean DEFAULT_ENABLED_STATE = true;
558
559 private final LinearLayout mSpinners;
560
561 private final NumberPicker mDaySpinner;
562
563 private final NumberPicker mMonthSpinner;
564
565 private final NumberPicker mYearSpinner;
566
567 private final EditText mDaySpinnerInput;
568
569 private final EditText mMonthSpinnerInput;
570
571 private final EditText mYearSpinnerInput;
572
573 private final CalendarView mCalendarView;
574
575 private String[] mShortMonths;
576
577 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
578
579 private int mNumberOfMonths;
580
581 private Calendar mTempDate;
582
583 private Calendar mMinDate;
584
585 private Calendar mMaxDate;
586
587 private Calendar mCurrentDate;
588
589 private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
590
Chet Haase3053b2f2014-08-06 07:51:50 -0700591 DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700592 int defStyleAttr, int defStyleRes) {
593 super(delegator, context);
594
595 mDelegator = delegator;
596 mContext = context;
597
598 // initialization based on locale
599 setCurrentLocale(Locale.getDefault());
600
601 final TypedArray attributesArray = context.obtainStyledAttributes(attrs,
602 R.styleable.DatePicker, defStyleAttr, defStyleRes);
603 boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown,
604 DEFAULT_SPINNERS_SHOWN);
605 boolean calendarViewShown = attributesArray.getBoolean(
606 R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
607 int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear,
608 DEFAULT_START_YEAR);
609 int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
610 String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
611 String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
612 int layoutResourceId = attributesArray.getResourceId(
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700613 R.styleable.DatePicker_legacyLayout, R.layout.date_picker_legacy);
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700614 attributesArray.recycle();
615
616 LayoutInflater inflater = (LayoutInflater) context
617 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
618 inflater.inflate(layoutResourceId, mDelegator, true);
619
620 OnValueChangeListener onChangeListener = new OnValueChangeListener() {
621 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
622 updateInputState();
623 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
624 // take care of wrapping of days and months to update greater fields
625 if (picker == mDaySpinner) {
626 int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
627 if (oldVal == maxDayOfMonth && newVal == 1) {
628 mTempDate.add(Calendar.DAY_OF_MONTH, 1);
629 } else if (oldVal == 1 && newVal == maxDayOfMonth) {
630 mTempDate.add(Calendar.DAY_OF_MONTH, -1);
631 } else {
632 mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
633 }
634 } else if (picker == mMonthSpinner) {
635 if (oldVal == 11 && newVal == 0) {
636 mTempDate.add(Calendar.MONTH, 1);
637 } else if (oldVal == 0 && newVal == 11) {
638 mTempDate.add(Calendar.MONTH, -1);
639 } else {
640 mTempDate.add(Calendar.MONTH, newVal - oldVal);
641 }
642 } else if (picker == mYearSpinner) {
643 mTempDate.set(Calendar.YEAR, newVal);
644 } else {
645 throw new IllegalArgumentException();
646 }
647 // now set the date to the adjusted one
648 setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
649 mTempDate.get(Calendar.DAY_OF_MONTH));
650 updateSpinners();
651 updateCalendarView();
652 notifyDateChanged();
653 }
654 };
655
656 mSpinners = (LinearLayout) mDelegator.findViewById(R.id.pickers);
657
658 // calendar view day-picker
659 mCalendarView = (CalendarView) mDelegator.findViewById(R.id.calendar_view);
660 mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
661 public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
662 setDate(year, month, monthDay);
663 updateSpinners();
664 notifyDateChanged();
665 }
666 });
667
668 // day
669 mDaySpinner = (NumberPicker) mDelegator.findViewById(R.id.day);
670 mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
671 mDaySpinner.setOnLongPressUpdateInterval(100);
672 mDaySpinner.setOnValueChangedListener(onChangeListener);
673 mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input);
674
675 // month
676 mMonthSpinner = (NumberPicker) mDelegator.findViewById(R.id.month);
677 mMonthSpinner.setMinValue(0);
678 mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
679 mMonthSpinner.setDisplayedValues(mShortMonths);
680 mMonthSpinner.setOnLongPressUpdateInterval(200);
681 mMonthSpinner.setOnValueChangedListener(onChangeListener);
682 mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input);
683
684 // year
685 mYearSpinner = (NumberPicker) mDelegator.findViewById(R.id.year);
686 mYearSpinner.setOnLongPressUpdateInterval(100);
687 mYearSpinner.setOnValueChangedListener(onChangeListener);
688 mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input);
689
690 // show only what the user required but make sure we
691 // show something and the spinners have higher priority
692 if (!spinnersShown && !calendarViewShown) {
693 setSpinnersShown(true);
694 } else {
695 setSpinnersShown(spinnersShown);
696 setCalendarViewShown(calendarViewShown);
697 }
698
699 // set the min date giving priority of the minDate over startYear
700 mTempDate.clear();
701 if (!TextUtils.isEmpty(minDate)) {
702 if (!parseDate(minDate, mTempDate)) {
703 mTempDate.set(startYear, 0, 1);
704 }
705 } else {
706 mTempDate.set(startYear, 0, 1);
707 }
708 setMinDate(mTempDate.getTimeInMillis());
709
710 // set the max date giving priority of the maxDate over endYear
711 mTempDate.clear();
712 if (!TextUtils.isEmpty(maxDate)) {
713 if (!parseDate(maxDate, mTempDate)) {
714 mTempDate.set(endYear, 11, 31);
715 }
716 } else {
717 mTempDate.set(endYear, 11, 31);
718 }
719 setMaxDate(mTempDate.getTimeInMillis());
720
721 // initialize to current date
722 mCurrentDate.setTimeInMillis(System.currentTimeMillis());
723 init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
724 .get(Calendar.DAY_OF_MONTH), null);
725
726 // re-order the number spinners to match the current date format
727 reorderSpinners();
728
729 // accessibility
730 setContentDescriptions();
731
732 // If not explicitly specified this view is important for accessibility.
733 if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
734 mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
735 }
736 }
737
738 @Override
739 public void init(int year, int monthOfYear, int dayOfMonth,
740 OnDateChangedListener onDateChangedListener) {
741 setDate(year, monthOfYear, dayOfMonth);
742 updateSpinners();
743 updateCalendarView();
744 mOnDateChangedListener = onDateChangedListener;
745 }
746
747 @Override
748 public void updateDate(int year, int month, int dayOfMonth) {
749 if (!isNewDate(year, month, dayOfMonth)) {
750 return;
751 }
752 setDate(year, month, dayOfMonth);
753 updateSpinners();
754 updateCalendarView();
755 notifyDateChanged();
756 }
757
758 @Override
759 public int getYear() {
760 return mCurrentDate.get(Calendar.YEAR);
761 }
762
763 @Override
764 public int getMonth() {
765 return mCurrentDate.get(Calendar.MONTH);
766 }
767
768 @Override
769 public int getDayOfMonth() {
770 return mCurrentDate.get(Calendar.DAY_OF_MONTH);
771 }
772
773 @Override
Alan Viverette0a04bb02014-09-03 20:48:02 -0700774 public void setFirstDayOfWeek(int firstDayOfWeek) {
775 mCalendarView.setFirstDayOfWeek(firstDayOfWeek);
776 }
777
778 @Override
779 public int getFirstDayOfWeek() {
780 return mCalendarView.getFirstDayOfWeek();
781 }
782
783 @Override
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700784 public void setMinDate(long minDate) {
785 mTempDate.setTimeInMillis(minDate);
786 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
787 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
788 return;
789 }
790 mMinDate.setTimeInMillis(minDate);
791 mCalendarView.setMinDate(minDate);
792 if (mCurrentDate.before(mMinDate)) {
793 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
794 updateCalendarView();
795 }
796 updateSpinners();
797 }
798
799 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700800 public Calendar getMinDate() {
801 final Calendar minDate = Calendar.getInstance();
802 minDate.setTimeInMillis(mCalendarView.getMinDate());
803 return minDate;
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700804 }
805
806 @Override
807 public void setMaxDate(long maxDate) {
808 mTempDate.setTimeInMillis(maxDate);
809 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
810 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
811 return;
812 }
813 mMaxDate.setTimeInMillis(maxDate);
814 mCalendarView.setMaxDate(maxDate);
815 if (mCurrentDate.after(mMaxDate)) {
816 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
817 updateCalendarView();
818 }
819 updateSpinners();
820 }
821
822 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700823 public Calendar getMaxDate() {
824 final Calendar maxDate = Calendar.getInstance();
825 maxDate.setTimeInMillis(mCalendarView.getMaxDate());
826 return maxDate;
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700827 }
828
829 @Override
830 public void setEnabled(boolean enabled) {
831 mDaySpinner.setEnabled(enabled);
832 mMonthSpinner.setEnabled(enabled);
833 mYearSpinner.setEnabled(enabled);
834 mCalendarView.setEnabled(enabled);
835 mIsEnabled = enabled;
836 }
837
838 @Override
839 public boolean isEnabled() {
840 return mIsEnabled;
841 }
842
843 @Override
844 public CalendarView getCalendarView() {
845 return mCalendarView;
846 }
847
848 @Override
849 public void setCalendarViewShown(boolean shown) {
850 mCalendarView.setVisibility(shown ? VISIBLE : GONE);
851 }
852
853 @Override
854 public boolean getCalendarViewShown() {
855 return (mCalendarView.getVisibility() == View.VISIBLE);
856 }
857
858 @Override
859 public void setSpinnersShown(boolean shown) {
860 mSpinners.setVisibility(shown ? VISIBLE : GONE);
861 }
862
863 @Override
864 public boolean getSpinnersShown() {
865 return mSpinners.isShown();
866 }
867
868 @Override
869 public void onConfigurationChanged(Configuration newConfig) {
870 setCurrentLocale(newConfig.locale);
871 }
872
873 @Override
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700874 public Parcelable onSaveInstanceState(Parcelable superState) {
875 return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
876 }
877
878 @Override
879 public void onRestoreInstanceState(Parcelable state) {
880 SavedState ss = (SavedState) state;
881 setDate(ss.mYear, ss.mMonth, ss.mDay);
882 updateSpinners();
883 updateCalendarView();
884 }
885
886 @Override
887 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
888 onPopulateAccessibilityEvent(event);
889 return true;
890 }
891
892 @Override
893 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
894 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
895 String selectedDateUtterance = DateUtils.formatDateTime(mContext,
896 mCurrentDate.getTimeInMillis(), flags);
897 event.getText().add(selectedDateUtterance);
898 }
899
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700900 /**
901 * Sets the current locale.
902 *
903 * @param locale The current locale.
904 */
905 @Override
906 protected void setCurrentLocale(Locale locale) {
907 super.setCurrentLocale(locale);
908
909 mTempDate = getCalendarForLocale(mTempDate, locale);
910 mMinDate = getCalendarForLocale(mMinDate, locale);
911 mMaxDate = getCalendarForLocale(mMaxDate, locale);
912 mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
913
914 mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
915 mShortMonths = new DateFormatSymbols().getShortMonths();
916
917 if (usingNumericMonths()) {
918 // We're in a locale where a date should either be all-numeric, or all-text.
919 // All-text would require custom NumberPicker formatters for day and year.
920 mShortMonths = new String[mNumberOfMonths];
921 for (int i = 0; i < mNumberOfMonths; ++i) {
922 mShortMonths[i] = String.format("%d", i + 1);
923 }
924 }
925 }
926
927 /**
928 * Tests whether the current locale is one where there are no real month names,
929 * such as Chinese, Japanese, or Korean locales.
930 */
931 private boolean usingNumericMonths() {
932 return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0));
933 }
934
935 /**
936 * Gets a calendar for locale bootstrapped with the value of a given calendar.
937 *
938 * @param oldCalendar The old calendar.
939 * @param locale The locale.
940 */
941 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
942 if (oldCalendar == null) {
943 return Calendar.getInstance(locale);
944 } else {
945 final long currentTimeMillis = oldCalendar.getTimeInMillis();
946 Calendar newCalendar = Calendar.getInstance(locale);
947 newCalendar.setTimeInMillis(currentTimeMillis);
948 return newCalendar;
949 }
950 }
951
952 /**
953 * Reorders the spinners according to the date format that is
954 * explicitly set by the user and if no such is set fall back
955 * to the current locale's default format.
956 */
957 private void reorderSpinners() {
958 mSpinners.removeAllViews();
959 // We use numeric spinners for year and day, but textual months. Ask icu4c what
960 // order the user's locale uses for that combination. http://b/7207103.
Elliott Hughesf8abeb82014-05-30 20:13:15 -0700961 String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
Fabrice Di Meglio039a7842013-08-28 17:41:26 -0700962 char[] order = ICU.getDateFormatOrder(pattern);
963 final int spinnerCount = order.length;
964 for (int i = 0; i < spinnerCount; i++) {
965 switch (order[i]) {
966 case 'd':
967 mSpinners.addView(mDaySpinner);
968 setImeOptions(mDaySpinner, spinnerCount, i);
969 break;
970 case 'M':
971 mSpinners.addView(mMonthSpinner);
972 setImeOptions(mMonthSpinner, spinnerCount, i);
973 break;
974 case 'y':
975 mSpinners.addView(mYearSpinner);
976 setImeOptions(mYearSpinner, spinnerCount, i);
977 break;
978 default:
979 throw new IllegalArgumentException(Arrays.toString(order));
980 }
981 }
982 }
983
984 /**
985 * Parses the given <code>date</code> and in case of success sets the result
986 * to the <code>outDate</code>.
987 *
988 * @return True if the date was parsed.
989 */
990 private boolean parseDate(String date, Calendar outDate) {
991 try {
992 outDate.setTime(mDateFormat.parse(date));
993 return true;
994 } catch (ParseException e) {
995 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
996 return false;
997 }
998 }
999
1000 private boolean isNewDate(int year, int month, int dayOfMonth) {
1001 return (mCurrentDate.get(Calendar.YEAR) != year
Philipp Hasper81fd6b42015-06-25 12:10:01 +02001002 || mCurrentDate.get(Calendar.MONTH) != month
1003 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != dayOfMonth);
Fabrice Di Meglio039a7842013-08-28 17:41:26 -07001004 }
1005
1006 private void setDate(int year, int month, int dayOfMonth) {
1007 mCurrentDate.set(year, month, dayOfMonth);
1008 if (mCurrentDate.before(mMinDate)) {
1009 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
1010 } else if (mCurrentDate.after(mMaxDate)) {
1011 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
1012 }
1013 }
1014
1015 private void updateSpinners() {
1016 // set the spinner ranges respecting the min and max dates
1017 if (mCurrentDate.equals(mMinDate)) {
1018 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
1019 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
1020 mDaySpinner.setWrapSelectorWheel(false);
1021 mMonthSpinner.setDisplayedValues(null);
1022 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
1023 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
1024 mMonthSpinner.setWrapSelectorWheel(false);
1025 } else if (mCurrentDate.equals(mMaxDate)) {
1026 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
1027 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
1028 mDaySpinner.setWrapSelectorWheel(false);
1029 mMonthSpinner.setDisplayedValues(null);
1030 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
1031 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
1032 mMonthSpinner.setWrapSelectorWheel(false);
1033 } else {
1034 mDaySpinner.setMinValue(1);
1035 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
1036 mDaySpinner.setWrapSelectorWheel(true);
1037 mMonthSpinner.setDisplayedValues(null);
1038 mMonthSpinner.setMinValue(0);
1039 mMonthSpinner.setMaxValue(11);
1040 mMonthSpinner.setWrapSelectorWheel(true);
1041 }
1042
1043 // make sure the month names are a zero based array
1044 // with the months in the month spinner
1045 String[] displayedValues = Arrays.copyOfRange(mShortMonths,
1046 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
1047 mMonthSpinner.setDisplayedValues(displayedValues);
1048
1049 // year spinner range does not change based on the current date
1050 mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
1051 mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
1052 mYearSpinner.setWrapSelectorWheel(false);
1053
1054 // set the spinner values
1055 mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
1056 mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
1057 mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
1058
1059 if (usingNumericMonths()) {
1060 mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER);
1061 }
1062 }
1063
1064 /**
1065 * Updates the calendar view with the current date.
1066 */
1067 private void updateCalendarView() {
1068 mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
1069 }
1070
1071
1072 /**
1073 * Notifies the listener, if such, for a change in the selected date.
1074 */
1075 private void notifyDateChanged() {
1076 mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1077 if (mOnDateChangedListener != null) {
1078 mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(),
1079 getDayOfMonth());
1080 }
1081 }
1082
1083 /**
1084 * Sets the IME options for a spinner based on its ordering.
1085 *
1086 * @param spinner The spinner.
1087 * @param spinnerCount The total spinner count.
1088 * @param spinnerIndex The index of the given spinner.
1089 */
1090 private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
1091 final int imeOptions;
1092 if (spinnerIndex < spinnerCount - 1) {
1093 imeOptions = EditorInfo.IME_ACTION_NEXT;
1094 } else {
1095 imeOptions = EditorInfo.IME_ACTION_DONE;
1096 }
1097 TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input);
1098 input.setImeOptions(imeOptions);
1099 }
1100
1101 private void setContentDescriptions() {
1102 // Day
1103 trySetContentDescription(mDaySpinner, R.id.increment,
1104 R.string.date_picker_increment_day_button);
1105 trySetContentDescription(mDaySpinner, R.id.decrement,
1106 R.string.date_picker_decrement_day_button);
1107 // Month
1108 trySetContentDescription(mMonthSpinner, R.id.increment,
1109 R.string.date_picker_increment_month_button);
1110 trySetContentDescription(mMonthSpinner, R.id.decrement,
1111 R.string.date_picker_decrement_month_button);
1112 // Year
1113 trySetContentDescription(mYearSpinner, R.id.increment,
1114 R.string.date_picker_increment_year_button);
1115 trySetContentDescription(mYearSpinner, R.id.decrement,
1116 R.string.date_picker_decrement_year_button);
1117 }
1118
1119 private void trySetContentDescription(View root, int viewId, int contDescResId) {
1120 View target = root.findViewById(viewId);
1121 if (target != null) {
1122 target.setContentDescription(mContext.getString(contDescResId));
1123 }
1124 }
1125
1126 private void updateInputState() {
1127 // Make sure that if the user changes the value and the IME is active
1128 // for one of the inputs if this widget, the IME is closed. If the user
1129 // changed the value via the IME and there is a next input the IME will
1130 // be shown, otherwise the user chose another means of changing the
1131 // value and having the IME up makes no sense.
1132 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
1133 if (inputMethodManager != null) {
1134 if (inputMethodManager.isActive(mYearSpinnerInput)) {
1135 mYearSpinnerInput.clearFocus();
1136 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
1137 } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
1138 mMonthSpinnerInput.clearFocus();
1139 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
1140 } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
1141 mDaySpinnerInput.clearFocus();
1142 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
1143 }
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -07001144 }
1145 }
1146 }
1147
Svetoslav Ganova53efe92011-09-08 18:08:36 -07001148 /**
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001149 * Class for managing state storing/restoring.
1150 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001151 private static class SavedState extends BaseSavedState {
1152
1153 private final int mYear;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001154
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155 private final int mMonth;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001156
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001157 private final int mDay;
1158
1159 /**
1160 * Constructor called from {@link DatePicker#onSaveInstanceState()}
1161 */
1162 private SavedState(Parcelable superState, int year, int month, int day) {
1163 super(superState);
1164 mYear = year;
1165 mMonth = month;
1166 mDay = day;
1167 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001168
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169 /**
1170 * Constructor called from {@link #CREATOR}
1171 */
1172 private SavedState(Parcel in) {
1173 super(in);
1174 mYear = in.readInt();
1175 mMonth = in.readInt();
1176 mDay = in.readInt();
1177 }
1178
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001179 @Override
1180 public void writeToParcel(Parcel dest, int flags) {
1181 super.writeToParcel(dest, flags);
1182 dest.writeInt(mYear);
1183 dest.writeInt(mMonth);
1184 dest.writeInt(mDay);
1185 }
1186
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001187 @SuppressWarnings("all")
1188 // suppress unused and hiding
1189 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001190
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001191 public SavedState createFromParcel(Parcel in) {
1192 return new SavedState(in);
1193 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194
Svetoslav Ganov50f34d12010-12-03 16:05:40 -08001195 public SavedState[] newArray(int size) {
1196 return new SavedState[size];
1197 }
1198 };
Kenneth Anderssone3491b62010-03-05 09:16:24 +01001199 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -07001200}