blob: b962962118cb9ed78b313db05a415ab978bce6a9 [file] [log] [blame]
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -07001/*
2 * Copyright (C) 2014 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
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070019import android.content.Context;
20import android.content.res.ColorStateList;
21import android.content.res.Configuration;
22import android.content.res.Resources;
23import android.content.res.TypedArray;
Alan Viveretteba9bf412014-09-03 20:14:21 -070024import android.graphics.drawable.Drawable;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070025import android.os.Parcel;
26import android.os.Parcelable;
27import android.text.format.DateFormat;
28import android.text.format.DateUtils;
29import android.util.AttributeSet;
30import android.util.SparseArray;
31import android.view.HapticFeedbackConstants;
32import android.view.LayoutInflater;
33import android.view.View;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070034import android.view.accessibility.AccessibilityEvent;
35import android.view.accessibility.AccessibilityNodeInfo;
36import android.view.animation.AlphaAnimation;
37import android.view.animation.Animation;
38
39import com.android.internal.R;
40import com.android.internal.widget.AccessibleDateAnimator;
41
42import java.text.SimpleDateFormat;
43import java.util.Calendar;
44import java.util.HashSet;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070045import java.util.Locale;
46
47/**
48 * A delegate for picking up a date (day / month / year).
49 */
Chet Haase3053b2f2014-08-06 07:51:50 -070050class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate implements
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070051 View.OnClickListener, DatePickerController {
Alan Viverette0a04bb02014-09-03 20:48:02 -070052 private static final int USE_LOCALE = 0;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070053
54 private static final int UNINITIALIZED = -1;
55 private static final int MONTH_AND_DAY_VIEW = 0;
56 private static final int YEAR_VIEW = 1;
57
58 private static final int DEFAULT_START_YEAR = 1900;
59 private static final int DEFAULT_END_YEAR = 2100;
60
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070061 private static final int ANIMATION_DURATION = 300;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070062
63 private static final int MONTH_INDEX = 0;
64 private static final int DAY_INDEX = 1;
65 private static final int YEAR_INDEX = 2;
66
67 private SimpleDateFormat mYearFormat = new SimpleDateFormat("y", Locale.getDefault());
68 private SimpleDateFormat mDayFormat = new SimpleDateFormat("d", Locale.getDefault());
69
70 private TextView mDayOfWeekView;
Alan Viverette7119d0d2014-08-25 17:27:02 -070071
72 /** Layout that contains the current month, day, and year. */
73 private LinearLayout mMonthDayYearLayout;
74
75 /** Clickable layout that contains the current day and year. */
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070076 private LinearLayout mMonthAndDayLayout;
Alan Viverette7119d0d2014-08-25 17:27:02 -070077
Alan Viverette60727e02014-07-28 16:56:32 -070078 private TextView mHeaderMonthTextView;
79 private TextView mHeaderDayOfMonthTextView;
80 private TextView mHeaderYearTextView;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070081 private DayPickerView mDayPickerView;
82 private YearPickerView mYearPickerView;
83
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070084 private boolean mIsEnabled = true;
85
86 // Accessibility strings.
87 private String mDayPickerDescription;
88 private String mSelectDay;
89 private String mYearPickerDescription;
90 private String mSelectYear;
91
92 private AccessibleDateAnimator mAnimator;
93
94 private DatePicker.OnDateChangedListener mDateChangedListener;
95
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070096 private int mCurrentView = UNINITIALIZED;
97
98 private Calendar mCurrentDate;
99 private Calendar mTempDate;
100 private Calendar mMinDate;
101 private Calendar mMaxDate;
102
Alan Viverette0a04bb02014-09-03 20:48:02 -0700103 private int mFirstDayOfWeek = USE_LOCALE;
104
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700105 private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>();
106
Chet Haase3053b2f2014-08-06 07:51:50 -0700107 public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700108 int defStyleAttr, int defStyleRes) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700109 super(delegator, context);
110
111 final Locale locale = Locale.getDefault();
112 mMinDate = getCalendarForLocale(mMinDate, locale);
113 mMaxDate = getCalendarForLocale(mMaxDate, locale);
114 mTempDate = getCalendarForLocale(mMaxDate, locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700115 mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
116
117 mMinDate.set(DEFAULT_START_YEAR, 1, 1);
118 mMaxDate.set(DEFAULT_END_YEAR, 12, 31);
119
Alan Viverette60727e02014-07-28 16:56:32 -0700120 final Resources res = mDelegator.getResources();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700121 final TypedArray a = mContext.obtainStyledAttributes(attrs,
122 R.styleable.DatePicker, defStyleAttr, defStyleRes);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700123 final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
124 Context.LAYOUT_INFLATER_SERVICE);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700125 final int layoutResourceId = a.getResourceId(
126 R.styleable.DatePicker_internalLayout, R.layout.date_picker_holo);
Alan Viverette60727e02014-07-28 16:56:32 -0700127 final View mainView = inflater.inflate(layoutResourceId, null);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700128 mDelegator.addView(mainView);
129
130 mDayOfWeekView = (TextView) mainView.findViewById(R.id.date_picker_header);
Alan Viverette7119d0d2014-08-25 17:27:02 -0700131
132 // Layout that contains the current date and day name header.
133 final LinearLayout dateLayout = (LinearLayout) mainView.findViewById(
134 R.id.day_picker_selector_layout);
135 mMonthDayYearLayout = (LinearLayout) mainView.findViewById(
136 R.id.date_picker_month_day_year_layout);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700137 mMonthAndDayLayout = (LinearLayout) mainView.findViewById(
138 R.id.date_picker_month_and_day_layout);
139 mMonthAndDayLayout.setOnClickListener(this);
Alan Viverette60727e02014-07-28 16:56:32 -0700140 mHeaderMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_month);
141 mHeaderDayOfMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_day);
142 mHeaderYearTextView = (TextView) mainView.findViewById(R.id.date_picker_year);
143 mHeaderYearTextView.setOnClickListener(this);
144
145 // Obtain default highlight color from the theme.
146 final int defaultHighlightColor = mHeaderYearTextView.getHighlightColor();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700147
148 // Use Theme attributes if possible
Alan Viverette60727e02014-07-28 16:56:32 -0700149 final int dayOfWeekTextAppearanceResId = a.getResourceId(
150 R.styleable.DatePicker_dayOfWeekTextAppearance, -1);
151 if (dayOfWeekTextAppearanceResId != -1) {
152 mDayOfWeekView.setTextAppearance(context, dayOfWeekTextAppearanceResId);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700153 }
154
Alan Viveretteba9bf412014-09-03 20:14:21 -0700155 mDayOfWeekView.setBackground(a.getDrawable(R.styleable.DatePicker_dayOfWeekBackground));
156
157 dateLayout.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground));
Alan Viverette60727e02014-07-28 16:56:32 -0700158
159 final int headerSelectedTextColor = a.getColor(
160 R.styleable.DatePicker_headerSelectedTextColor, defaultHighlightColor);
Alan Viverette60727e02014-07-28 16:56:32 -0700161 final int monthTextAppearanceResId = a.getResourceId(
162 R.styleable.DatePicker_headerMonthTextAppearance, -1);
163 if (monthTextAppearanceResId != -1) {
164 mHeaderMonthTextView.setTextAppearance(context, monthTextAppearanceResId);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700165 }
Alan Viverette60727e02014-07-28 16:56:32 -0700166 mHeaderMonthTextView.setTextColor(ColorStateList.addFirstIfMissing(
167 mHeaderMonthTextView.getTextColors(), R.attr.state_selected,
168 headerSelectedTextColor));
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700169
Alan Viverette60727e02014-07-28 16:56:32 -0700170 final int dayOfMonthTextAppearanceResId = a.getResourceId(
171 R.styleable.DatePicker_headerDayOfMonthTextAppearance, -1);
172 if (dayOfMonthTextAppearanceResId != -1) {
173 mHeaderDayOfMonthTextView.setTextAppearance(context, dayOfMonthTextAppearanceResId);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700174 }
Alan Viverette60727e02014-07-28 16:56:32 -0700175 mHeaderDayOfMonthTextView.setTextColor(ColorStateList.addFirstIfMissing(
176 mHeaderDayOfMonthTextView.getTextColors(), R.attr.state_selected,
177 headerSelectedTextColor));
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700178
Alan Viverette60727e02014-07-28 16:56:32 -0700179 final int yearTextAppearanceResId = a.getResourceId(
180 R.styleable.DatePicker_headerYearTextAppearance, -1);
181 if (yearTextAppearanceResId != -1) {
182 mHeaderYearTextView.setTextAppearance(context, yearTextAppearanceResId);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700183 }
Alan Viverette60727e02014-07-28 16:56:32 -0700184 mHeaderYearTextView.setTextColor(ColorStateList.addFirstIfMissing(
185 mHeaderYearTextView.getTextColors(), R.attr.state_selected,
186 headerSelectedTextColor));
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700187
188 mDayPickerView = new DayPickerView(mContext, this);
189 mYearPickerView = new YearPickerView(mContext);
190 mYearPickerView.init(this);
191
Alan Viverette7119d0d2014-08-25 17:27:02 -0700192 final int yearSelectedCircleColor = a.getColor(R.styleable.DatePicker_yearListSelectorColor,
193 defaultHighlightColor);
194 mYearPickerView.setYearSelectedCircleColor(yearSelectedCircleColor);
195
Alan Viverette60727e02014-07-28 16:56:32 -0700196 final ColorStateList calendarTextColor = a.getColorStateList(
197 R.styleable.DatePicker_calendarTextColor);
198 final int calendarSelectedTextColor = a.getColor(
199 R.styleable.DatePicker_calendarSelectedTextColor, defaultHighlightColor);
200 mDayPickerView.setCalendarTextColor(ColorStateList.addFirstIfMissing(
201 calendarTextColor, R.attr.state_selected, calendarSelectedTextColor));
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700202
203 mDayPickerDescription = res.getString(R.string.day_picker_description);
204 mSelectDay = res.getString(R.string.select_day);
205 mYearPickerDescription = res.getString(R.string.year_picker_description);
206 mSelectYear = res.getString(R.string.select_year);
207
208 mAnimator = (AccessibleDateAnimator) mainView.findViewById(R.id.animator);
209 mAnimator.addView(mDayPickerView);
210 mAnimator.addView(mYearPickerView);
211 mAnimator.setDateMillis(mCurrentDate.getTimeInMillis());
Alan Viverette7119d0d2014-08-25 17:27:02 -0700212
213 final Animation animation = new AlphaAnimation(0.0f, 1.0f);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700214 animation.setDuration(ANIMATION_DURATION);
215 mAnimator.setInAnimation(animation);
Alan Viverette7119d0d2014-08-25 17:27:02 -0700216
217 final Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700218 animation2.setDuration(ANIMATION_DURATION);
219 mAnimator.setOutAnimation(animation2);
220
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700221 updateDisplay(false);
222 setCurrentView(MONTH_AND_DAY_VIEW);
223 }
224
225 /**
226 * Gets a calendar for locale bootstrapped with the value of a given calendar.
227 *
228 * @param oldCalendar The old calendar.
229 * @param locale The locale.
230 */
231 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
232 if (oldCalendar == null) {
233 return Calendar.getInstance(locale);
234 } else {
235 final long currentTimeMillis = oldCalendar.getTimeInMillis();
236 Calendar newCalendar = Calendar.getInstance(locale);
237 newCalendar.setTimeInMillis(currentTimeMillis);
238 return newCalendar;
239 }
240 }
241
242 /**
243 * Compute the array representing the order of Month / Day / Year views in their layout.
244 * Will be used for I18N purpose as the order of them depends on the Locale.
245 */
246 private int[] getMonthDayYearIndexes(String pattern) {
247 int[] result = new int[3];
248
249 final String filteredPattern = pattern.replaceAll("'.*?'", "");
250
251 final int dayIndex = filteredPattern.indexOf('d');
252 final int monthMIndex = filteredPattern.indexOf("M");
253 final int monthIndex = (monthMIndex != -1) ? monthMIndex : filteredPattern.indexOf("L");
254 final int yearIndex = filteredPattern.indexOf("y");
255
256 if (yearIndex < monthIndex) {
257 result[YEAR_INDEX] = 0;
258
259 if (monthIndex < dayIndex) {
260 result[MONTH_INDEX] = 1;
261 result[DAY_INDEX] = 2;
262 } else {
263 result[MONTH_INDEX] = 2;
264 result[DAY_INDEX] = 1;
265 }
266 } else {
267 result[YEAR_INDEX] = 2;
268
269 if (monthIndex < dayIndex) {
270 result[MONTH_INDEX] = 0;
271 result[DAY_INDEX] = 1;
272 } else {
273 result[MONTH_INDEX] = 1;
274 result[DAY_INDEX] = 0;
275 }
276 }
277 return result;
278 }
279
280 private void updateDisplay(boolean announce) {
281 if (mDayOfWeekView != null) {
282 mDayOfWeekView.setText(mCurrentDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
283 Locale.getDefault()));
284 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700285
286 // Compute indices of Month, Day and Year views
Alan Viverette7119d0d2014-08-25 17:27:02 -0700287 final String bestDateTimePattern =
288 DateFormat.getBestDateTimePattern(mCurrentLocale, "yMMMd");
289 final int[] viewIndices = getMonthDayYearIndexes(bestDateTimePattern);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700290
Alan Viverette7119d0d2014-08-25 17:27:02 -0700291 // Position the Year and MonthAndDay views within the header.
292 mMonthDayYearLayout.removeAllViews();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700293 if (viewIndices[YEAR_INDEX] == 0) {
Alan Viverette7119d0d2014-08-25 17:27:02 -0700294 mMonthDayYearLayout.addView(mHeaderYearTextView);
295 mMonthDayYearLayout.addView(mMonthAndDayLayout);
Alan Viverette518ff0d2014-08-15 14:20:35 -0700296 } else {
Alan Viverette7119d0d2014-08-25 17:27:02 -0700297 mMonthDayYearLayout.addView(mMonthAndDayLayout);
298 mMonthDayYearLayout.addView(mHeaderYearTextView);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700299 }
300
Alan Viverette7119d0d2014-08-25 17:27:02 -0700301 // Position Day and Month views within the MonthAndDay view.
302 mMonthAndDayLayout.removeAllViews();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700303 if (viewIndices[MONTH_INDEX] > viewIndices[DAY_INDEX]) {
Alan Viverette60727e02014-07-28 16:56:32 -0700304 mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView);
305 mMonthAndDayLayout.addView(mHeaderMonthTextView);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700306 } else {
Alan Viverette60727e02014-07-28 16:56:32 -0700307 mMonthAndDayLayout.addView(mHeaderMonthTextView);
308 mMonthAndDayLayout.addView(mHeaderDayOfMonthTextView);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700309 }
310
Alan Viverette60727e02014-07-28 16:56:32 -0700311 mHeaderMonthTextView.setText(mCurrentDate.getDisplayName(Calendar.MONTH, Calendar.SHORT,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700312 Locale.getDefault()).toUpperCase(Locale.getDefault()));
Alan Viverette60727e02014-07-28 16:56:32 -0700313 mHeaderDayOfMonthTextView.setText(mDayFormat.format(mCurrentDate.getTime()));
314 mHeaderYearTextView.setText(mYearFormat.format(mCurrentDate.getTime()));
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700315
316 // Accessibility.
317 long millis = mCurrentDate.getTimeInMillis();
318 mAnimator.setDateMillis(millis);
319 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
320 String monthAndDayText = DateUtils.formatDateTime(mContext, millis, flags);
321 mMonthAndDayLayout.setContentDescription(monthAndDayText);
322
323 if (announce) {
324 flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
325 String fullDateText = DateUtils.formatDateTime(mContext, millis, flags);
326 mAnimator.announceForAccessibility(fullDateText);
327 }
328 updatePickers();
329 }
330
331 private void setCurrentView(final int viewIndex) {
332 long millis = mCurrentDate.getTimeInMillis();
333
334 switch (viewIndex) {
335 case MONTH_AND_DAY_VIEW:
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700336 mDayPickerView.onDateChanged();
337 if (mCurrentView != viewIndex) {
338 mMonthAndDayLayout.setSelected(true);
Alan Viverette60727e02014-07-28 16:56:32 -0700339 mHeaderYearTextView.setSelected(false);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700340 mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
341 mCurrentView = viewIndex;
342 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700343
Alan Viverette7119d0d2014-08-25 17:27:02 -0700344 final int flags = DateUtils.FORMAT_SHOW_DATE;
345 final String dayString = DateUtils.formatDateTime(mContext, millis, flags);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700346 mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString);
347 mAnimator.announceForAccessibility(mSelectDay);
348 break;
349 case YEAR_VIEW:
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700350 mYearPickerView.onDateChanged();
351 if (mCurrentView != viewIndex) {
352 mMonthAndDayLayout.setSelected(false);
Alan Viverette60727e02014-07-28 16:56:32 -0700353 mHeaderYearTextView.setSelected(true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700354 mAnimator.setDisplayedChild(YEAR_VIEW);
355 mCurrentView = viewIndex;
356 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700357
Alan Viverette7119d0d2014-08-25 17:27:02 -0700358 final CharSequence yearString = mYearFormat.format(millis);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700359 mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString);
360 mAnimator.announceForAccessibility(mSelectYear);
361 break;
362 }
363 }
364
365 @Override
366 public void init(int year, int monthOfYear, int dayOfMonth,
367 DatePicker.OnDateChangedListener callBack) {
368 mDateChangedListener = callBack;
369 mCurrentDate.set(Calendar.YEAR, year);
370 mCurrentDate.set(Calendar.MONTH, monthOfYear);
371 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
372 updateDisplay(false);
373 }
374
375 @Override
376 public void updateDate(int year, int month, int dayOfMonth) {
377 mCurrentDate.set(Calendar.YEAR, year);
378 mCurrentDate.set(Calendar.MONTH, month);
379 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
Alan Viverette9468c6a2014-08-21 13:56:54 -0700380 if (mDateChangedListener != null) {
381 mDateChangedListener.onDateChanged(mDelegator, year, month, dayOfMonth);
382 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700383 updateDisplay(false);
384 }
385
386 @Override
387 public int getYear() {
388 return mCurrentDate.get(Calendar.YEAR);
389 }
390
391 @Override
392 public int getMonth() {
393 return mCurrentDate.get(Calendar.MONTH);
394 }
395
396 @Override
397 public int getDayOfMonth() {
398 return mCurrentDate.get(Calendar.DAY_OF_MONTH);
399 }
400
401 @Override
402 public void setMinDate(long minDate) {
403 mTempDate.setTimeInMillis(minDate);
404 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
405 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
406 return;
407 }
408 if (mCurrentDate.before(mTempDate)) {
409 mCurrentDate.setTimeInMillis(minDate);
410 updatePickers();
411 updateDisplay(false);
412 }
413 mMinDate.setTimeInMillis(minDate);
414 mDayPickerView.goTo(getSelectedDay(), false, true, true);
415 }
416
417 @Override
418 public Calendar getMinDate() {
419 return mMinDate;
420 }
421
422 @Override
423 public void setMaxDate(long maxDate) {
424 mTempDate.setTimeInMillis(maxDate);
425 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
426 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
427 return;
428 }
429 if (mCurrentDate.after(mTempDate)) {
430 mCurrentDate.setTimeInMillis(maxDate);
431 updatePickers();
432 updateDisplay(false);
433 }
434 mMaxDate.setTimeInMillis(maxDate);
435 mDayPickerView.goTo(getSelectedDay(), false, true, true);
436 }
437
438 @Override
439 public Calendar getMaxDate() {
440 return mMaxDate;
441 }
442
443 @Override
Alan Viverette0a04bb02014-09-03 20:48:02 -0700444 public void setFirstDayOfWeek(int firstDayOfWeek) {
445 mFirstDayOfWeek = firstDayOfWeek;
446 }
447
448 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700449 public int getFirstDayOfWeek() {
Alan Viverette0a04bb02014-09-03 20:48:02 -0700450 if (mFirstDayOfWeek != USE_LOCALE) {
451 return mFirstDayOfWeek;
452 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700453 return mCurrentDate.getFirstDayOfWeek();
454 }
455
456 @Override
457 public int getMinYear() {
458 return mMinDate.get(Calendar.YEAR);
459 }
460
461 @Override
462 public int getMaxYear() {
463 return mMaxDate.get(Calendar.YEAR);
464 }
465
466 @Override
467 public int getMinMonth() {
468 return mMinDate.get(Calendar.MONTH);
469 }
470
471 @Override
472 public int getMaxMonth() {
473 return mMaxDate.get(Calendar.MONTH);
474 }
475
476 @Override
477 public int getMinDay() {
478 return mMinDate.get(Calendar.DAY_OF_MONTH);
479 }
480
481 @Override
482 public int getMaxDay() {
483 return mMaxDate.get(Calendar.DAY_OF_MONTH);
484 }
485
486 @Override
487 public void setEnabled(boolean enabled) {
488 mMonthAndDayLayout.setEnabled(enabled);
Alan Viverette60727e02014-07-28 16:56:32 -0700489 mHeaderYearTextView.setEnabled(enabled);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700490 mAnimator.setEnabled(enabled);
491 mIsEnabled = enabled;
492 }
493
494 @Override
495 public boolean isEnabled() {
496 return mIsEnabled;
497 }
498
499 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700500 public CalendarView getCalendarView() {
501 throw new UnsupportedOperationException(
502 "CalendarView does not exists for the new DatePicker");
503 }
504
505 @Override
506 public void setCalendarViewShown(boolean shown) {
507 // No-op for compatibility with the old DatePicker.
508 }
509
510 @Override
511 public boolean getCalendarViewShown() {
512 return false;
513 }
514
515 @Override
516 public void setSpinnersShown(boolean shown) {
517 // No-op for compatibility with the old DatePicker.
518 }
519
520 @Override
521 public boolean getSpinnersShown() {
522 return false;
523 }
524
525 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700526 public void onConfigurationChanged(Configuration newConfig) {
527 mYearFormat = new SimpleDateFormat("y", newConfig.locale);
528 mDayFormat = new SimpleDateFormat("d", newConfig.locale);
529 }
530
531 @Override
532 public void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
533 // Nothing to do
534 }
535
536 @Override
537 public Parcelable onSaveInstanceState(Parcelable superState) {
538 final int year = mCurrentDate.get(Calendar.YEAR);
539 final int month = mCurrentDate.get(Calendar.MONTH);
540 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
541
542 int listPosition = -1;
543 int listPositionOffset = -1;
544
545 if (mCurrentView == MONTH_AND_DAY_VIEW) {
546 listPosition = mDayPickerView.getMostVisiblePosition();
547 } else if (mCurrentView == YEAR_VIEW) {
548 listPosition = mYearPickerView.getFirstVisiblePosition();
549 listPositionOffset = mYearPickerView.getFirstPositionOffset();
550 }
551
552 return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
553 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
554 }
555
556 @Override
557 public void onRestoreInstanceState(Parcelable state) {
558 SavedState ss = (SavedState) state;
559
560 mCurrentDate.set(ss.getSelectedDay(), ss.getSelectedMonth(), ss.getSelectedYear());
561 mCurrentView = ss.getCurrentView();
562 mMinDate.setTimeInMillis(ss.getMinDate());
563 mMaxDate.setTimeInMillis(ss.getMaxDate());
564
565 updateDisplay(false);
566 setCurrentView(mCurrentView);
567
568 final int listPosition = ss.getListPosition();
569 if (listPosition != -1) {
570 if (mCurrentView == MONTH_AND_DAY_VIEW) {
571 mDayPickerView.postSetSelection(listPosition);
572 } else if (mCurrentView == YEAR_VIEW) {
573 mYearPickerView.postSetSelectionFromTop(listPosition, ss.getListPositionOffset());
574 }
575 }
576 }
577
578 @Override
579 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
580 onPopulateAccessibilityEvent(event);
581 return true;
582 }
583
584 @Override
585 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
586 event.getText().add(mCurrentDate.getTime().toString());
587 }
588
589 @Override
590 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
591 event.setClassName(DatePicker.class.getName());
592 }
593
594 @Override
595 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
596 info.setClassName(DatePicker.class.getName());
597 }
598
599 @Override
600 public void onYearSelected(int year) {
601 adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year);
602 mCurrentDate.set(Calendar.YEAR, year);
603 updatePickers();
604 setCurrentView(MONTH_AND_DAY_VIEW);
605 updateDisplay(true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700606 }
607
608 // If the newly selected month / year does not contain the currently selected day number,
609 // change the selected day number to the last day of the selected month or year.
610 // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
611 // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
612 private void adjustDayInMonthIfNeeded(int month, int year) {
613 int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
614 int daysInMonth = getDaysInMonth(month, year);
615 if (day > daysInMonth) {
616 mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth);
617 }
618 }
619
620 public static int getDaysInMonth(int month, int year) {
621 switch (month) {
622 case Calendar.JANUARY:
623 case Calendar.MARCH:
624 case Calendar.MAY:
625 case Calendar.JULY:
626 case Calendar.AUGUST:
627 case Calendar.OCTOBER:
628 case Calendar.DECEMBER:
629 return 31;
630 case Calendar.APRIL:
631 case Calendar.JUNE:
632 case Calendar.SEPTEMBER:
633 case Calendar.NOVEMBER:
634 return 30;
635 case Calendar.FEBRUARY:
636 return (year % 4 == 0) ? 29 : 28;
637 default:
638 throw new IllegalArgumentException("Invalid Month");
639 }
640 }
641
642 @Override
643 public void onDayOfMonthSelected(int year, int month, int day) {
644 mCurrentDate.set(Calendar.YEAR, year);
645 mCurrentDate.set(Calendar.MONTH, month);
646 mCurrentDate.set(Calendar.DAY_OF_MONTH, day);
647 updatePickers();
648 updateDisplay(true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700649 }
650
651 private void updatePickers() {
Alan Viverette60727e02014-07-28 16:56:32 -0700652 for (OnDateChangedListener listener : mListeners) {
653 listener.onDateChanged();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700654 }
655 }
656
657 @Override
658 public void registerOnDateChangedListener(OnDateChangedListener listener) {
659 mListeners.add(listener);
660 }
661
662 @Override
663 public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
664 mListeners.remove(listener);
665 }
666
667 @Override
668 public Calendar getSelectedDay() {
669 return mCurrentDate;
670 }
671
672 @Override
673 public void tryVibrate() {
674 mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
675 }
676
677 @Override
678 public void onClick(View v) {
679 tryVibrate();
680 if (v.getId() == R.id.date_picker_year) {
681 setCurrentView(YEAR_VIEW);
682 } else if (v.getId() == R.id.date_picker_month_and_day_layout) {
683 setCurrentView(MONTH_AND_DAY_VIEW);
684 }
685 }
686
687 /**
688 * Class for managing state storing/restoring.
689 */
690 private static class SavedState extends View.BaseSavedState {
691
692 private final int mSelectedYear;
693 private final int mSelectedMonth;
694 private final int mSelectedDay;
695 private final long mMinDate;
696 private final long mMaxDate;
697 private final int mCurrentView;
698 private final int mListPosition;
699 private final int mListPositionOffset;
700
701 /**
702 * Constructor called from {@link DatePicker#onSaveInstanceState()}
703 */
704 private SavedState(Parcelable superState, int year, int month, int day,
705 long minDate, long maxDate, int currentView, int listPosition,
706 int listPositionOffset) {
707 super(superState);
708 mSelectedYear = year;
709 mSelectedMonth = month;
710 mSelectedDay = day;
711 mMinDate = minDate;
712 mMaxDate = maxDate;
713 mCurrentView = currentView;
714 mListPosition = listPosition;
715 mListPositionOffset = listPositionOffset;
716 }
717
718 /**
719 * Constructor called from {@link #CREATOR}
720 */
721 private SavedState(Parcel in) {
722 super(in);
723 mSelectedYear = in.readInt();
724 mSelectedMonth = in.readInt();
725 mSelectedDay = in.readInt();
726 mMinDate = in.readLong();
727 mMaxDate = in.readLong();
728 mCurrentView = in.readInt();
729 mListPosition = in.readInt();
730 mListPositionOffset = in.readInt();
731 }
732
733 @Override
734 public void writeToParcel(Parcel dest, int flags) {
735 super.writeToParcel(dest, flags);
736 dest.writeInt(mSelectedYear);
737 dest.writeInt(mSelectedMonth);
738 dest.writeInt(mSelectedDay);
739 dest.writeLong(mMinDate);
740 dest.writeLong(mMaxDate);
741 dest.writeInt(mCurrentView);
742 dest.writeInt(mListPosition);
743 dest.writeInt(mListPositionOffset);
744 }
745
746 public int getSelectedDay() {
747 return mSelectedDay;
748 }
749
750 public int getSelectedMonth() {
751 return mSelectedMonth;
752 }
753
754 public int getSelectedYear() {
755 return mSelectedYear;
756 }
757
758 public long getMinDate() {
759 return mMinDate;
760 }
761
762 public long getMaxDate() {
763 return mMaxDate;
764 }
765
766 public int getCurrentView() {
767 return mCurrentView;
768 }
769
770 public int getListPosition() {
771 return mListPosition;
772 }
773
774 public int getListPositionOffset() {
775 return mListPositionOffset;
776 }
777
778 @SuppressWarnings("all")
779 // suppress unused and hiding
780 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
781
782 public SavedState createFromParcel(Parcel in) {
783 return new SavedState(in);
784 }
785
786 public SavedState[] newArray(int size) {
787 return new SavedState[size];
788 }
789 };
790 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700791}