blob: 53e145eb15fd0da36895c39149b46dc2034069bb [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 Viverette0ef59ac2015-03-23 13:13:25 -070019import android.annotation.Nullable;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070020import android.content.Context;
21import android.content.res.ColorStateList;
22import android.content.res.Configuration;
23import android.content.res.Resources;
24import android.content.res.TypedArray;
Alan Viveretteca38c432017-08-18 10:44:08 -040025import android.icu.text.DateFormat;
Alan Viverettec3e5a822016-05-16 13:35:56 -040026import android.icu.text.DisplayContext;
Alan Viverette68763be2016-05-25 11:42:42 -040027import android.icu.util.Calendar;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070028import android.os.Parcelable;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070029import android.util.AttributeSet;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070030import android.util.StateSet;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070031import android.view.HapticFeedbackConstants;
32import android.view.LayoutInflater;
33import android.view.View;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070034import android.view.View.OnClickListener;
35import android.view.ViewGroup;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070036import android.view.accessibility.AccessibilityEvent;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070037import android.widget.DayPickerView.OnDaySelectedListener;
38import android.widget.YearPickerView.OnYearSelectedListener;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070039
Aurimas Liutikas99441c52016-10-11 16:48:32 -070040import com.android.internal.R;
41
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070042import java.util.Locale;
43
44/**
45 * A delegate for picking up a date (day / month / year).
46 */
Alan Viverette0ef59ac2015-03-23 13:13:25 -070047class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
Alan Viverette0a04bb02014-09-03 20:48:02 -070048 private static final int USE_LOCALE = 0;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070049
50 private static final int UNINITIALIZED = -1;
Alan Viverette0ef59ac2015-03-23 13:13:25 -070051 private static final int VIEW_MONTH_DAY = 0;
52 private static final int VIEW_YEAR = 1;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070053
54 private static final int DEFAULT_START_YEAR = 1900;
55 private static final int DEFAULT_END_YEAR = 2100;
56
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070057 private static final int ANIMATION_DURATION = 300;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070058
Alan Viverette60b674e2015-03-25 13:00:42 -070059 private static final int[] ATTRS_TEXT_COLOR = new int[] {
60 com.android.internal.R.attr.textColor};
61 private static final int[] ATTRS_DISABLED_ALPHA = new int[] {
Alan Viverette0ef59ac2015-03-23 13:13:25 -070062 com.android.internal.R.attr.disabledAlpha};
Alan Viverette7119d0d2014-08-25 17:27:02 -070063
Alan Viveretteca38c432017-08-18 10:44:08 -040064 private DateFormat mYearFormat;
65 private DateFormat mMonthDayFormat;
Alan Viverette7119d0d2014-08-25 17:27:02 -070066
Alan Viverette0ef59ac2015-03-23 13:13:25 -070067 // Top-level container.
68 private ViewGroup mContainer;
Alan Viverette7119d0d2014-08-25 17:27:02 -070069
Alan Viverette0ef59ac2015-03-23 13:13:25 -070070 // Header views.
71 private TextView mHeaderYear;
72 private TextView mHeaderMonthDay;
73
74 // Picker views.
75 private ViewAnimator mAnimator;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070076 private DayPickerView mDayPickerView;
77 private YearPickerView mYearPickerView;
78
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070079 // Accessibility strings.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070080 private String mSelectDay;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070081 private String mSelectYear;
82
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070083 private int mCurrentView = UNINITIALIZED;
84
Alan Viverette452fe342015-03-23 14:26:09 -070085 private final Calendar mTempDate;
86 private final Calendar mMinDate;
87 private final Calendar mMaxDate;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070088
Alan Viverette0a04bb02014-09-03 20:48:02 -070089 private int mFirstDayOfWeek = USE_LOCALE;
90
Chet Haase3053b2f2014-08-06 07:51:50 -070091 public DatePickerCalendarDelegate(DatePicker delegator, Context context, AttributeSet attrs,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070092 int defStyleAttr, int defStyleRes) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070093 super(delegator, context);
94
Alan Viverette0ef59ac2015-03-23 13:13:25 -070095 final Locale locale = mCurrentLocale;
Alan Viverette452fe342015-03-23 14:26:09 -070096 mCurrentDate = Calendar.getInstance(locale);
97 mTempDate = Calendar.getInstance(locale);
98 mMinDate = Calendar.getInstance(locale);
99 mMaxDate = Calendar.getInstance(locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700100
You Kim49e6c4a2015-02-26 19:06:16 +0900101 mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
102 mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700103
Alan Viverette60727e02014-07-28 16:56:32 -0700104 final Resources res = mDelegator.getResources();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700105 final TypedArray a = mContext.obtainStyledAttributes(attrs,
106 R.styleable.DatePicker, defStyleAttr, defStyleRes);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700107 final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
108 Context.LAYOUT_INFLATER_SERVICE);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700109 final int layoutResourceId = a.getResourceId(
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700110 R.styleable.DatePicker_internalLayout, R.layout.date_picker_material);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700111
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700112 // Set up and attach container.
Alan Viverettef7aa9252015-06-03 14:07:26 -0700113 mContainer = (ViewGroup) inflater.inflate(layoutResourceId, mDelegator, false);
Adam Powell43da25c2017-05-23 15:56:59 -0700114 mContainer.setSaveFromParentEnabled(false);
Alan Viverettef7aa9252015-06-03 14:07:26 -0700115 mDelegator.addView(mContainer);
Alan Viverette7119d0d2014-08-25 17:27:02 -0700116
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700117 // Set up header views.
Alan Viverette8e1a7292017-02-27 10:57:58 -0500118 final ViewGroup header = mContainer.findViewById(R.id.date_picker_header);
119 mHeaderYear = header.findViewById(R.id.date_picker_header_year);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700120 mHeaderYear.setOnClickListener(mOnHeaderClickListener);
Alan Viverette8e1a7292017-02-27 10:57:58 -0500121 mHeaderMonthDay = header.findViewById(R.id.date_picker_header_date);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700122 mHeaderMonthDay.setOnClickListener(mOnHeaderClickListener);
Alan Viverette60727e02014-07-28 16:56:32 -0700123
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700124 // For the sake of backwards compatibility, attempt to extract the text
125 // color from the header month text appearance. If it's set, we'll let
126 // that override the "real" header text color.
127 ColorStateList headerTextColor = null;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700128
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700129 @SuppressWarnings("deprecation")
130 final int monthHeaderTextAppearance = a.getResourceId(
Alan Viverette4e5168f2015-01-08 15:24:45 -0800131 R.styleable.DatePicker_headerMonthTextAppearance, 0);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700132 if (monthHeaderTextAppearance != 0) {
133 final TypedArray textAppearance = mContext.obtainStyledAttributes(null,
134 ATTRS_TEXT_COLOR, 0, monthHeaderTextAppearance);
135 final ColorStateList legacyHeaderTextColor = textAppearance.getColorStateList(0);
136 headerTextColor = applyLegacyColorFixes(legacyHeaderTextColor);
137 textAppearance.recycle();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700138 }
139
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700140 if (headerTextColor == null) {
141 headerTextColor = a.getColorStateList(R.styleable.DatePicker_headerTextColor);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700142 }
143
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700144 if (headerTextColor != null) {
145 mHeaderYear.setTextColor(headerTextColor);
146 mHeaderMonthDay.setTextColor(headerTextColor);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700147 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700148
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700149 // Set up header background, if available.
150 if (a.hasValueOrEmpty(R.styleable.DatePicker_headerBackground)) {
151 header.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground));
152 }
153
Alan Viverette60b674e2015-03-25 13:00:42 -0700154 a.recycle();
155
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700156 // Set up picker container.
Alan Viverette8e1a7292017-02-27 10:57:58 -0500157 mAnimator = mContainer.findViewById(R.id.animator);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700158
159 // Set up day picker view.
Alan Viverette8e1a7292017-02-27 10:57:58 -0500160 mDayPickerView = mAnimator.findViewById(R.id.date_picker_day_picker);
Alan Viverettee763c9b2014-11-06 15:22:31 -0800161 mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek);
Alan Viverette46127402014-11-13 10:50:37 -0800162 mDayPickerView.setMinDate(mMinDate.getTimeInMillis());
163 mDayPickerView.setMaxDate(mMaxDate.getTimeInMillis());
164 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
Alan Viverettee763c9b2014-11-06 15:22:31 -0800165 mDayPickerView.setOnDaySelectedListener(mOnDaySelectedListener);
Alan Viverette50eb0252014-10-24 14:34:26 -0700166
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700167 // Set up year picker view.
Alan Viverette8e1a7292017-02-27 10:57:58 -0500168 mYearPickerView = mAnimator.findViewById(R.id.date_picker_year_picker);
Alan Viverette2a90fa62015-02-17 13:09:04 -0800169 mYearPickerView.setRange(mMinDate, mMaxDate);
George Mounte998c3f2015-10-27 08:46:44 -0700170 mYearPickerView.setYear(mCurrentDate.get(Calendar.YEAR));
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700171 mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener);
Alan Viverette4e5168f2015-01-08 15:24:45 -0800172
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700173 // Set up content descriptions.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700174 mSelectDay = res.getString(R.string.select_day);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700175 mSelectYear = res.getString(R.string.select_year);
176
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700177 // Initialize for current locale. This also initializes the date, so no
178 // need to call onDateChanged.
179 onLocaleChanged(mCurrentLocale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700180
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700181 setCurrentView(VIEW_MONTH_DAY);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700182 }
183
184 /**
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700185 * The legacy text color might have been poorly defined. Ensures that it
186 * has an appropriate activated state, using the selected state if one
187 * exists or modifying the default text color otherwise.
188 *
189 * @param color a legacy text color, or {@code null}
190 * @return a color state list with an appropriate activated state, or
191 * {@code null} if a valid activated state could not be generated
192 */
193 @Nullable
194 private ColorStateList applyLegacyColorFixes(@Nullable ColorStateList color) {
195 if (color == null || color.hasState(R.attr.state_activated)) {
196 return color;
197 }
198
199 final int activatedColor;
200 final int defaultColor;
201 if (color.hasState(R.attr.state_selected)) {
202 activatedColor = color.getColorForState(StateSet.get(
203 StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_SELECTED), 0);
204 defaultColor = color.getColorForState(StateSet.get(
205 StateSet.VIEW_STATE_ENABLED), 0);
206 } else {
207 activatedColor = color.getDefaultColor();
208
209 // Generate a non-activated color using the disabled alpha.
210 final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA);
211 final float disabledAlpha = ta.getFloat(0, 0.30f);
212 defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha);
213 }
214
215 if (activatedColor == 0 || defaultColor == 0) {
216 // We somehow failed to obtain the colors.
217 return null;
218 }
219
220 final int[][] stateSet = new int[][] {{ R.attr.state_activated }, {}};
221 final int[] colors = new int[] { activatedColor, defaultColor };
222 return new ColorStateList(stateSet, colors);
223 }
224
225 private int multiplyAlphaComponent(int color, float alphaMod) {
226 final int srcRgb = color & 0xFFFFFF;
227 final int srcAlpha = (color >> 24) & 0xFF;
228 final int dstAlpha = (int) (srcAlpha * alphaMod + 0.5f);
229 return srcRgb | (dstAlpha << 24);
230 }
231
232 /**
233 * Listener called when the user selects a day in the day picker view.
234 */
235 private final OnDaySelectedListener mOnDaySelectedListener = new OnDaySelectedListener() {
236 @Override
237 public void onDaySelected(DayPickerView view, Calendar day) {
238 mCurrentDate.setTimeInMillis(day.getTimeInMillis());
239 onDateChanged(true, true);
240 }
241 };
242
243 /**
244 * Listener called when the user selects a year in the year picker view.
245 */
246 private final OnYearSelectedListener mOnYearSelectedListener = new OnYearSelectedListener() {
247 @Override
248 public void onYearChanged(YearPickerView view, int year) {
249 // If the newly selected month / year does not contain the
250 // currently selected day number, change the selected day number
251 // to the last day of the selected month or year.
252 // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
253 // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
254 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
255 final int month = mCurrentDate.get(Calendar.MONTH);
256 final int daysInMonth = getDaysInMonth(month, year);
257 if (day > daysInMonth) {
258 mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth);
259 }
260
261 mCurrentDate.set(Calendar.YEAR, year);
262 onDateChanged(true, true);
263
264 // Automatically switch to day picker.
265 setCurrentView(VIEW_MONTH_DAY);
George Mounte998c3f2015-10-27 08:46:44 -0700266
267 // Switch focus back to the year text.
268 mHeaderYear.requestFocus();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700269 }
270 };
271
272 /**
273 * Listener called when the user clicks on a header item.
274 */
Alan Viveretteca38c432017-08-18 10:44:08 -0400275 private final OnClickListener mOnHeaderClickListener = v -> {
276 tryVibrate();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700277
Alan Viveretteca38c432017-08-18 10:44:08 -0400278 switch (v.getId()) {
279 case R.id.date_picker_header_year:
280 setCurrentView(VIEW_YEAR);
281 break;
282 case R.id.date_picker_header_date:
283 setCurrentView(VIEW_MONTH_DAY);
284 break;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700285 }
286 };
287
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700288 @Override
289 protected void onLocaleChanged(Locale locale) {
290 final TextView headerYear = mHeaderYear;
291 if (headerYear == null) {
292 // Abort, we haven't initialized yet. This method will get called
293 // again later after everything has been set up.
294 return;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700295 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700296
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700297 // Update the date formatter.
Alan Viveretteca38c432017-08-18 10:44:08 -0400298 mMonthDayFormat = DateFormat.getInstanceForSkeleton("EMMMd", locale);
Alan Viverettec3e5a822016-05-16 13:35:56 -0400299 mMonthDayFormat.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
Alan Viveretteca38c432017-08-18 10:44:08 -0400300 mYearFormat = DateFormat.getInstanceForSkeleton("y", locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700301
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700302 // Update the header text.
303 onCurrentDateChanged(false);
304 }
305
306 private void onCurrentDateChanged(boolean announce) {
307 if (mHeaderYear == null) {
308 // Abort, we haven't initialized yet. This method will get called
309 // again later after everything has been set up.
310 return;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700311 }
312
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700313 final String year = mYearFormat.format(mCurrentDate.getTime());
314 mHeaderYear.setText(year);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700315
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700316 final String monthDay = mMonthDayFormat.format(mCurrentDate.getTime());
317 mHeaderMonthDay.setText(monthDay);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700318
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700319 // TODO: This should use live regions.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700320 if (announce) {
Andrei Stingaceanudd80f002016-06-16 12:00:39 +0100321 mAnimator.announceForAccessibility(getFormattedCurrentDate());
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700322 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700323 }
324
325 private void setCurrentView(final int viewIndex) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700326 switch (viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700327 case VIEW_MONTH_DAY:
328 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
329
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700330 if (mCurrentView != viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700331 mHeaderMonthDay.setActivated(true);
332 mHeaderYear.setActivated(false);
333 mAnimator.setDisplayedChild(VIEW_MONTH_DAY);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700334 mCurrentView = viewIndex;
335 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700336
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700337 mAnimator.announceForAccessibility(mSelectDay);
338 break;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700339 case VIEW_YEAR:
George Mounte998c3f2015-10-27 08:46:44 -0700340 final int year = mCurrentDate.get(Calendar.YEAR);
341 mYearPickerView.setYear(year);
Alan Viveretteca38c432017-08-18 10:44:08 -0400342 mYearPickerView.post(() -> {
343 mYearPickerView.requestFocus();
344 final View selected = mYearPickerView.getSelectedView();
345 if (selected != null) {
346 selected.requestFocus();
George Mounte998c3f2015-10-27 08:46:44 -0700347 }
348 });
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700349
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700350 if (mCurrentView != viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700351 mHeaderMonthDay.setActivated(false);
352 mHeaderYear.setActivated(true);
353 mAnimator.setDisplayedChild(VIEW_YEAR);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700354 mCurrentView = viewIndex;
355 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700356
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700357 mAnimator.announceForAccessibility(mSelectYear);
358 break;
359 }
360 }
361
362 @Override
Felipe Lemef480e8c2017-08-10 18:38:44 -0700363 public void init(int year, int month, int dayOfMonth,
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700364 DatePicker.OnDateChangedListener callBack) {
Felipe Lemef480e8c2017-08-10 18:38:44 -0700365 setDate(year, month, dayOfMonth);
Alan Viverette0cecbc92014-12-12 14:32:43 -0800366 onDateChanged(false, false);
Alan Viverettea9a75f52016-04-01 13:22:10 -0400367
Clara Bayarri68640b62016-06-02 14:56:18 +0100368 mOnDateChangedListener = callBack;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700369 }
370
371 @Override
372 public void updateDate(int year, int month, int dayOfMonth) {
Felipe Lemef480e8c2017-08-10 18:38:44 -0700373 setDate(year, month, dayOfMonth);
374 onDateChanged(false, true);
375 }
376
377 private void setDate(int year, int month, int dayOfMonth) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700378 mCurrentDate.set(Calendar.YEAR, year);
379 mCurrentDate.set(Calendar.MONTH, month);
380 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
Felipe Lemef480e8c2017-08-10 18:38:44 -0700381 resetAutofilledValue();
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800382 }
383
Alan Viverette0cecbc92014-12-12 14:32:43 -0800384 private void onDateChanged(boolean fromUser, boolean callbackToClient) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700385 final int year = mCurrentDate.get(Calendar.YEAR);
386
Felipe Leme305b72c2017-02-27 12:46:04 -0800387 if (callbackToClient
388 && (mOnDateChangedListener != null || mAutoFillChangeListener != null)) {
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800389 final int monthOfYear = mCurrentDate.get(Calendar.MONTH);
390 final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH);
Felipe Leme305b72c2017-02-27 12:46:04 -0800391 if (mOnDateChangedListener != null) {
392 mOnDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
393 }
394 if (mAutoFillChangeListener != null) {
395 mAutoFillChangeListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
396 }
Alan Viverette9468c6a2014-08-21 13:56:54 -0700397 }
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800398
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700399 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
400 mYearPickerView.setYear(year);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800401
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700402 onCurrentDateChanged(fromUser);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800403
404 if (fromUser) {
405 tryVibrate();
406 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700407 }
408
409 @Override
410 public int getYear() {
411 return mCurrentDate.get(Calendar.YEAR);
412 }
413
414 @Override
415 public int getMonth() {
416 return mCurrentDate.get(Calendar.MONTH);
417 }
418
419 @Override
420 public int getDayOfMonth() {
421 return mCurrentDate.get(Calendar.DAY_OF_MONTH);
422 }
423
424 @Override
425 public void setMinDate(long minDate) {
426 mTempDate.setTimeInMillis(minDate);
427 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
Alan Viverette3fb5c7b2016-05-25 12:34:55 -0400428 && mTempDate.get(Calendar.DAY_OF_YEAR) == mMinDate.get(Calendar.DAY_OF_YEAR)) {
429 // Same day, no-op.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700430 return;
431 }
432 if (mCurrentDate.before(mTempDate)) {
433 mCurrentDate.setTimeInMillis(minDate);
Alan Viverette0cecbc92014-12-12 14:32:43 -0800434 onDateChanged(false, true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700435 }
436 mMinDate.setTimeInMillis(minDate);
Alan Viverette46127402014-11-13 10:50:37 -0800437 mDayPickerView.setMinDate(minDate);
Alan Viverette50eb0252014-10-24 14:34:26 -0700438 mYearPickerView.setRange(mMinDate, mMaxDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700439 }
440
441 @Override
442 public Calendar getMinDate() {
443 return mMinDate;
444 }
445
446 @Override
447 public void setMaxDate(long maxDate) {
448 mTempDate.setTimeInMillis(maxDate);
449 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
Alan Viverette3fb5c7b2016-05-25 12:34:55 -0400450 && mTempDate.get(Calendar.DAY_OF_YEAR) == mMaxDate.get(Calendar.DAY_OF_YEAR)) {
451 // Same day, no-op.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700452 return;
453 }
454 if (mCurrentDate.after(mTempDate)) {
455 mCurrentDate.setTimeInMillis(maxDate);
Alan Viverette0cecbc92014-12-12 14:32:43 -0800456 onDateChanged(false, true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700457 }
458 mMaxDate.setTimeInMillis(maxDate);
Alan Viverette46127402014-11-13 10:50:37 -0800459 mDayPickerView.setMaxDate(maxDate);
Alan Viverette50eb0252014-10-24 14:34:26 -0700460 mYearPickerView.setRange(mMinDate, mMaxDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700461 }
462
463 @Override
464 public Calendar getMaxDate() {
465 return mMaxDate;
466 }
467
468 @Override
Alan Viverette0a04bb02014-09-03 20:48:02 -0700469 public void setFirstDayOfWeek(int firstDayOfWeek) {
470 mFirstDayOfWeek = firstDayOfWeek;
Alan Viverettee763c9b2014-11-06 15:22:31 -0800471
472 mDayPickerView.setFirstDayOfWeek(firstDayOfWeek);
Alan Viverette0a04bb02014-09-03 20:48:02 -0700473 }
474
475 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700476 public int getFirstDayOfWeek() {
Alan Viverette0a04bb02014-09-03 20:48:02 -0700477 if (mFirstDayOfWeek != USE_LOCALE) {
478 return mFirstDayOfWeek;
479 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700480 return mCurrentDate.getFirstDayOfWeek();
481 }
482
483 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700484 public void setEnabled(boolean enabled) {
Alan Viverettef7aa9252015-06-03 14:07:26 -0700485 mContainer.setEnabled(enabled);
486 mDayPickerView.setEnabled(enabled);
487 mYearPickerView.setEnabled(enabled);
488 mHeaderYear.setEnabled(enabled);
489 mHeaderMonthDay.setEnabled(enabled);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700490 }
491
492 @Override
493 public boolean isEnabled() {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700494 return mContainer.isEnabled();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700495 }
496
497 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700498 public CalendarView getCalendarView() {
Alan Viverettef7aa9252015-06-03 14:07:26 -0700499 throw new UnsupportedOperationException("Not supported by calendar-mode DatePicker");
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700500 }
501
502 @Override
503 public void setCalendarViewShown(boolean shown) {
504 // No-op for compatibility with the old DatePicker.
505 }
506
507 @Override
508 public boolean getCalendarViewShown() {
509 return false;
510 }
511
512 @Override
513 public void setSpinnersShown(boolean shown) {
514 // No-op for compatibility with the old DatePicker.
515 }
516
517 @Override
518 public boolean getSpinnersShown() {
519 return false;
520 }
521
522 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700523 public void onConfigurationChanged(Configuration newConfig) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700524 setCurrentLocale(newConfig.locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700525 }
526
527 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700528 public Parcelable onSaveInstanceState(Parcelable superState) {
529 final int year = mCurrentDate.get(Calendar.YEAR);
530 final int month = mCurrentDate.get(Calendar.MONTH);
531 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
532
533 int listPosition = -1;
534 int listPositionOffset = -1;
535
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700536 if (mCurrentView == VIEW_MONTH_DAY) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700537 listPosition = mDayPickerView.getMostVisiblePosition();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700538 } else if (mCurrentView == VIEW_YEAR) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700539 listPosition = mYearPickerView.getFirstVisiblePosition();
540 listPositionOffset = mYearPickerView.getFirstPositionOffset();
541 }
542
543 return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
544 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
545 }
546
547 @Override
548 public void onRestoreInstanceState(Parcelable state) {
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500549 if (state instanceof SavedState) {
550 final SavedState ss = (SavedState) state;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700551
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500552 // TODO: Move instance state into DayPickerView, YearPickerView.
553 mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay());
554 mMinDate.setTimeInMillis(ss.getMinDate());
555 mMaxDate.setTimeInMillis(ss.getMaxDate());
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700556
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500557 onCurrentDateChanged(false);
Alan Viverette816aa142015-04-10 15:41:10 -0700558
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500559 final int currentView = ss.getCurrentView();
560 setCurrentView(currentView);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700561
Alan Viverette6b3f85f2016-03-01 16:48:04 -0500562 final int listPosition = ss.getListPosition();
563 if (listPosition != -1) {
564 if (currentView == VIEW_MONTH_DAY) {
565 mDayPickerView.setPosition(listPosition);
566 } else if (currentView == VIEW_YEAR) {
567 final int listPositionOffset = ss.getListPositionOffset();
568 mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset);
569 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700570 }
571 }
572 }
573
574 @Override
575 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
576 onPopulateAccessibilityEvent(event);
577 return true;
578 }
579
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800580 public CharSequence getAccessibilityClassName() {
581 return DatePicker.class.getName();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700582 }
583
Neil Fuller9900c522019-04-17 08:48:23 +0100584 private static int getDaysInMonth(int month, int year) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700585 switch (month) {
586 case Calendar.JANUARY:
587 case Calendar.MARCH:
588 case Calendar.MAY:
589 case Calendar.JULY:
590 case Calendar.AUGUST:
591 case Calendar.OCTOBER:
592 case Calendar.DECEMBER:
593 return 31;
594 case Calendar.APRIL:
595 case Calendar.JUNE:
596 case Calendar.SEPTEMBER:
597 case Calendar.NOVEMBER:
598 return 30;
599 case Calendar.FEBRUARY:
Neil Fuller6c0984a2019-04-17 15:54:22 +0100600 return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 29 : 28;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700601 default:
602 throw new IllegalArgumentException("Invalid Month");
603 }
604 }
605
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700606 private void tryVibrate() {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700607 mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
608 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700609}