blob: b15d866df16c99b00ce509ae5f02480ec629cbf3 [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;
Alan Viverettec3e5a822016-05-16 13:35:56 -040027import android.icu.text.DisplayContext;
28import android.icu.text.SimpleDateFormat;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070029import android.os.Parcelable;
30import android.text.format.DateFormat;
31import android.text.format.DateUtils;
32import android.util.AttributeSet;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070033import android.util.StateSet;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070034import android.view.HapticFeedbackConstants;
35import android.view.LayoutInflater;
36import android.view.View;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070037import android.view.View.OnClickListener;
38import android.view.ViewGroup;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070039import android.view.accessibility.AccessibilityEvent;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070040import android.widget.DayPickerView.OnDaySelectedListener;
41import android.widget.YearPickerView.OnYearSelectedListener;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070042
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070043import java.util.Calendar;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070044import java.util.Locale;
45
46/**
47 * A delegate for picking up a date (day / month / year).
48 */
Alan Viverette0ef59ac2015-03-23 13:13:25 -070049class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
Alan Viverette0a04bb02014-09-03 20:48:02 -070050 private static final int USE_LOCALE = 0;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070051
52 private static final int UNINITIALIZED = -1;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070053 private static final int VIEW_MONTH_DAY = 0;
54 private static final int VIEW_YEAR = 1;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070055
56 private static final int DEFAULT_START_YEAR = 1900;
57 private static final int DEFAULT_END_YEAR = 2100;
58
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070059 private static final int ANIMATION_DURATION = 300;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070060
Alan Viverette60b674e2015-03-25 13:00:42 -070061 private static final int[] ATTRS_TEXT_COLOR = new int[] {
62 com.android.internal.R.attr.textColor};
63 private static final int[] ATTRS_DISABLED_ALPHA = new int[] {
Alan Viverette0ef59ac2015-03-23 13:13:25 -070064 com.android.internal.R.attr.disabledAlpha};
Alan Viverette7119d0d2014-08-25 17:27:02 -070065
Alan Viverette0ef59ac2015-03-23 13:13:25 -070066 private SimpleDateFormat mYearFormat;
67 private SimpleDateFormat mMonthDayFormat;
Alan Viverette7119d0d2014-08-25 17:27:02 -070068
Alan Viverette0ef59ac2015-03-23 13:13:25 -070069 // Top-level container.
70 private ViewGroup mContainer;
Alan Viverette7119d0d2014-08-25 17:27:02 -070071
Alan Viverette0ef59ac2015-03-23 13:13:25 -070072 // Header views.
73 private TextView mHeaderYear;
74 private TextView mHeaderMonthDay;
75
76 // Picker views.
77 private ViewAnimator mAnimator;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070078 private DayPickerView mDayPickerView;
79 private YearPickerView mYearPickerView;
80
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070081 // Accessibility strings.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070082 private String mSelectDay;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070083 private String mSelectYear;
84
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070085 private DatePicker.OnDateChangedListener mDateChangedListener;
86
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070087 private int mCurrentView = UNINITIALIZED;
88
Alan Viverette452fe342015-03-23 14:26:09 -070089 private final Calendar mCurrentDate;
90 private final Calendar mTempDate;
91 private final Calendar mMinDate;
92 private final Calendar mMaxDate;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070093
Alan Viverette0a04bb02014-09-03 20:48:02 -070094 private int mFirstDayOfWeek = USE_LOCALE;
95
Chet Haase3053b2f2014-08-06 07:51:50 -070096 public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070097 int defStyleAttr, int defStyleRes) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070098 super(delegator, context);
99
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700100 final Locale locale = mCurrentLocale;
Alan Viverette452fe342015-03-23 14:26:09 -0700101 mCurrentDate = Calendar.getInstance(locale);
102 mTempDate = Calendar.getInstance(locale);
103 mMinDate = Calendar.getInstance(locale);
104 mMaxDate = Calendar.getInstance(locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700105
You Kim49e6c4a2015-02-26 19:06:16 +0900106 mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
107 mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700108
Alan Viverette60727e02014-07-28 16:56:32 -0700109 final Resources res = mDelegator.getResources();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700110 final TypedArray a = mContext.obtainStyledAttributes(attrs,
111 R.styleable.DatePicker, defStyleAttr, defStyleRes);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700112 final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
113 Context.LAYOUT_INFLATER_SERVICE);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700114 final int layoutResourceId = a.getResourceId(
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700115 R.styleable.DatePicker_internalLayout, R.layout.date_picker_material);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700116
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700117 // Set up and attach container.
Alan Viverettef7aa9252015-06-03 14:07:26 -0700118 mContainer = (ViewGroup) inflater.inflate(layoutResourceId, mDelegator, false);
119 mDelegator.addView(mContainer);
Alan Viverette7119d0d2014-08-25 17:27:02 -0700120
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700121 // Set up header views.
122 final ViewGroup header = (ViewGroup) mContainer.findViewById(R.id.date_picker_header);
123 mHeaderYear = (TextView) header.findViewById(R.id.date_picker_header_year);
124 mHeaderYear.setOnClickListener(mOnHeaderClickListener);
125 mHeaderMonthDay = (TextView) header.findViewById(R.id.date_picker_header_date);
126 mHeaderMonthDay.setOnClickListener(mOnHeaderClickListener);
Alan Viverette60727e02014-07-28 16:56:32 -0700127
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700128 // For the sake of backwards compatibility, attempt to extract the text
129 // color from the header month text appearance. If it's set, we'll let
130 // that override the "real" header text color.
131 ColorStateList headerTextColor = null;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700132
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700133 @SuppressWarnings("deprecation")
134 final int monthHeaderTextAppearance = a.getResourceId(
Alan Viverette4e5168f2015-01-08 15:24:45 -0800135 R.styleable.DatePicker_headerMonthTextAppearance, 0);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700136 if (monthHeaderTextAppearance != 0) {
137 final TypedArray textAppearance = mContext.obtainStyledAttributes(null,
138 ATTRS_TEXT_COLOR, 0, monthHeaderTextAppearance);
139 final ColorStateList legacyHeaderTextColor = textAppearance.getColorStateList(0);
140 headerTextColor = applyLegacyColorFixes(legacyHeaderTextColor);
141 textAppearance.recycle();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700142 }
143
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700144 if (headerTextColor == null) {
145 headerTextColor = a.getColorStateList(R.styleable.DatePicker_headerTextColor);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700146 }
147
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700148 if (headerTextColor != null) {
149 mHeaderYear.setTextColor(headerTextColor);
150 mHeaderMonthDay.setTextColor(headerTextColor);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700151 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700152
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700153 // Set up header background, if available.
154 if (a.hasValueOrEmpty(R.styleable.DatePicker_headerBackground)) {
155 header.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground));
156 }
157
Alan Viverette60b674e2015-03-25 13:00:42 -0700158 a.recycle();
159
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700160 // Set up picker container.
161 mAnimator = (ViewAnimator) mContainer.findViewById(R.id.animator);
162
163 // Set up day picker view.
164 mDayPickerView = (DayPickerView) mAnimator.findViewById(R.id.date_picker_day_picker);
Alan Viverettee763c9b2014-11-06 15:22:31 -0800165 mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek);
Alan Viverette46127402014-11-13 10:50:37 -0800166 mDayPickerView.setMinDate(mMinDate.getTimeInMillis());
167 mDayPickerView.setMaxDate(mMaxDate.getTimeInMillis());
168 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
Alan Viverettee763c9b2014-11-06 15:22:31 -0800169 mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener);
Alan Viverette50eb0252014-10-24 14:34:26 -0700170
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700171 // Set up year picker view.
172 mYearPickerView = (YearPickerView) mAnimator.findViewById(R.id.date_picker_year_picker);
Alan Viverette2a90fa62015-02-17 13:09:04 -0800173 mYearPickerView.setRange(mMinDate, mMaxDate);
George Mounte998c3f2015-10-27 08:46:44 -0700174 mYearPickerView.setYear(mCurrentDate.get(Calendar.YEAR));
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700175 mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener);
Alan Viverette4e5168f2015-01-08 15:24:45 -0800176
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700177 // Set up content descriptions.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700178 mSelectDay = res.getString(R.string.select_day);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700179 mSelectYear = res.getString(R.string.select_year);
180
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700181 // Initialize for current locale. This also initializes the date, so no
182 // need to call onDateChanged.
183 onLocaleChanged(mCurrentLocale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700184
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700185 setCurrentView(VIEW_MONTH_DAY);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700186 }
187
188 /**
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700189 * The legacy text color might have been poorly defined. Ensures that it
190 * has an appropriate activated state, using the selected state if one
191 * exists or modifying the default text color otherwise.
192 *
193 * @param color a legacy text color, or {@code null}
194 * @return a color state list with an appropriate activated state, or
195 * {@code null} if a valid activated state could not be generated
196 */
197 @Nullable
198 private ColorStateList applyLegacyColorFixes(@Nullable ColorStateList color) {
199 if (color == null || color.hasState(R.attr.state_activated)) {
200 return color;
201 }
202
203 final int activatedColor;
204 final int defaultColor;
205 if (color.hasState(R.attr.state_selected)) {
206 activatedColor = color.getColorForState(StateSet.get(
207 StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_SELECTED), 0);
208 defaultColor = color.getColorForState(StateSet.get(
209 StateSet.VIEW_STATE_ENABLED), 0);
210 } else {
211 activatedColor = color.getDefaultColor();
212
213 // Generate a non-activated color using the disabled alpha.
214 final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA);
215 final float disabledAlpha = ta.getFloat(0, 0.30f);
216 defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha);
217 }
218
219 if (activatedColor == 0 || defaultColor == 0) {
220 // We somehow failed to obtain the colors.
221 return null;
222 }
223
224 final int[][] stateSet = new int[][] {{ R.attr.state_activated }, {}};
225 final int[] colors = new int[] { activatedColor, defaultColor };
226 return new ColorStateList(stateSet, colors);
227 }
228
229 private int multiplyAlphaComponent(int color, float alphaMod) {
230 final int srcRgb = color & 0xFFFFFF;
231 final int srcAlpha = (color >> 24) & 0xFF;
232 final int dstAlpha = (int) (srcAlpha * alphaMod + 0.5f);
233 return srcRgb | (dstAlpha << 24);
234 }
235
236 /**
237 * Listener called when the user selects a day in the day picker view.
238 */
239 private final OnDaySelectedListener mOnDaySelectedListener = new OnDaySelectedListener() {
240 @Override
241 public void onDaySelected(DayPickerView view, Calendar day) {
242 mCurrentDate.setTimeInMillis(day.getTimeInMillis());
243 onDateChanged(true, true);
244 }
245 };
246
247 /**
248 * Listener called when the user selects a year in the year picker view.
249 */
250 private final OnYearSelectedListener mOnYearSelectedListener = new OnYearSelectedListener() {
251 @Override
252 public void onYearChanged(YearPickerView view, int year) {
253 // If the newly selected month / year does not contain the
254 // currently selected day number, change the selected day number
255 // to the last day of the selected month or year.
256 // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
257 // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
258 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
259 final int month = mCurrentDate.get(Calendar.MONTH);
260 final int daysInMonth = getDaysInMonth(month, year);
261 if (day > daysInMonth) {
262 mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth);
263 }
264
265 mCurrentDate.set(Calendar.YEAR, year);
266 onDateChanged(true, true);
267
268 // Automatically switch to day picker.
269 setCurrentView(VIEW_MONTH_DAY);
George Mounte998c3f2015-10-27 08:46:44 -0700270
271 // Switch focus back to the year text.
272 mHeaderYear.requestFocus();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700273 }
274 };
275
276 /**
277 * Listener called when the user clicks on a header item.
278 */
279 private final OnClickListener mOnHeaderClickListener = new OnClickListener() {
280 @Override
281 public void onClick(View v) {
282 tryVibrate();
283
284 switch (v.getId()) {
285 case R.id.date_picker_header_year:
286 setCurrentView(VIEW_YEAR);
287 break;
288 case R.id.date_picker_header_date:
289 setCurrentView(VIEW_MONTH_DAY);
290 break;
291 }
292 }
293 };
294
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700295 @Override
296 protected void onLocaleChanged(Locale locale) {
297 final TextView headerYear = mHeaderYear;
298 if (headerYear == null) {
299 // Abort, we haven't initialized yet. This method will get called
300 // again later after everything has been set up.
301 return;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700302 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700303
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700304 // Update the date formatter.
305 final String datePattern = DateFormat.getBestDateTimePattern(locale, "EMMMd");
306 mMonthDayFormat = new SimpleDateFormat(datePattern, locale);
Alan Viverettec3e5a822016-05-16 13:35:56 -0400307 mMonthDayFormat.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700308 mYearFormat = new SimpleDateFormat("y", locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700309
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700310 // Update the header text.
311 onCurrentDateChanged(false);
312 }
313
314 private void onCurrentDateChanged(boolean announce) {
315 if (mHeaderYear == null) {
316 // Abort, we haven't initialized yet. This method will get called
317 // again later after everything has been set up.
318 return;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700319 }
320
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700321 final String year = mYearFormat.format(mCurrentDate.getTime());
322 mHeaderYear.setText(year);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700323
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700324 final String monthDay = mMonthDayFormat.format(mCurrentDate.getTime());
325 mHeaderMonthDay.setText(monthDay);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700326
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700327 // TODO: This should use live regions.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700328 if (announce) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700329 final long millis = mCurrentDate.getTimeInMillis();
330 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
331 final String fullDateText = DateUtils.formatDateTime(mContext, millis, flags);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700332 mAnimator.announceForAccessibility(fullDateText);
333 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700334 }
335
336 private void setCurrentView(final int viewIndex) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700337 switch (viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700338 case VIEW_MONTH_DAY:
339 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
340
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700341 if (mCurrentView != viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700342 mHeaderMonthDay.setActivated(true);
343 mHeaderYear.setActivated(false);
344 mAnimator.setDisplayedChild(VIEW_MONTH_DAY);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700345 mCurrentView = viewIndex;
346 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700347
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700348 mAnimator.announceForAccessibility(mSelectDay);
349 break;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700350 case VIEW_YEAR:
George Mounte998c3f2015-10-27 08:46:44 -0700351 final int year = mCurrentDate.get(Calendar.YEAR);
352 mYearPickerView.setYear(year);
353 mYearPickerView.post(new Runnable() {
354 @Override
355 public void run() {
356 mYearPickerView.requestFocus();
357 final View selected = mYearPickerView.getSelectedView();
358 if (selected != null) {
359 selected.requestFocus();
360 }
361 }
362 });
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700363
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700364 if (mCurrentView != viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700365 mHeaderMonthDay.setActivated(false);
366 mHeaderYear.setActivated(true);
367 mAnimator.setDisplayedChild(VIEW_YEAR);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700368 mCurrentView = viewIndex;
369 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700370
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700371 mAnimator.announceForAccessibility(mSelectYear);
372 break;
373 }
374 }
375
376 @Override
377 public void init(int year, int monthOfYear, int dayOfMonth,
378 DatePicker.OnDateChangedListener callBack) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700379 mCurrentDate.set(Calendar.YEAR, year);
380 mCurrentDate.set(Calendar.MONTH, monthOfYear);
381 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800382
Alan Viverette0cecbc92014-12-12 14:32:43 -0800383 onDateChanged(false, false);
Alan Viverettea9a75f52016-04-01 13:22:10 -0400384
385 mDateChangedListener = callBack;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700386 }
387
388 @Override
389 public void updateDate(int year, int month, int dayOfMonth) {
390 mCurrentDate.set(Calendar.YEAR, year);
391 mCurrentDate.set(Calendar.MONTH, month);
392 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800393
Alan Viverette0cecbc92014-12-12 14:32:43 -0800394 onDateChanged(false, true);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800395 }
396
Alan Viverette0cecbc92014-12-12 14:32:43 -0800397 private void onDateChanged(boolean fromUser, boolean callbackToClient) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700398 final int year = mCurrentDate.get(Calendar.YEAR);
399
Alan Viverette0cecbc92014-12-12 14:32:43 -0800400 if (callbackToClient && mDateChangedListener != null) {
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800401 final int monthOfYear = mCurrentDate.get(Calendar.MONTH);
402 final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH);
403 mDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
Alan Viverette9468c6a2014-08-21 13:56:54 -0700404 }
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800405
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700406 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
407 mYearPickerView.setYear(year);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800408
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700409 onCurrentDateChanged(fromUser);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800410
411 if (fromUser) {
412 tryVibrate();
413 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700414 }
415
416 @Override
417 public int getYear() {
418 return mCurrentDate.get(Calendar.YEAR);
419 }
420
421 @Override
422 public int getMonth() {
423 return mCurrentDate.get(Calendar.MONTH);
424 }
425
426 @Override
427 public int getDayOfMonth() {
428 return mCurrentDate.get(Calendar.DAY_OF_MONTH);
429 }
430
431 @Override
432 public void setMinDate(long minDate) {
433 mTempDate.setTimeInMillis(minDate);
434 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
Alan Viverette3fb5c7b2016-05-25 12:34:55 -0400435 && mTempDate.get(Calendar.DAY_OF_YEAR) == mMinDate.get(Calendar.DAY_OF_YEAR)) {
436 // Same day, no-op.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700437 return;
438 }
439 if (mCurrentDate.before(mTempDate)) {
440 mCurrentDate.setTimeInMillis(minDate);
Alan Viverette0cecbc92014-12-12 14:32:43 -0800441 onDateChanged(false, true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700442 }
443 mMinDate.setTimeInMillis(minDate);
Alan Viverette46127402014-11-13 10:50:37 -0800444 mDayPickerView.setMinDate(minDate);
Alan Viverette50eb0252014-10-24 14:34:26 -0700445 mYearPickerView.setRange(mMinDate, mMaxDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700446 }
447
448 @Override
449 public Calendar getMinDate() {
450 return mMinDate;
451 }
452
453 @Override
454 public void setMaxDate(long maxDate) {
455 mTempDate.setTimeInMillis(maxDate);
456 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
Alan Viverette3fb5c7b2016-05-25 12:34:55 -0400457 && mTempDate.get(Calendar.DAY_OF_YEAR) == mMaxDate.get(Calendar.DAY_OF_YEAR)) {
458 // Same day, no-op.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700459 return;
460 }
461 if (mCurrentDate.after(mTempDate)) {
462 mCurrentDate.setTimeInMillis(maxDate);
Alan Viverette0cecbc92014-12-12 14:32:43 -0800463 onDateChanged(false, true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700464 }
465 mMaxDate.setTimeInMillis(maxDate);
Alan Viverette46127402014-11-13 10:50:37 -0800466 mDayPickerView.setMaxDate(maxDate);
Alan Viverette50eb0252014-10-24 14:34:26 -0700467 mYearPickerView.setRange(mMinDate, mMaxDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700468 }
469
470 @Override
471 public Calendar getMaxDate() {
472 return mMaxDate;
473 }
474
475 @Override
Alan Viverette0a04bb02014-09-03 20:48:02 -0700476 public void setFirstDayOfWeek(int firstDayOfWeek) {
477 mFirstDayOfWeek = firstDayOfWeek;
Alan Viverettee763c9b2014-11-06 15:22:31 -0800478
479 mDayPickerView.setFirstDayOfWeek(firstDayOfWeek);
Alan Viverette0a04bb02014-09-03 20:48:02 -0700480 }
481
482 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700483 public int getFirstDayOfWeek() {
Alan Viverette0a04bb02014-09-03 20:48:02 -0700484 if (mFirstDayOfWeek != USE_LOCALE) {
485 return mFirstDayOfWeek;
486 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700487 return mCurrentDate.getFirstDayOfWeek();
488 }
489
490 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700491 public void setEnabled(boolean enabled) {
Alan Viverettef7aa9252015-06-03 14:07:26 -0700492 mContainer.setEnabled(enabled);
493 mDayPickerView.setEnabled(enabled);
494 mYearPickerView.setEnabled(enabled);
495 mHeaderYear.setEnabled(enabled);
496 mHeaderMonthDay.setEnabled(enabled);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700497 }
498
499 @Override
500 public boolean isEnabled() {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700501 return mContainer.isEnabled();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700502 }
503
504 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700505 public CalendarView getCalendarView() {
Alan Viverettef7aa9252015-06-03 14:07:26 -0700506 throw new UnsupportedOperationException("Not supported by calendar-mode DatePicker");
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700507 }
508
509 @Override
510 public void setCalendarViewShown(boolean shown) {
511 // No-op for compatibility with the old DatePicker.
512 }
513
514 @Override
515 public boolean getCalendarViewShown() {
516 return false;
517 }
518
519 @Override
520 public void setSpinnersShown(boolean shown) {
521 // No-op for compatibility with the old DatePicker.
522 }
523
524 @Override
525 public boolean getSpinnersShown() {
526 return false;
527 }
528
529 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700530 public void onConfigurationChanged(Configuration newConfig) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700531 setCurrentLocale(newConfig.locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700532 }
533
534 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700535 public Parcelable onSaveInstanceState(Parcelable superState) {
536 final int year = mCurrentDate.get(Calendar.YEAR);
537 final int month = mCurrentDate.get(Calendar.MONTH);
538 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
539
540 int listPosition = -1;
541 int listPositionOffset = -1;
542
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700543 if (mCurrentView == VIEW_MONTH_DAY) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700544 listPosition = mDayPickerView.getMostVisiblePosition();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700545 } else if (mCurrentView == VIEW_YEAR) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700546 listPosition = mYearPickerView.getFirstVisiblePosition();
547 listPositionOffset = mYearPickerView.getFirstPositionOffset();
548 }
549
550 return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
551 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
552 }
553
554 @Override
555 public void onRestoreInstanceState(Parcelable state) {
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500556 if (state instanceof SavedState) {
557 final SavedState ss = (SavedState) state;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700558
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500559 // TODO: Move instance state into DayPickerView, YearPickerView.
560 mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay());
561 mMinDate.setTimeInMillis(ss.getMinDate());
562 mMaxDate.setTimeInMillis(ss.getMaxDate());
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700563
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500564 onCurrentDateChanged(false);
Alan Viverette816aa142015-04-10 15:41:10 -0700565
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500566 final int currentView = ss.getCurrentView();
567 setCurrentView(currentView);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700568
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500569 final int listPosition = ss.getListPosition();
570 if (listPosition != -1) {
571 if (currentView == VIEW_MONTH_DAY) {
572 mDayPickerView.setPosition(listPosition);
573 } else if (currentView == VIEW_YEAR) {
574 final int listPositionOffset = ss.getListPositionOffset();
575 mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset);
576 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700577 }
578 }
579 }
580
581 @Override
582 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
583 onPopulateAccessibilityEvent(event);
584 return true;
585 }
586
587 @Override
588 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
589 event.getText().add(mCurrentDate.getTime().toString());
590 }
591
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800592 public CharSequence getAccessibilityClassName() {
593 return DatePicker.class.getName();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700594 }
595
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700596 public static int getDaysInMonth(int month, int year) {
597 switch (month) {
598 case Calendar.JANUARY:
599 case Calendar.MARCH:
600 case Calendar.MAY:
601 case Calendar.JULY:
602 case Calendar.AUGUST:
603 case Calendar.OCTOBER:
604 case Calendar.DECEMBER:
605 return 31;
606 case Calendar.APRIL:
607 case Calendar.JUNE:
608 case Calendar.SEPTEMBER:
609 case Calendar.NOVEMBER:
610 return 30;
611 case Calendar.FEBRUARY:
612 return (year % 4 == 0) ? 29 : 28;
613 default:
614 throw new IllegalArgumentException("Invalid Month");
615 }
616 }
617
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700618 private void tryVibrate() {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700619 mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
620 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700621}