blob: cbb61093cd39d5cfdf4ec7fe4278256a23e83b24 [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;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.text.format.DateFormat;
28import android.text.format.DateUtils;
29import 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
40import com.android.internal.R;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -070041
42import java.text.SimpleDateFormat;
43import 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);
307 mYearFormat = new SimpleDateFormat("y", locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700308
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700309 // Update the header text.
310 onCurrentDateChanged(false);
311 }
312
313 private void onCurrentDateChanged(boolean announce) {
314 if (mHeaderYear == null) {
315 // Abort, we haven't initialized yet. This method will get called
316 // again later after everything has been set up.
317 return;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700318 }
319
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700320 final String year = mYearFormat.format(mCurrentDate.getTime());
321 mHeaderYear.setText(year);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700322
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700323 final String monthDay = mMonthDayFormat.format(mCurrentDate.getTime());
324 mHeaderMonthDay.setText(monthDay);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700325
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700326 // TODO: This should use live regions.
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700327 if (announce) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700328 final long millis = mCurrentDate.getTimeInMillis();
329 final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
330 final String fullDateText = DateUtils.formatDateTime(mContext, millis, flags);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700331 mAnimator.announceForAccessibility(fullDateText);
332 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700333 }
334
335 private void setCurrentView(final int viewIndex) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700336 switch (viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700337 case VIEW_MONTH_DAY:
338 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
339
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700340 if (mCurrentView != viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700341 mHeaderMonthDay.setActivated(true);
342 mHeaderYear.setActivated(false);
343 mAnimator.setDisplayedChild(VIEW_MONTH_DAY);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700344 mCurrentView = viewIndex;
345 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700346
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700347 mAnimator.announceForAccessibility(mSelectDay);
348 break;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700349 case VIEW_YEAR:
George Mounte998c3f2015-10-27 08:46:44 -0700350 final int year = mCurrentDate.get(Calendar.YEAR);
351 mYearPickerView.setYear(year);
352 mYearPickerView.post(new Runnable() {
353 @Override
354 public void run() {
355 mYearPickerView.requestFocus();
356 final View selected = mYearPickerView.getSelectedView();
357 if (selected != null) {
358 selected.requestFocus();
359 }
360 }
361 });
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700362
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700363 if (mCurrentView != viewIndex) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700364 mHeaderMonthDay.setActivated(false);
365 mHeaderYear.setActivated(true);
366 mAnimator.setDisplayedChild(VIEW_YEAR);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700367 mCurrentView = viewIndex;
368 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700369
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700370 mAnimator.announceForAccessibility(mSelectYear);
371 break;
372 }
373 }
374
375 @Override
376 public void init(int year, int monthOfYear, int dayOfMonth,
377 DatePicker.OnDateChangedListener callBack) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700378 mCurrentDate.set(Calendar.YEAR, year);
379 mCurrentDate.set(Calendar.MONTH, monthOfYear);
380 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800381
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800382 mDateChangedListener = callBack;
Alan Viverette0cecbc92014-12-12 14:32:43 -0800383
384 onDateChanged(false, false);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700385 }
386
387 @Override
388 public void updateDate(int year, int month, int dayOfMonth) {
389 mCurrentDate.set(Calendar.YEAR, year);
390 mCurrentDate.set(Calendar.MONTH, month);
391 mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800392
Alan Viverette0cecbc92014-12-12 14:32:43 -0800393 onDateChanged(false, true);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800394 }
395
Alan Viverette0cecbc92014-12-12 14:32:43 -0800396 private void onDateChanged(boolean fromUser, boolean callbackToClient) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700397 final int year = mCurrentDate.get(Calendar.YEAR);
398
Alan Viverette0cecbc92014-12-12 14:32:43 -0800399 if (callbackToClient && mDateChangedListener != null) {
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800400 final int monthOfYear = mCurrentDate.get(Calendar.MONTH);
401 final int dayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH);
402 mDateChangedListener.onDateChanged(mDelegator, year, monthOfYear, dayOfMonth);
Alan Viverette9468c6a2014-08-21 13:56:54 -0700403 }
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800404
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700405 mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
406 mYearPickerView.setYear(year);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800407
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700408 onCurrentDateChanged(fromUser);
Alan Viverettee6ec07f2014-12-10 11:24:27 -0800409
410 if (fromUser) {
411 tryVibrate();
412 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700413 }
414
415 @Override
416 public int getYear() {
417 return mCurrentDate.get(Calendar.YEAR);
418 }
419
420 @Override
421 public int getMonth() {
422 return mCurrentDate.get(Calendar.MONTH);
423 }
424
425 @Override
426 public int getDayOfMonth() {
427 return mCurrentDate.get(Calendar.DAY_OF_MONTH);
428 }
429
430 @Override
431 public void setMinDate(long minDate) {
432 mTempDate.setTimeInMillis(minDate);
433 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
434 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
435 return;
436 }
437 if (mCurrentDate.before(mTempDate)) {
438 mCurrentDate.setTimeInMillis(minDate);
Alan Viverette0cecbc92014-12-12 14:32:43 -0800439 onDateChanged(false, true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700440 }
441 mMinDate.setTimeInMillis(minDate);
Alan Viverette46127402014-11-13 10:50:37 -0800442 mDayPickerView.setMinDate(minDate);
Alan Viverette50eb0252014-10-24 14:34:26 -0700443 mYearPickerView.setRange(mMinDate, mMaxDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700444 }
445
446 @Override
447 public Calendar getMinDate() {
448 return mMinDate;
449 }
450
451 @Override
452 public void setMaxDate(long maxDate) {
453 mTempDate.setTimeInMillis(maxDate);
454 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
455 && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
456 return;
457 }
458 if (mCurrentDate.after(mTempDate)) {
459 mCurrentDate.setTimeInMillis(maxDate);
Alan Viverette0cecbc92014-12-12 14:32:43 -0800460 onDateChanged(false, true);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700461 }
462 mMaxDate.setTimeInMillis(maxDate);
Alan Viverette46127402014-11-13 10:50:37 -0800463 mDayPickerView.setMaxDate(maxDate);
Alan Viverette50eb0252014-10-24 14:34:26 -0700464 mYearPickerView.setRange(mMinDate, mMaxDate);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700465 }
466
467 @Override
468 public Calendar getMaxDate() {
469 return mMaxDate;
470 }
471
472 @Override
Alan Viverette0a04bb02014-09-03 20:48:02 -0700473 public void setFirstDayOfWeek(int firstDayOfWeek) {
474 mFirstDayOfWeek = firstDayOfWeek;
Alan Viverettee763c9b2014-11-06 15:22:31 -0800475
476 mDayPickerView.setFirstDayOfWeek(firstDayOfWeek);
Alan Viverette0a04bb02014-09-03 20:48:02 -0700477 }
478
479 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700480 public int getFirstDayOfWeek() {
Alan Viverette0a04bb02014-09-03 20:48:02 -0700481 if (mFirstDayOfWeek != USE_LOCALE) {
482 return mFirstDayOfWeek;
483 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700484 return mCurrentDate.getFirstDayOfWeek();
485 }
486
487 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700488 public void setEnabled(boolean enabled) {
Alan Viverettef7aa9252015-06-03 14:07:26 -0700489 mContainer.setEnabled(enabled);
490 mDayPickerView.setEnabled(enabled);
491 mYearPickerView.setEnabled(enabled);
492 mHeaderYear.setEnabled(enabled);
493 mHeaderMonthDay.setEnabled(enabled);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700494 }
495
496 @Override
497 public boolean isEnabled() {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700498 return mContainer.isEnabled();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700499 }
500
501 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700502 public CalendarView getCalendarView() {
Alan Viverettef7aa9252015-06-03 14:07:26 -0700503 throw new UnsupportedOperationException("Not supported by calendar-mode DatePicker");
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700504 }
505
506 @Override
507 public void setCalendarViewShown(boolean shown) {
508 // No-op for compatibility with the old DatePicker.
509 }
510
511 @Override
512 public boolean getCalendarViewShown() {
513 return false;
514 }
515
516 @Override
517 public void setSpinnersShown(boolean shown) {
518 // No-op for compatibility with the old DatePicker.
519 }
520
521 @Override
522 public boolean getSpinnersShown() {
523 return false;
524 }
525
526 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700527 public void onConfigurationChanged(Configuration newConfig) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700528 setCurrentLocale(newConfig.locale);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700529 }
530
531 @Override
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700532 public Parcelable onSaveInstanceState(Parcelable superState) {
533 final int year = mCurrentDate.get(Calendar.YEAR);
534 final int month = mCurrentDate.get(Calendar.MONTH);
535 final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
536
537 int listPosition = -1;
538 int listPositionOffset = -1;
539
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700540 if (mCurrentView == VIEW_MONTH_DAY) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700541 listPosition = mDayPickerView.getMostVisiblePosition();
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700542 } else if (mCurrentView == VIEW_YEAR) {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700543 listPosition = mYearPickerView.getFirstVisiblePosition();
544 listPositionOffset = mYearPickerView.getFirstPositionOffset();
545 }
546
547 return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
548 mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
549 }
550
551 @Override
552 public void onRestoreInstanceState(Parcelable state) {
Alan Viverette816aa142015-04-10 15:41:10 -0700553 final SavedState ss = (SavedState) state;
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700554
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700555 // TODO: Move instance state into DayPickerView, YearPickerView.
Craig Mautnera67d9092014-09-16 15:38:47 -0700556 mCurrentDate.set(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay());
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700557 mMinDate.setTimeInMillis(ss.getMinDate());
558 mMaxDate.setTimeInMillis(ss.getMaxDate());
559
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700560 onCurrentDateChanged(false);
Alan Viverette816aa142015-04-10 15:41:10 -0700561
562 final int currentView = ss.getCurrentView();
563 setCurrentView(currentView);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700564
565 final int listPosition = ss.getListPosition();
566 if (listPosition != -1) {
Alan Viverette816aa142015-04-10 15:41:10 -0700567 if (currentView == VIEW_MONTH_DAY) {
Alan Viverette78bf1d32015-04-17 10:39:22 -0700568 mDayPickerView.setPosition(listPosition);
Alan Viverette816aa142015-04-10 15:41:10 -0700569 } else if (currentView == VIEW_YEAR) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700570 final int listPositionOffset = ss.getListPositionOffset();
571 mYearPickerView.setSelectionFromTop(listPosition, listPositionOffset);
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700572 }
573 }
574 }
575
576 @Override
577 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
578 onPopulateAccessibilityEvent(event);
579 return true;
580 }
581
582 @Override
583 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
584 event.getText().add(mCurrentDate.getTime().toString());
585 }
586
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800587 public CharSequence getAccessibilityClassName() {
588 return DatePicker.class.getName();
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700589 }
590
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700591 public static int getDaysInMonth(int month, int year) {
592 switch (month) {
593 case Calendar.JANUARY:
594 case Calendar.MARCH:
595 case Calendar.MAY:
596 case Calendar.JULY:
597 case Calendar.AUGUST:
598 case Calendar.OCTOBER:
599 case Calendar.DECEMBER:
600 return 31;
601 case Calendar.APRIL:
602 case Calendar.JUNE:
603 case Calendar.SEPTEMBER:
604 case Calendar.NOVEMBER:
605 return 30;
606 case Calendar.FEBRUARY:
607 return (year % 4 == 0) ? 29 : 28;
608 default:
609 throw new IllegalArgumentException("Invalid Month");
610 }
611 }
612
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700613 private void tryVibrate() {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700614 mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
615 }
616
Alan Viverettee763c9b2014-11-06 15:22:31 -0800617 /**
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700618 * Class for managing state storing/restoring.
619 */
620 private static class SavedState extends View.BaseSavedState {
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700621 private final int mSelectedYear;
622 private final int mSelectedMonth;
623 private final int mSelectedDay;
624 private final long mMinDate;
625 private final long mMaxDate;
626 private final int mCurrentView;
627 private final int mListPosition;
628 private final int mListPositionOffset;
629
630 /**
631 * Constructor called from {@link DatePicker#onSaveInstanceState()}
632 */
633 private SavedState(Parcelable superState, int year, int month, int day,
634 long minDate, long maxDate, int currentView, int listPosition,
635 int listPositionOffset) {
636 super(superState);
637 mSelectedYear = year;
638 mSelectedMonth = month;
639 mSelectedDay = day;
640 mMinDate = minDate;
641 mMaxDate = maxDate;
642 mCurrentView = currentView;
643 mListPosition = listPosition;
644 mListPositionOffset = listPositionOffset;
645 }
646
647 /**
648 * Constructor called from {@link #CREATOR}
649 */
650 private SavedState(Parcel in) {
651 super(in);
652 mSelectedYear = in.readInt();
653 mSelectedMonth = in.readInt();
654 mSelectedDay = in.readInt();
655 mMinDate = in.readLong();
656 mMaxDate = in.readLong();
657 mCurrentView = in.readInt();
658 mListPosition = in.readInt();
659 mListPositionOffset = in.readInt();
660 }
661
662 @Override
663 public void writeToParcel(Parcel dest, int flags) {
664 super.writeToParcel(dest, flags);
665 dest.writeInt(mSelectedYear);
666 dest.writeInt(mSelectedMonth);
667 dest.writeInt(mSelectedDay);
668 dest.writeLong(mMinDate);
669 dest.writeLong(mMaxDate);
670 dest.writeInt(mCurrentView);
671 dest.writeInt(mListPosition);
672 dest.writeInt(mListPositionOffset);
673 }
674
675 public int getSelectedDay() {
676 return mSelectedDay;
677 }
678
679 public int getSelectedMonth() {
680 return mSelectedMonth;
681 }
682
683 public int getSelectedYear() {
684 return mSelectedYear;
685 }
686
687 public long getMinDate() {
688 return mMinDate;
689 }
690
691 public long getMaxDate() {
692 return mMaxDate;
693 }
694
695 public int getCurrentView() {
696 return mCurrentView;
697 }
698
699 public int getListPosition() {
700 return mListPosition;
701 }
702
703 public int getListPositionOffset() {
704 return mListPositionOffset;
705 }
706
707 @SuppressWarnings("all")
708 // suppress unused and hiding
709 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
710
711 public SavedState createFromParcel(Parcel in) {
712 return new SavedState(in);
713 }
714
715 public SavedState[] newArray(int size) {
716 return new SavedState[size];
717 }
718 };
719 }
Fabrice Di Megliobd9152f2013-10-01 11:21:31 -0700720}