blob: 5adac01957018122c08e0e2701b00ede678d2c21 [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
Alan Viverette6b3f85f2016-03-01 16:48:04 -050019import com.android.internal.R;
20
Alan Viverette0ef59ac2015-03-23 13:13:25 -070021import android.annotation.Nullable;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070022import android.content.Context;
23import android.content.res.ColorStateList;
24import android.content.res.Configuration;
25import android.content.res.Resources;
26import android.content.res.TypedArray;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070027import android.os.Parcelable;
28import android.text.format.DateFormat;
29import android.text.format.DateUtils;
30import android.util.AttributeSet;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070031import android.util.StateSet;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070032import android.view.HapticFeedbackConstants;
33import android.view.LayoutInflater;
34import android.view.View;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070035import android.view.View.OnClickListener;
36import android.view.ViewGroup;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070037import android.view.accessibility.AccessibilityEvent;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070038import android.widget.DayPickerView.OnDaySelectedListener;
39import android.widget.YearPickerView.OnYearSelectedListener;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070040
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070041import java.text.SimpleDateFormat;
42import java.util.Calendar;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070043import java.util.Locale;
44
45/**
46 * A delegate for picking up a date (day / month / year).
47 */
Alan Viverette0ef59ac2015-03-23 13:13:25 -070048class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
Alan Viverette0a04bb02014-09-03 20:48:02 -070049 private static final int USE_LOCALE = 0;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070050
51 private static final int UNINITIALIZED = -1;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070052 private static final int VIEW_MONTH_DAY = 0;
53 private static final int VIEW_YEAR = 1;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070054
55 private static final int DEFAULT_START_YEAR = 1900;
56 private static final int DEFAULT_END_YEAR = 2100;
57
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070058 private static final int ANIMATION_DURATION = 300;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070059
Alan Viverette60b674e2015-03-25 13:00:42 -070060 private static final int[] ATTRS_TEXT_COLOR = new int[] {
61 com.android.internal.R.attr.textColor};
62 private static final int[] ATTRS_DISABLED_ALPHA = new int[] {
Alan Viverette0ef59ac2015-03-23 13:13:25 -070063 com.android.internal.R.attr.disabledAlpha};
Alan Viverette7119d0d2014-08-25 17:27:02 -070064
Alan Viverette0ef59ac2015-03-23 13:13:25 -070065 private SimpleDateFormat mYearFormat;
66 private SimpleDateFormat mMonthDayFormat;
Alan Viverette7119d0d2014-08-25 17:27:02 -070067
Alan Viverette0ef59ac2015-03-23 13:13:25 -070068 // Top-level container.
69 private ViewGroup mContainer;
Alan Viverette7119d0d2014-08-25 17:27:02 -070070
Alan Viverette0ef59ac2015-03-23 13:13:25 -070071 // Header views.
72 private TextView mHeaderYear;
73 private TextView mHeaderMonthDay;
74
75 // Picker views.
76 private ViewAnimator mAnimator;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070077 private DayPickerView mDayPickerView;
78 private YearPickerView mYearPickerView;
79
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070080 // Accessibility strings.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070081 private String mSelectDay;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070082 private String mSelectYear;
83
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070084 private DatePicker.OnDateChangedListener mDateChangedListener;
85
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070086 private int mCurrentView = UNINITIALIZED;
87
Alan Viverette452fe342015-03-23 14:26:09 -070088 private final Calendar mCurrentDate;
89 private final Calendar mTempDate;
90 private final Calendar mMinDate;
91 private final Calendar mMaxDate;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070092
Alan Viverette0a04bb02014-09-03 20:48:02 -070093 private int mFirstDayOfWeek = USE_LOCALE;
94
Chet Haase3053b2f2014-08-06 07:51:50 -070095 public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070096 int defStyleAttr, int defStyleRes) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070097 super(delegator, context);
98
Alan Viverette0ef59ac2015-03-23 13:13:25 -070099 final Locale locale = mCurrentLocale;
Alan Viverette452fe342015-03-23 14:26:09 -0700100 mCurrentDate = Calendar.getInstance(locale);
101 mTempDate = Calendar.getInstance(locale);
102 mMinDate = Calendar.getInstance(locale);
103 mMaxDate = Calendar.getInstance(locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700104
You Kim49e6c4a2015-02-26 19:06:16 +0900105 mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
106 mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700107
Alan Viverette60727e02014-07-28 16:56:32 -0700108 final Resources res = mDelegator.getResources();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700109 final TypedArray a = mContext.obtainStyledAttributes(attrs,
110 R.styleable.DatePicker, defStyleAttr, defStyleRes);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700111 final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
112 Context.LAYOUT_INFLATER_SERVICE);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700113 final int layoutResourceId = a.getResourceId(
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700114 R.styleable.DatePicker_internalLayout, R.layout.date_picker_material);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700115
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700116 // Set up and attach container.
Alan Viverettef7aa9252015-06-03 14:07:26 -0700117 mContainer = (ViewGroup) inflater.inflate(layoutResourceId, mDelegator, false);
118 mDelegator.addView(mContainer);
Alan Viverette7119d0d2014-08-25 17:27:02 -0700119
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700120 // Set up header views.
121 final ViewGroup header = (ViewGroup) mContainer.findViewById(R.id.date_picker_header);
122 mHeaderYear = (TextView) header.findViewById(R.id.date_picker_header_year);
123 mHeaderYear.setOnClickListener(mOnHeaderClickListener);
124 mHeaderMonthDay = (TextView) header.findViewById(R.id.date_picker_header_date);
125 mHeaderMonthDay.setOnClickListener(mOnHeaderClickListener);
Alan Viverette60727e02014-07-28 16:56:32 -0700126
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700127 // For the sake of backwards compatibility, attempt to extract the text
128 // color from the header month text appearance. If it's set, we'll let
129 // that override the "real" header text color.
130 ColorStateList headerTextColor = null;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700131
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700132 @SuppressWarnings("deprecation")
133 final int monthHeaderTextAppearance = a.getResourceId(
Alan Viverette4e5168f2015-01-08 15:24:45 -0800134 R.styleable.DatePicker_headerMonthTextAppearance, 0);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700135 if (monthHeaderTextAppearance != 0) {
136 final TypedArray textAppearance = mContext.obtainStyledAttributes(null,
137 ATTRS_TEXT_COLOR, 0, monthHeaderTextAppearance);
138 final ColorStateList legacyHeaderTextColor = textAppearance.getColorStateList(0);
139 headerTextColor = applyLegacyColorFixes(legacyHeaderTextColor);
140 textAppearance.recycle();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700141 }
142
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700143 if (headerTextColor == null) {
144 headerTextColor = a.getColorStateList(R.styleable.DatePicker_headerTextColor);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700145 }
146
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700147 if (headerTextColor != null) {
148 mHeaderYear.setTextColor(headerTextColor);
149 mHeaderMonthDay.setTextColor(headerTextColor);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700150 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700151
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700152 // Set up header background, if available.
153 if (a.hasValueOrEmpty(R.styleable.DatePicker_headerBackground)) {
154 header.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground));
155 }
156
Alan Viverette60b674e2015-03-25 13:00:42 -0700157 a.recycle();
158
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700159 // Set up picker container.
160 mAnimator = (ViewAnimator) mContainer.findViewById(R.id.animator);
161
162 // Set up day picker view.
163 mDayPickerView = (DayPickerView) mAnimator.findViewById(R.id.date_picker_day_picker);
Alan Viverettee763c9b2014-11-06 15:22:31 -0800164 mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek);
Alan Viverette46127402014-11-13 10:50:37 -0800165 mDayPickerView.setMinDate(mMinDate.getTimeInMillis());
166 mDayPickerView.setMaxDate(mMaxDate.getTimeInMillis());
167 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
Alan Viverettee763c9b2014-11-06 15:22:31 -0800168 mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener);
Alan Viverette50eb0252014-10-24 14:34:26 -0700169
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700170 // Set up year picker view.
171 mYearPickerView = (YearPickerView) mAnimator.findViewById(R.id.date_picker_year_picker);
Alan Viverette2a90fa62015-02-17 13:09:04 -0800172 mYearPickerView.setRange(mMinDate, mMaxDate);
George Mounte998c3f2015-10-27 08:46:44 -0700173 mYearPickerView.setYear(mCurrentDate.get(Calendar.YEAR));
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700174 mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener);
Alan Viverette4e5168f2015-01-08 15:24:45 -0800175
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700176 // Set up content descriptions.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700177 mSelectDay = res.getString(R.string.select_day);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700178 mSelectYear = res.getString(R.string.select_year);
179
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700180 // Initialize for current locale. This also initializes the date, so no
181 // need to call onDateChanged.
182 onLocaleChanged(mCurrentLocale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700183
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700184 setCurrentView(VIEW_MONTH_DAY);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700185 }
186
187 /**
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700188 * The legacy text color might have been poorly defined. Ensures that it
189 * has an appropriate activated state, using the selected state if one
190 * exists or modifying the default text color otherwise.
191 *
192 * @param color a legacy text color, or {@code null}
193 * @return a color state list with an appropriate activated state, or
194 * {@code null} if a valid activated state could not be generated
195 */
196 @Nullable
197 private ColorStateList applyLegacyColorFixes(@Nullable ColorStateList color) {
198 if (color == null || color.hasState(R.attr.state_activated)) {
199 return color;
200 }
201
202 final int activatedColor;
203 final int defaultColor;
204 if (color.hasState(R.attr.state_selected)) {
205 activatedColor = color.getColorForState(StateSet.get(
206 StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_SELECTED), 0);
207 defaultColor = color.getColorForState(StateSet.get(
208 StateSet.VIEW_STATE_ENABLED), 0);
209 } else {
210 activatedColor = color.getDefaultColor();
211
212 // Generate a non-activated color using the disabled alpha.
213 final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA);
214 final float disabledAlpha = ta.getFloat(0, 0.30f);
215 defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha);
216 }
217
218 if (activatedColor == 0 || defaultColor == 0) {
219 // We somehow failed to obtain the colors.
220 return null;
221 }
222
223 final int[][] stateSet = new int[][] {{ R.attr.state_activated }, {}};
224 final int[] colors = new int[] { activatedColor, defaultColor };
225 return new ColorStateList(stateSet, colors);
226 }
227
228 private int multiplyAlphaComponent(int color, float alphaMod) {
229 final int srcRgb = color & 0xFFFFFF;
230 final int srcAlpha = (color >> 24) & 0xFF;
231 final int dstAlpha = (int) (srcAlpha * alphaMod + 0.5f);
232 return srcRgb | (dstAlpha << 24);
233 }
234
235 /**
236 * Listener called when the user selects a day in the day picker view.
237 */
238 private final OnDaySelectedListener mOnDaySelectedListener = new OnDaySelectedListener() {
239 @Override
240 public void onDaySelected(DayPickerView view, Calendar day) {
241 mCurrentDate.setTimeInMillis(day.getTimeInMillis());
242 onDateChanged(true, true);
243 }
244 };
245
246 /**
247 * Listener called when the user selects a year in the year picker view.
248 */
249 private final OnYearSelectedListener mOnYearSelectedListener = new OnYearSelectedListener() {
250 @Override
251 public void onYearChanged(YearPickerView view, int year) {
252 // If the newly selected month / year does not contain the
253 // currently selected day number, change the selected day number
254 // to the last day of the selected month or year.
255 // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
256 // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
257 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
258 final int month = mCurrentDate.get(Calendar.MONTH);
259 final int daysInMonth = getDaysInMonth(month, year);
260 if (day > daysInMonth) {
261 mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth);
262 }
263
264 mCurrentDate.set(Calendar.YEAR, year);
265 onDateChanged(true, true);
266
267 // Automatically switch to day picker.
268 setCurrentView(VIEW_MONTH_DAY);
George Mounte998c3f2015-10-27 08:46:44 -0700269
270 // Switch focus back to the year text.
271 mHeaderYear.requestFocus();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700272 }
273 };
274
275 /**
276 * Listener called when the user clicks on a header item.
277 */
278 private final OnClickListener mOnHeaderClickListener = new OnClickListener() {
279 @Override
280 public void onClick(View v) {
281 tryVibrate();
282
283 switch (v.getId()) {
284 case R.id.date_picker_header_year:
285 setCurrentView(VIEW_YEAR);
286 break;
287 case R.id.date_picker_header_date:
288 setCurrentView(VIEW_MONTH_DAY);
289 break;
290 }
291 }
292 };
293
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700294 @Override
295 protected void onLocaleChanged(Locale locale) {
296 final TextView headerYear = mHeaderYear;
297 if (headerYear == null) {
298 // Abort, we haven't initialized yet. This method will get called
299 // again later after everything has been set up.
300 return;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700301 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700302
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700303 // Update the date formatter.
304 final String datePattern = DateFormat.getBestDateTimePattern(locale, "EMMMd");
305 mMonthDayFormat = new SimpleDateFormat(datePattern, locale);
306 mYearFormat = new SimpleDateFormat("y", locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700307
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700308 // Update the header text.
309 onCurrentDateChanged(false);
310 }
311
312 private void onCurrentDateChanged(boolean announce) {
313 if (mHeaderYear == null) {
314 // Abort, we haven't initialized yet. This method will get called
315 // again later after everything has been set up.
316 return;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700317 }
318
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700319 final String year = mYearFormat.format(mCurrentDate.getTime());
320 mHeaderYear.setText(year);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700321
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700322 final String monthDay = mMonthDayFormat.format(mCurrentDate.getTime());
323 mHeaderMonthDay.setText(monthDay);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700324
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700325 // TODO: This should use live regions.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700326 if (announce) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700327 final long millis = mCurrentDate.getTimeInMillis();
328 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
329 final String fullDateText = DateUtils.formatDateTime(mContext, millis, flags);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700330 mAnimator.announceForAccessibility(fullDateText);
331 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700332 }
333
334 private void setCurrentView(final int viewIndex) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700335 switch (viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700336 case VIEW_MONTH_DAY:
337 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
338
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700339 if (mCurrentView != viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700340 mHeaderMonthDay.setActivated(true);
341 mHeaderYear.setActivated(false);
342 mAnimator.setDisplayedChild(VIEW_MONTH_DAY);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700343 mCurrentView = viewIndex;
344 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700345
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700346 mAnimator.announceForAccessibility(mSelectDay);
347 break;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700348 case VIEW_YEAR:
George Mounte998c3f2015-10-27 08:46:44 -0700349 final int year = mCurrentDate.get(Calendar.YEAR);
350 mYearPickerView.setYear(year);
351 mYearPickerView.post(new Runnable() {
352 @Override
353 public void run() {
354 mYearPickerView.requestFocus();
355 final View selected = mYearPickerView.getSelectedView();
356 if (selected != null) {
357 selected.requestFocus();
358 }
359 }
360 });
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700361
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700362 if (mCurrentView != viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700363 mHeaderMonthDay.setActivated(false);
364 mHeaderYear.setActivated(true);
365 mAnimator.setDisplayedChild(VIEW_YEAR);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700366 mCurrentView = viewIndex;
367 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700368
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700369 mAnimator.announceForAccessibility(mSelectYear);
370 break;
371 }
372 }
373
374 @Override
375 public void init(int year, int monthOfYear, int dayOfMonth,
376 DatePicker.OnDateChangedListener callBack) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700377 mCurrentDate.set(Calendar.YEAR, year);
378 mCurrentDate.set(Calendar.MONTH, monthOfYear);
379 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800380
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800381 mDateChangedListener = callBack;
Alan Viverette0cecbc92014-12-12 14:32:43 -0800382
383 onDateChanged(false, false);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700384 }
385
386 @Override
387 public void updateDate(int year, int month, int dayOfMonth) {
388 mCurrentDate.set(Calendar.YEAR, year);
389 mCurrentDate.set(Calendar.MONTH, month);
390 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800391
Alan Viverette0cecbc92014-12-12 14:32:43 -0800392 onDateChanged(false, true);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800393 }
394
Alan Viverette0cecbc92014-12-12 14:32:43 -0800395 private void onDateChanged(boolean fromUser, boolean callbackToClient) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700396 final int year = mCurrentDate.get(Calendar.YEAR);
397
Alan Viverette0cecbc92014-12-12 14:32:43 -0800398 if (callbackToClient && mDateChangedListener != null) {
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800399 final int monthOfYear = mCurrentDate.get(Calendar.MONTH);
400 final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH);
401 mDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
Alan Viverette9468c6a2014-08-21 13:56:54 -0700402 }
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800403
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700404 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
405 mYearPickerView.setYear(year);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800406
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700407 onCurrentDateChanged(fromUser);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800408
409 if (fromUser) {
410 tryVibrate();
411 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700412 }
413
414 @Override
415 public int getYear() {
416 return mCurrentDate.get(Calendar.YEAR);
417 }
418
419 @Override
420 public int getMonth() {
421 return mCurrentDate.get(Calendar.MONTH);
422 }
423
424 @Override
425 public int getDayOfMonth() {
426 return mCurrentDate.get(Calendar.DAY_OF_MONTH);
427 }
428
429 @Override
430 public void setMinDate(long minDate) {
431 mTempDate.setTimeInMillis(minDate);
432 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
433 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
434 return;
435 }
436 if (mCurrentDate.before(mTempDate)) {
437 mCurrentDate.setTimeInMillis(minDate);
Alan Viverette0cecbc92014-12-12 14:32:43 -0800438 onDateChanged(false, true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700439 }
440 mMinDate.setTimeInMillis(minDate);
Alan Viverette46127402014-11-13 10:50:37 -0800441 mDayPickerView.setMinDate(minDate);
Alan Viverette50eb0252014-10-24 14:34:26 -0700442 mYearPickerView.setRange(mMinDate, mMaxDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700443 }
444
445 @Override
446 public Calendar getMinDate() {
447 return mMinDate;
448 }
449
450 @Override
451 public void setMaxDate(long maxDate) {
452 mTempDate.setTimeInMillis(maxDate);
453 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
454 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
455 return;
456 }
457 if (mCurrentDate.after(mTempDate)) {
458 mCurrentDate.setTimeInMillis(maxDate);
Alan Viverette0cecbc92014-12-12 14:32:43 -0800459 onDateChanged(false, true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700460 }
461 mMaxDate.setTimeInMillis(maxDate);
Alan Viverette46127402014-11-13 10:50:37 -0800462 mDayPickerView.setMaxDate(maxDate);
Alan Viverette50eb0252014-10-24 14:34:26 -0700463 mYearPickerView.setRange(mMinDate, mMaxDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700464 }
465
466 @Override
467 public Calendar getMaxDate() {
468 return mMaxDate;
469 }
470
471 @Override
Alan Viverette0a04bb02014-09-03 20:48:02 -0700472 public void setFirstDayOfWeek(int firstDayOfWeek) {
473 mFirstDayOfWeek = firstDayOfWeek;
Alan Viverettee763c9b2014-11-06 15:22:31 -0800474
475 mDayPickerView.setFirstDayOfWeek(firstDayOfWeek);
Alan Viverette0a04bb02014-09-03 20:48:02 -0700476 }
477
478 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700479 public int getFirstDayOfWeek() {
Alan Viverette0a04bb02014-09-03 20:48:02 -0700480 if (mFirstDayOfWeek != USE_LOCALE) {
481 return mFirstDayOfWeek;
482 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700483 return mCurrentDate.getFirstDayOfWeek();
484 }
485
486 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700487 public void setEnabled(boolean enabled) {
Alan Viverettef7aa9252015-06-03 14:07:26 -0700488 mContainer.setEnabled(enabled);
489 mDayPickerView.setEnabled(enabled);
490 mYearPickerView.setEnabled(enabled);
491 mHeaderYear.setEnabled(enabled);
492 mHeaderMonthDay.setEnabled(enabled);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700493 }
494
495 @Override
496 public boolean isEnabled() {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700497 return mContainer.isEnabled();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700498 }
499
500 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700501 public CalendarView getCalendarView() {
Alan Viverettef7aa9252015-06-03 14:07:26 -0700502 throw new UnsupportedOperationException("Not supported by calendar-mode DatePicker");
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700503 }
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) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700527 setCurrentLocale(newConfig.locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700528 }
529
530 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700531 public Parcelable onSaveInstanceState(Parcelable superState) {
532 final int year = mCurrentDate.get(Calendar.YEAR);
533 final int month = mCurrentDate.get(Calendar.MONTH);
534 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
535
536 int listPosition = -1;
537 int listPositionOffset = -1;
538
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700539 if (mCurrentView == VIEW_MONTH_DAY) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700540 listPosition = mDayPickerView.getMostVisiblePosition();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700541 } else if (mCurrentView == VIEW_YEAR) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700542 listPosition = mYearPickerView.getFirstVisiblePosition();
543 listPositionOffset = mYearPickerView.getFirstPositionOffset();
544 }
545
546 return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
547 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
548 }
549
550 @Override
551 public void onRestoreInstanceState(Parcelable state) {
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500552 if (state instanceof SavedState) {
553 final SavedState ss = (SavedState) state;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700554
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500555 // TODO: Move instance state into DayPickerView, YearPickerView.
556 mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay());
557 mMinDate.setTimeInMillis(ss.getMinDate());
558 mMaxDate.setTimeInMillis(ss.getMaxDate());
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700559
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500560 onCurrentDateChanged(false);
Alan Viverette816aa142015-04-10 15:41:10 -0700561
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500562 final int currentView = ss.getCurrentView();
563 setCurrentView(currentView);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700564
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500565 final int listPosition = ss.getListPosition();
566 if (listPosition != -1) {
567 if (currentView == VIEW_MONTH_DAY) {
568 mDayPickerView.setPosition(listPosition);
569 } else if (currentView == VIEW_YEAR) {
570 final int listPositionOffset = ss.getListPositionOffset();
571 mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset);
572 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700573 }
574 }
575 }
576
577 @Override
578 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
579 onPopulateAccessibilityEvent(event);
580 return true;
581 }
582
583 @Override
584 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
585 event.getText().add(mCurrentDate.getTime().toString());
586 }
587
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800588 public CharSequence getAccessibilityClassName() {
589 return DatePicker.class.getName();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700590 }
591
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700592 public static int getDaysInMonth(int month, int year) {
593 switch (month) {
594 case Calendar.JANUARY:
595 case Calendar.MARCH:
596 case Calendar.MAY:
597 case Calendar.JULY:
598 case Calendar.AUGUST:
599 case Calendar.OCTOBER:
600 case Calendar.DECEMBER:
601 return 31;
602 case Calendar.APRIL:
603 case Calendar.JUNE:
604 case Calendar.SEPTEMBER:
605 case Calendar.NOVEMBER:
606 return 30;
607 case Calendar.FEBRUARY:
608 return (year % 4 == 0) ? 29 : 28;
609 default:
610 throw new IllegalArgumentException("Invalid Month");
611 }
612 }
613
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700614 private void tryVibrate() {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700615 mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
616 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700617}