blob: 108b7200f0b55269af09ec8dd20dedd7112382f5 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.annotation.Widget;
20import android.content.Context;
Svetoslav Ganovf5926962011-07-12 12:26:20 -070021import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.res.TypedArray;
23import android.os.Parcel;
24import android.os.Parcelable;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080025import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.text.format.DateFormat;
Kenny Rootdddda8d2010-11-15 14:38:51 -080027import android.text.format.DateUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.util.AttributeSet;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080029import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.util.SparseArray;
31import android.view.LayoutInflater;
Svetoslav Ganovd11e6152012-03-20 12:13:02 -070032import android.view.View;
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -080033import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080034import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav Ganova53efe92011-09-08 18:08:36 -070035import android.view.inputmethod.EditorInfo;
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -070036import android.view.inputmethod.InputMethodManager;
Svetoslav Ganovcedc4462011-01-19 19:25:46 -080037import android.widget.NumberPicker.OnValueChangeListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038
Svetoslav Ganovf5926962011-07-12 12:26:20 -070039import com.android.internal.R;
40
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080041import java.text.ParseException;
Eric Fischer03a80172009-07-23 18:32:42 -070042import java.text.SimpleDateFormat;
Svetoslav Ganov156f2092011-01-24 02:13:36 -080043import java.util.Arrays;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import java.util.Calendar;
Kenny Rootdddda8d2010-11-15 14:38:51 -080045import java.util.Locale;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080046import java.util.TimeZone;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047
48/**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080049 * This class is a widget for selecting a date. The date can be selected by a
50 * year, month, and day spinners or a {@link CalendarView}. The set of spinners
51 * and the calendar view are automatically synchronized. The client can
52 * customize whether only the spinners, or only the calendar view, or both to be
53 * displayed. Also the minimal and maximal date from which dates to be selected
54 * can be customized.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080055 * <p>
Dirk Dougherty9a143e62011-01-17 20:09:46 -080056 * See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date
57 * Picker tutorial</a>.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080058 * </p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080059 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 * For a dialog using this view, see {@link android.app.DatePickerDialog}.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080061 * </p>
62 *
63 * @attr ref android.R.styleable#DatePicker_startYear
64 * @attr ref android.R.styleable#DatePicker_endYear
65 * @attr ref android.R.styleable#DatePicker_maxDate
66 * @attr ref android.R.styleable#DatePicker_minDate
67 * @attr ref android.R.styleable#DatePicker_spinnersShown
68 * @attr ref android.R.styleable#DatePicker_calendarViewShown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069 */
70@Widget
71public class DatePicker extends FrameLayout {
72
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080073 private static final String LOG_TAG = DatePicker.class.getSimpleName();
74
75 private static final String DATE_FORMAT = "MM/dd/yyyy";
76
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 private static final int DEFAULT_START_YEAR = 1900;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080078
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 private static final int DEFAULT_END_YEAR = 2100;
Kenny Rootdddda8d2010-11-15 14:38:51 -080080
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080081 private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080082
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080083 private static final boolean DEFAULT_SPINNERS_SHOWN = true;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -080084
Svetoslav Ganov25f84f32010-12-29 22:39:54 -080085 private static final boolean DEFAULT_ENABLED_STATE = true;
86
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080087 private final LinearLayout mSpinners;
88
Svetoslav Ganova53efe92011-09-08 18:08:36 -070089 private final NumberPicker mDaySpinner;
90
Svetoslav Ganove9730bf2010-12-20 21:25:20 -080091 private final NumberPicker mMonthSpinner;
92
93 private final NumberPicker mYearSpinner;
94
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -070095 private final EditText mDaySpinnerInput;
96
97 private final EditText mMonthSpinnerInput;
98
99 private final EditText mYearSpinnerInput;
100
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800101 private final CalendarView mCalendarView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700103 private Locale mCurrentLocale;
104
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800105 private OnDateChangedListener mOnDateChangedListener;
106
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700107 private String[] mShortMonths;
Kenny Rootdddda8d2010-11-15 14:38:51 -0800108
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800109 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
110
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700111 private int mNumberOfMonths;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800112
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700113 private Calendar mTempDate;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800114
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700115 private Calendar mMinDate;
116
117 private Calendar mMaxDate;
118
119 private Calendar mCurrentDate;
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800120
Svetoslav Ganov25f84f32010-12-29 22:39:54 -0800121 private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800122
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800124 * The callback used to indicate the user changes\d the date.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 */
126 public interface OnDateChangedListener {
127
128 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800129 * Called upon a date change.
130 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 * @param view The view associated with this listener.
132 * @param year The year that was set.
133 * @param monthOfYear The month that was set (0-11) for compatibility
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800134 * with {@link java.util.Calendar}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135 * @param dayOfMonth The day of the month that was set.
136 */
137 void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth);
138 }
139
140 public DatePicker(Context context) {
141 this(context, null);
142 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800143
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 public DatePicker(Context context, AttributeSet attrs) {
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800145 this(context, attrs, R.attr.datePickerStyle);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 }
147
148 public DatePicker(Context context, AttributeSet attrs, int defStyle) {
149 super(context, attrs, defStyle);
150
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700151 // initialization based on locale
152 setCurrentLocale(Locale.getDefault());
153
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800154 TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
155 defStyle, 0);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800156 boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown,
157 DEFAULT_SPINNERS_SHOWN);
158 boolean calendarViewShown = attributesArray.getBoolean(
159 R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800160 int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear,
161 DEFAULT_START_YEAR);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800162 int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
163 String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
164 String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
Svetoslav Ganovc49a8be2012-03-02 13:39:25 -0800165 int layoutResourceId = attributesArray.getResourceId(R.styleable.DatePicker_internalLayout,
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800166 R.layout.date_picker);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800167 attributesArray.recycle();
168
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800169 LayoutInflater inflater = (LayoutInflater) context
170 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Svetoslav Ganov4243dc32011-01-18 19:39:57 -0800171 inflater.inflate(layoutResourceId, this, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172
Svetoslav Ganovcedc4462011-01-19 19:25:46 -0800173 OnValueChangeListener onChangeListener = new OnValueChangeListener() {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800174 public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700175 updateInputState();
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800176 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
177 // take care of wrapping of days and months to update greater fields
178 if (picker == mDaySpinner) {
179 int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
180 if (oldVal == maxDayOfMonth && newVal == 1) {
181 mTempDate.add(Calendar.DAY_OF_MONTH, 1);
182 } else if (oldVal == 1 && newVal == maxDayOfMonth) {
183 mTempDate.add(Calendar.DAY_OF_MONTH, -1);
184 } else {
185 mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
186 }
187 } else if (picker == mMonthSpinner) {
188 if (oldVal == 11 && newVal == 0) {
189 mTempDate.add(Calendar.MONTH, 1);
190 } else if (oldVal == 0 && newVal == 11) {
191 mTempDate.add(Calendar.MONTH, -1);
192 } else {
193 mTempDate.add(Calendar.MONTH, newVal - oldVal);
194 }
195 } else if (picker == mYearSpinner) {
196 mTempDate.set(Calendar.YEAR, newVal);
197 } else {
198 throw new IllegalArgumentException();
199 }
200 // now set the date to the adjusted one
201 setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
202 mTempDate.get(Calendar.DAY_OF_MONTH));
Svetoslav Ganov58f51252011-01-26 22:50:51 -0800203 updateSpinners();
204 updateCalendarView();
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800205 notifyDateChanged();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800206 }
207 };
208
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800209 mSpinners = (LinearLayout) findViewById(R.id.pickers);
210
211 // calendar view day-picker
212 mCalendarView = (CalendarView) findViewById(R.id.calendar_view);
213 mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
214 public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800215 setDate(year, month, monthDay);
Svetoslav Ganov58f51252011-01-26 22:50:51 -0800216 updateSpinners();
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800217 notifyDateChanged();
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800218 }
219 });
220
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800221 // day
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800222 mDaySpinner = (NumberPicker) findViewById(R.id.day);
223 mDaySpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
224 mDaySpinner.setOnLongPressUpdateInterval(100);
225 mDaySpinner.setOnValueChangedListener(onChangeListener);
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700226 mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800227
228 // month
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800229 mMonthSpinner = (NumberPicker) findViewById(R.id.month);
230 mMonthSpinner.setMinValue(0);
231 mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700232 mMonthSpinner.setDisplayedValues(mShortMonths);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800233 mMonthSpinner.setOnLongPressUpdateInterval(200);
234 mMonthSpinner.setOnValueChangedListener(onChangeListener);
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700235 mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800236
237 // year
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800238 mYearSpinner = (NumberPicker) findViewById(R.id.year);
239 mYearSpinner.setOnLongPressUpdateInterval(100);
240 mYearSpinner.setOnValueChangedListener(onChangeListener);
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700241 mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800242
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800243 // show only what the user required but make sure we
244 // show something and the spinners have higher priority
245 if (!spinnersShown && !calendarViewShown) {
246 setSpinnersShown(true);
247 } else {
248 setSpinnersShown(spinnersShown);
249 setCalendarViewShown(calendarViewShown);
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800250 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800251
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800252 // set the min date giving priority of the minDate over startYear
253 mTempDate.clear();
254 if (!TextUtils.isEmpty(minDate)) {
255 if (!parseDate(minDate, mTempDate)) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800256 mTempDate.set(startYear, 0, 1);
257 }
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800258 } else {
259 mTempDate.set(startYear, 0, 1);
260 }
261 setMinDate(mTempDate.getTimeInMillis());
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800262
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800263 // set the max date giving priority of the maxDate over endYear
264 mTempDate.clear();
265 if (!TextUtils.isEmpty(maxDate)) {
266 if (!parseDate(maxDate, mTempDate)) {
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800267 mTempDate.set(endYear, 11, 31);
268 }
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800269 } else {
270 mTempDate.set(endYear, 11, 31);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800271 }
Svetoslav Ganov13427a02011-01-31 16:37:05 -0800272 setMaxDate(mTempDate.getTimeInMillis());
273
274 // initialize to current date
275 mCurrentDate.setTimeInMillis(System.currentTimeMillis());
276 init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
277 .get(Calendar.DAY_OF_MONTH), null);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800278
279 // re-order the number spinners to match the current date format
280 reorderSpinners();
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -0700281
Svetoslav Ganov42138042012-03-20 11:51:39 -0700282 // accessibility
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700283 setContentDescriptions();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700284
285 // If not explicitly specified this view is important for accessibility.
286 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
287 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
288 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800290
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800291 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800292 * Gets the minimal date supported by this {@link DatePicker} in
293 * milliseconds since January 1, 1970 00:00:00 in
294 * {@link TimeZone#getDefault()} time zone.
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800295 * <p>
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800296 * Note: The default minimal date is 01/01/1900.
297 * <p>
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800298 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800299 * @return The minimal supported date.
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800300 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800301 public long getMinDate() {
302 return mCalendarView.getMinDate();
303 }
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800304
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800305 /**
306 * Sets the minimal date supported by this {@link NumberPicker} in
307 * milliseconds since January 1, 1970 00:00:00 in
308 * {@link TimeZone#getDefault()} time zone.
309 *
310 * @param minDate The minimal supported date.
311 */
312 public void setMinDate(long minDate) {
313 mTempDate.setTimeInMillis(minDate);
314 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
315 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
316 return;
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800317 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800318 mMinDate.setTimeInMillis(minDate);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800319 mCalendarView.setMinDate(minDate);
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800320 if (mCurrentDate.before(mMinDate)) {
321 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
322 updateCalendarView();
323 }
324 updateSpinners();
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800325 }
326
327 /**
328 * Gets the maximal date supported by this {@link DatePicker} in
329 * milliseconds since January 1, 1970 00:00:00 in
330 * {@link TimeZone#getDefault()} time zone.
331 * <p>
332 * Note: The default maximal date is 12/31/2100.
333 * <p>
334 *
335 * @return The maximal supported date.
336 */
337 public long getMaxDate() {
338 return mCalendarView.getMaxDate();
339 }
340
341 /**
342 * Sets the maximal date supported by this {@link DatePicker} in
343 * milliseconds since January 1, 1970 00:00:00 in
344 * {@link TimeZone#getDefault()} time zone.
345 *
346 * @param maxDate The maximal supported date.
347 */
348 public void setMaxDate(long maxDate) {
349 mTempDate.setTimeInMillis(maxDate);
350 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
351 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
352 return;
353 }
354 mMaxDate.setTimeInMillis(maxDate);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800355 mCalendarView.setMaxDate(maxDate);
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800356 if (mCurrentDate.after(mMaxDate)) {
357 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
358 updateCalendarView();
359 }
360 updateSpinners();
Svetoslav Ganov28104e12010-12-19 16:03:07 -0800361 }
362
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363 @Override
364 public void setEnabled(boolean enabled) {
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800365 if (mIsEnabled == enabled) {
366 return;
367 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 super.setEnabled(enabled);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800369 mDaySpinner.setEnabled(enabled);
370 mMonthSpinner.setEnabled(enabled);
371 mYearSpinner.setEnabled(enabled);
372 mCalendarView.setEnabled(enabled);
Svetoslav Ganov51c52ed2010-12-28 13:45:03 -0800373 mIsEnabled = enabled;
374 }
375
376 @Override
377 public boolean isEnabled() {
378 return mIsEnabled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 }
380
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800381 @Override
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -0700382 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
383 onPopulateAccessibilityEvent(event);
384 return true;
385 }
386
387 @Override
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700388 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
389 super.onPopulateAccessibilityEvent(event);
390
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -0700391 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800392 String selectedDateUtterance = DateUtils.formatDateTime(mContext,
393 mCurrentDate.getTimeInMillis(), flags);
394 event.getText().add(selectedDateUtterance);
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800395 }
396
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700397 @Override
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800398 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
399 super.onInitializeAccessibilityEvent(event);
400 event.setClassName(DatePicker.class.getName());
401 }
402
403 @Override
404 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
405 super.onInitializeAccessibilityNodeInfo(info);
406 info.setClassName(DatePicker.class.getName());
407 }
408
409 @Override
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700410 protected void onConfigurationChanged(Configuration newConfig) {
411 super.onConfigurationChanged(newConfig);
412 setCurrentLocale(newConfig.locale);
413 }
414
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800415 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800416 * Gets whether the {@link CalendarView} is shown.
417 *
418 * @return True if the calendar view is shown.
Svetoslav Ganov5f3f6ce2011-02-24 15:36:13 -0800419 * @see #getCalendarView()
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800420 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800421 public boolean getCalendarViewShown() {
422 return mCalendarView.isShown();
423 }
424
425 /**
Svetoslav Ganov5f3f6ce2011-02-24 15:36:13 -0800426 * Gets the {@link CalendarView}.
427 *
428 * @return The calendar view.
429 * @see #getCalendarViewShown()
430 */
431 public CalendarView getCalendarView () {
432 return mCalendarView;
433 }
434
435 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800436 * Sets whether the {@link CalendarView} is shown.
437 *
438 * @param shown True if the calendar view is to be shown.
439 */
440 public void setCalendarViewShown(boolean shown) {
441 mCalendarView.setVisibility(shown ? VISIBLE : GONE);
442 }
443
444 /**
445 * Gets whether the spinners are shown.
446 *
447 * @return True if the spinners are shown.
448 */
449 public boolean getSpinnersShown() {
450 return mSpinners.isShown();
451 }
452
453 /**
454 * Sets whether the spinners are shown.
455 *
456 * @param shown True if the spinners are to be shown.
457 */
458 public void setSpinnersShown(boolean shown) {
459 mSpinners.setVisibility(shown ? VISIBLE : GONE);
460 }
461
462 /**
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700463 * Sets the current locale.
464 *
465 * @param locale The current locale.
466 */
467 private void setCurrentLocale(Locale locale) {
468 if (locale.equals(mCurrentLocale)) {
469 return;
470 }
471
472 mCurrentLocale = locale;
473
474 mTempDate = getCalendarForLocale(mTempDate, locale);
475 mMinDate = getCalendarForLocale(mMinDate, locale);
476 mMaxDate = getCalendarForLocale(mMaxDate, locale);
477 mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
478
479 mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
480 mShortMonths = new String[mNumberOfMonths];
481 for (int i = 0; i < mNumberOfMonths; i++) {
482 mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
483 DateUtils.LENGTH_MEDIUM);
484 }
485 }
486
487 /**
488 * Gets a calendar for locale bootstrapped with the value of a given calendar.
489 *
490 * @param oldCalendar The old calendar.
491 * @param locale The locale.
492 */
493 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
494 if (oldCalendar == null) {
495 return Calendar.getInstance(locale);
496 } else {
497 final long currentTimeMillis = oldCalendar.getTimeInMillis();
498 Calendar newCalendar = Calendar.getInstance(locale);
499 newCalendar.setTimeInMillis(currentTimeMillis);
500 return newCalendar;
501 }
502 }
503
504 /**
Svetoslav Ganovf583dd32011-03-18 14:41:50 -0700505 * Reorders the spinners according to the date format that is
506 * explicitly set by the user and if no such is set fall back
507 * to the current locale's default format.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800508 */
509 private void reorderSpinners() {
Svetoslav Ganovf583dd32011-03-18 14:41:50 -0700510 mSpinners.removeAllViews();
511 char[] order = DateFormat.getDateFormatOrder(getContext());
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700512 final int spinnerCount = order.length;
513 for (int i = 0; i < spinnerCount; i++) {
Svetoslav Ganovf583dd32011-03-18 14:41:50 -0700514 switch (order[i]) {
515 case DateFormat.DATE:
516 mSpinners.addView(mDaySpinner);
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700517 setImeOptions(mDaySpinner, spinnerCount, i);
Svetoslav Ganovf583dd32011-03-18 14:41:50 -0700518 break;
519 case DateFormat.MONTH:
520 mSpinners.addView(mMonthSpinner);
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700521 setImeOptions(mMonthSpinner, spinnerCount, i);
Svetoslav Ganovf583dd32011-03-18 14:41:50 -0700522 break;
523 case DateFormat.YEAR:
524 mSpinners.addView(mYearSpinner);
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700525 setImeOptions(mYearSpinner, spinnerCount, i);
Svetoslav Ganovf583dd32011-03-18 14:41:50 -0700526 break;
527 default:
528 throw new IllegalArgumentException();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 }
530 }
531 }
532
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800533 /**
534 * Updates the current date.
535 *
536 * @param year The year.
537 * @param month The month which is <strong>starting from zero</strong>.
538 * @param dayOfMonth The day of the month.
539 */
540 public void updateDate(int year, int month, int dayOfMonth) {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800541 if (!isNewDate(year, month, dayOfMonth)) {
542 return;
Kenny Rootdddda8d2010-11-15 14:38:51 -0800543 }
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800544 setDate(year, month, dayOfMonth);
Svetoslav Ganov58f51252011-01-26 22:50:51 -0800545 updateSpinners();
546 updateCalendarView();
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800547 notifyDateChanged();
Kenny Rootdddda8d2010-11-15 14:38:51 -0800548 }
549
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800550 // Override so we are in complete control of save / restore for this widget.
551 @Override
552 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
553 dispatchThawSelfOnly(container);
554 }
555
556 @Override
557 protected Parcelable onSaveInstanceState() {
558 Parcelable superState = super.onSaveInstanceState();
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800559 return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800560 }
561
562 @Override
563 protected void onRestoreInstanceState(Parcelable state) {
564 SavedState ss = (SavedState) state;
565 super.onRestoreInstanceState(ss.getSuperState());
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800566 setDate(ss.mYear, ss.mMonth, ss.mDay);
Svetoslav Ganov58f51252011-01-26 22:50:51 -0800567 updateSpinners();
568 updateCalendarView();
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800569 }
570
571 /**
572 * Initialize the state. If the provided values designate an inconsistent
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800573 * date the values are normalized before updating the spinners.
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800574 *
575 * @param year The initial year.
576 * @param monthOfYear The initial month <strong>starting from zero</strong>.
577 * @param dayOfMonth The initial day of the month.
578 * @param onDateChangedListener How user is notified date is changed by
579 * user, can be null.
580 */
581 public void init(int year, int monthOfYear, int dayOfMonth,
582 OnDateChangedListener onDateChangedListener) {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800583 setDate(year, monthOfYear, dayOfMonth);
Svetoslav Ganov58f51252011-01-26 22:50:51 -0800584 updateSpinners();
585 updateCalendarView();
Svetoslav Ganov2f136a82011-01-13 11:33:05 -0800586 mOnDateChangedListener = onDateChangedListener;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800587 }
588
589 /**
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800590 * Parses the given <code>date</code> and in case of success sets the result
591 * to the <code>outDate</code>.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800592 *
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800593 * @return True if the date was parsed.
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800594 */
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800595 private boolean parseDate(String date, Calendar outDate) {
596 try {
597 outDate.setTime(mDateFormat.parse(date));
598 return true;
599 } catch (ParseException e) {
600 Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
601 return false;
602 }
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800603 }
604
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800605 private boolean isNewDate(int year, int month, int dayOfMonth) {
606 return (mCurrentDate.get(Calendar.YEAR) != year
607 || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
608 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
609 }
Svetoslav Ganova911d4a2010-12-08 16:11:30 -0800610
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800611 private void setDate(int year, int month, int dayOfMonth) {
612 mCurrentDate.set(year, month, dayOfMonth);
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800613 if (mCurrentDate.before(mMinDate)) {
614 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
615 } else if (mCurrentDate.after(mMaxDate)) {
616 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
617 }
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800618 }
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800619
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800620 private void updateSpinners() {
621 // set the spinner ranges respecting the min and max dates
622 if (mCurrentDate.equals(mMinDate)) {
623 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
624 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
625 mDaySpinner.setWrapSelectorWheel(false);
626 mMonthSpinner.setDisplayedValues(null);
627 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
628 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
629 mMonthSpinner.setWrapSelectorWheel(false);
630 } else if (mCurrentDate.equals(mMaxDate)) {
631 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
632 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
633 mDaySpinner.setWrapSelectorWheel(false);
634 mMonthSpinner.setDisplayedValues(null);
635 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
636 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
637 mMonthSpinner.setWrapSelectorWheel(false);
638 } else {
639 mDaySpinner.setMinValue(1);
640 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
641 mDaySpinner.setWrapSelectorWheel(true);
642 mMonthSpinner.setDisplayedValues(null);
643 mMonthSpinner.setMinValue(0);
644 mMonthSpinner.setMaxValue(11);
645 mMonthSpinner.setWrapSelectorWheel(true);
646 }
647
648 // make sure the month names are a zero based array
649 // with the months in the month spinner
Svetoslav Ganovf5926962011-07-12 12:26:20 -0700650 String[] displayedValues = Arrays.copyOfRange(mShortMonths,
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800651 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
652 mMonthSpinner.setDisplayedValues(displayedValues);
653
654 // year spinner range does not change based on the current date
655 mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
656 mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
657 mYearSpinner.setWrapSelectorWheel(false);
658
659 // set the spinner values
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800660 mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
661 mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800662 mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800663 }
664
665 /**
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800666 * Updates the calendar view with the current date.
Svetoslav Ganove9730bf2010-12-20 21:25:20 -0800667 */
668 private void updateCalendarView() {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800669 mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800670 }
671
672 /**
673 * @return The selected year.
674 */
675 public int getYear() {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800676 return mCurrentDate.get(Calendar.YEAR);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800677 }
678
679 /**
680 * @return The selected month.
681 */
682 public int getMonth() {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800683 return mCurrentDate.get(Calendar.MONTH);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800684 }
685
686 /**
687 * @return The selected day of month.
688 */
689 public int getDayOfMonth() {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800690 return mCurrentDate.get(Calendar.DAY_OF_MONTH);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800691 }
692
693 /**
694 * Notifies the listener, if such, for a change in the selected date.
695 */
696 private void notifyDateChanged() {
Svetoslav Ganov8a2a8952011-01-27 17:40:13 -0800697 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800698 if (mOnDateChangedListener != null) {
Svetoslav Ganov156f2092011-01-24 02:13:36 -0800699 mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth());
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800700 }
701 }
702
703 /**
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700704 * Sets the IME options for a spinner based on its ordering.
705 *
706 * @param spinner The spinner.
707 * @param spinnerCount The total spinner count.
708 * @param spinnerIndex The index of the given spinner.
709 */
710 private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
711 final int imeOptions;
712 if (spinnerIndex < spinnerCount - 1) {
713 imeOptions = EditorInfo.IME_ACTION_NEXT;
714 } else {
715 imeOptions = EditorInfo.IME_ACTION_DONE;
716 }
717 TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input);
718 input.setImeOptions(imeOptions);
719 }
720
721 private void setContentDescriptions() {
722 // Day
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700723 trySetContentDescription(mDaySpinner, R.id.increment,
724 R.string.date_picker_increment_day_button);
725 trySetContentDescription(mDaySpinner, R.id.decrement,
726 R.string.date_picker_decrement_day_button);
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700727 // Month
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700728 trySetContentDescription(mMonthSpinner, R.id.increment,
729 R.string.date_picker_increment_month_button);
730 trySetContentDescription(mMonthSpinner, R.id.decrement,
731 R.string.date_picker_decrement_month_button);
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700732 // Year
Svetoslav Ganovd11e6152012-03-20 12:13:02 -0700733 trySetContentDescription(mYearSpinner, R.id.increment,
734 R.string.date_picker_increment_year_button);
735 trySetContentDescription(mYearSpinner, R.id.decrement,
736 R.string.date_picker_decrement_year_button);
737 }
738
739 private void trySetContentDescription(View root, int viewId, int contDescResId) {
740 View target = root.findViewById(viewId);
741 if (target != null) {
742 target.setContentDescription(mContext.getString(contDescResId));
743 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700744 }
745
Svetoslav Ganov6304b0d2011-10-19 19:55:44 -0700746 private void updateInputState() {
747 // Make sure that if the user changes the value and the IME is active
748 // for one of the inputs if this widget, the IME is closed. If the user
749 // changed the value via the IME and there is a next input the IME will
750 // be shown, otherwise the user chose another means of changing the
751 // value and having the IME up makes no sense.
752 InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
753 if (inputMethodManager != null) {
754 if (inputMethodManager.isActive(mYearSpinnerInput)) {
755 mYearSpinnerInput.clearFocus();
756 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
757 } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
758 mMonthSpinnerInput.clearFocus();
759 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
760 } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
761 mDaySpinnerInput.clearFocus();
762 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
763 }
764 }
765 }
766
Svetoslav Ganova53efe92011-09-08 18:08:36 -0700767 /**
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800768 * Class for managing state storing/restoring.
769 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800770 private static class SavedState extends BaseSavedState {
771
772 private final int mYear;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800773
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774 private final int mMonth;
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800775
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800776 private final int mDay;
777
778 /**
779 * Constructor called from {@link DatePicker#onSaveInstanceState()}
780 */
781 private SavedState(Parcelable superState, int year, int month, int day) {
782 super(superState);
783 mYear = year;
784 mMonth = month;
785 mDay = day;
786 }
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800787
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800788 /**
789 * Constructor called from {@link #CREATOR}
790 */
791 private SavedState(Parcel in) {
792 super(in);
793 mYear = in.readInt();
794 mMonth = in.readInt();
795 mDay = in.readInt();
796 }
797
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800798 @Override
799 public void writeToParcel(Parcel dest, int flags) {
800 super.writeToParcel(dest, flags);
801 dest.writeInt(mYear);
802 dest.writeInt(mMonth);
803 dest.writeInt(mDay);
804 }
805
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800806 @SuppressWarnings("all")
807 // suppress unused and hiding
808 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800809
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800810 public SavedState createFromParcel(Parcel in) {
811 return new SavedState(in);
812 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800813
Svetoslav Ganov50f34d12010-12-03 16:05:40 -0800814 public SavedState[] newArray(int size) {
815 return new SavedState[size];
816 }
817 };
Kenneth Anderssone3491b62010-03-05 09:16:24 +0100818 }
Svetoslav Ganov3fec3fe2011-09-01 14:48:37 -0700819}