1 /* 2 * Copyright (C) 2016 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 17 package android.widget; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.content.res.TypedArray; 23 import android.icu.util.Calendar; 24 import android.os.Build; 25 import android.os.Parcelable; 26 import android.text.InputType; 27 import android.text.TextUtils; 28 import android.text.format.DateFormat; 29 import android.util.AttributeSet; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.accessibility.AccessibilityEvent; 33 import android.view.inputmethod.EditorInfo; 34 import android.view.inputmethod.InputMethodManager; 35 import android.widget.DatePicker.AbstractDatePickerDelegate; 36 import android.widget.NumberPicker.OnValueChangeListener; 37 38 import java.text.DateFormatSymbols; 39 import java.text.ParseException; 40 import java.text.SimpleDateFormat; 41 import java.util.Arrays; 42 import java.util.Locale; 43 44 /** 45 * A delegate implementing the basic DatePicker 46 */ 47 class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate { 48 49 private static final String DATE_FORMAT = "MM/dd/yyyy"; 50 51 private static final int DEFAULT_START_YEAR = 1900; 52 53 private static final int DEFAULT_END_YEAR = 2100; 54 55 private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true; 56 57 private static final boolean DEFAULT_SPINNERS_SHOWN = true; 58 59 private static final boolean DEFAULT_ENABLED_STATE = true; 60 61 private final LinearLayout mSpinners; 62 63 private final NumberPicker mDaySpinner; 64 65 private final NumberPicker mMonthSpinner; 66 67 private final NumberPicker mYearSpinner; 68 69 private final EditText mDaySpinnerInput; 70 71 private final EditText mMonthSpinnerInput; 72 73 private final EditText mYearSpinnerInput; 74 75 private final CalendarView mCalendarView; 76 77 private String[] mShortMonths; 78 79 private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); 80 81 private int mNumberOfMonths; 82 83 private Calendar mTempDate; 84 85 private Calendar mMinDate; 86 87 private Calendar mMaxDate; 88 89 private boolean mIsEnabled = DEFAULT_ENABLED_STATE; 90 DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)91 DatePickerSpinnerDelegate(DatePicker delegator, Context context, AttributeSet attrs, 92 int defStyleAttr, int defStyleRes) { 93 super(delegator, context); 94 95 mDelegator = delegator; 96 mContext = context; 97 98 // initialization based on locale 99 setCurrentLocale(Locale.getDefault()); 100 101 final TypedArray attributesArray = context.obtainStyledAttributes(attrs, 102 com.android.internal.R.styleable.DatePicker, defStyleAttr, defStyleRes); 103 boolean spinnersShown = attributesArray.getBoolean(com.android.internal.R.styleable.DatePicker_spinnersShown, 104 DEFAULT_SPINNERS_SHOWN); 105 boolean calendarViewShown = attributesArray.getBoolean( 106 com.android.internal.R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN); 107 int startYear = attributesArray.getInt(com.android.internal.R.styleable.DatePicker_startYear, 108 DEFAULT_START_YEAR); 109 int endYear = attributesArray.getInt(com.android.internal.R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); 110 String minDate = attributesArray.getString(com.android.internal.R.styleable.DatePicker_minDate); 111 String maxDate = attributesArray.getString(com.android.internal.R.styleable.DatePicker_maxDate); 112 int layoutResourceId = attributesArray.getResourceId( 113 com.android.internal.R.styleable.DatePicker_legacyLayout, com.android.internal.R.layout.date_picker_legacy); 114 attributesArray.recycle(); 115 116 LayoutInflater inflater = (LayoutInflater) context 117 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 118 final View view = inflater.inflate(layoutResourceId, mDelegator, true); 119 view.setSaveFromParentEnabled(false); 120 121 OnValueChangeListener onChangeListener = new OnValueChangeListener() { 122 public void onValueChange(NumberPicker picker, int oldVal, int newVal) { 123 updateInputState(); 124 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis()); 125 // take care of wrapping of days and months to update greater fields 126 if (picker == mDaySpinner) { 127 int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH); 128 if (oldVal == maxDayOfMonth && newVal == 1) { 129 mTempDate.add(Calendar.DAY_OF_MONTH, 1); 130 } else if (oldVal == 1 && newVal == maxDayOfMonth) { 131 mTempDate.add(Calendar.DAY_OF_MONTH, -1); 132 } else { 133 mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal); 134 } 135 } else if (picker == mMonthSpinner) { 136 if (oldVal == 11 && newVal == 0) { 137 mTempDate.add(Calendar.MONTH, 1); 138 } else if (oldVal == 0 && newVal == 11) { 139 mTempDate.add(Calendar.MONTH, -1); 140 } else { 141 mTempDate.add(Calendar.MONTH, newVal - oldVal); 142 } 143 } else if (picker == mYearSpinner) { 144 mTempDate.set(Calendar.YEAR, newVal); 145 } else { 146 throw new IllegalArgumentException(); 147 } 148 // now set the date to the adjusted one 149 setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH), 150 mTempDate.get(Calendar.DAY_OF_MONTH)); 151 updateSpinners(); 152 updateCalendarView(); 153 notifyDateChanged(); 154 } 155 }; 156 157 mSpinners = (LinearLayout) mDelegator.findViewById(com.android.internal.R.id.pickers); 158 159 // calendar view day-picker 160 mCalendarView = (CalendarView) mDelegator.findViewById(com.android.internal.R.id.calendar_view); 161 mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() { 162 public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) { 163 setDate(year, month, monthDay); 164 updateSpinners(); 165 notifyDateChanged(); 166 } 167 }); 168 169 // day 170 mDaySpinner = (NumberPicker) mDelegator.findViewById(com.android.internal.R.id.day); 171 mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); 172 mDaySpinner.setOnLongPressUpdateInterval(100); 173 mDaySpinner.setOnValueChangedListener(onChangeListener); 174 mDaySpinnerInput = (EditText) mDaySpinner.findViewById(com.android.internal.R.id.numberpicker_input); 175 176 // month 177 mMonthSpinner = (NumberPicker) mDelegator.findViewById(com.android.internal.R.id.month); 178 mMonthSpinner.setMinValue(0); 179 mMonthSpinner.setMaxValue(mNumberOfMonths - 1); 180 mMonthSpinner.setDisplayedValues(mShortMonths); 181 mMonthSpinner.setOnLongPressUpdateInterval(200); 182 mMonthSpinner.setOnValueChangedListener(onChangeListener); 183 mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(com.android.internal.R.id.numberpicker_input); 184 185 // year 186 mYearSpinner = (NumberPicker) mDelegator.findViewById(com.android.internal.R.id.year); 187 mYearSpinner.setOnLongPressUpdateInterval(100); 188 mYearSpinner.setOnValueChangedListener(onChangeListener); 189 mYearSpinnerInput = (EditText) mYearSpinner.findViewById(com.android.internal.R.id.numberpicker_input); 190 191 // show only what the user required but make sure we 192 // show something and the spinners have higher priority 193 if (!spinnersShown && !calendarViewShown) { 194 setSpinnersShown(true); 195 } else { 196 setSpinnersShown(spinnersShown); 197 setCalendarViewShown(calendarViewShown); 198 } 199 200 // set the min date giving priority of the minDate over startYear 201 mTempDate.clear(); 202 if (!TextUtils.isEmpty(minDate)) { 203 if (!parseDate(minDate, mTempDate)) { 204 mTempDate.set(startYear, 0, 1); 205 } 206 } else { 207 mTempDate.set(startYear, 0, 1); 208 } 209 setMinDate(mTempDate.getTimeInMillis()); 210 211 // set the max date giving priority of the maxDate over endYear 212 mTempDate.clear(); 213 if (!TextUtils.isEmpty(maxDate)) { 214 if (!parseDate(maxDate, mTempDate)) { 215 mTempDate.set(endYear, 11, 31); 216 } 217 } else { 218 mTempDate.set(endYear, 11, 31); 219 } 220 setMaxDate(mTempDate.getTimeInMillis()); 221 222 // initialize to current date 223 mCurrentDate.setTimeInMillis(System.currentTimeMillis()); 224 init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate 225 .get(Calendar.DAY_OF_MONTH), null); 226 227 // re-order the number spinners to match the current date format 228 reorderSpinners(); 229 230 // accessibility 231 setContentDescriptions(); 232 233 // If not explicitly specified this view is important for accessibility. 234 if (mDelegator.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 235 mDelegator.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 236 } 237 } 238 239 @Override init(int year, int monthOfYear, int dayOfMonth, DatePicker.OnDateChangedListener onDateChangedListener)240 public void init(int year, int monthOfYear, int dayOfMonth, 241 DatePicker.OnDateChangedListener onDateChangedListener) { 242 setDate(year, monthOfYear, dayOfMonth); 243 updateSpinners(); 244 updateCalendarView(); 245 246 mOnDateChangedListener = onDateChangedListener; 247 } 248 249 @Override updateDate(int year, int month, int dayOfMonth)250 public void updateDate(int year, int month, int dayOfMonth) { 251 if (!isNewDate(year, month, dayOfMonth)) { 252 return; 253 } 254 setDate(year, month, dayOfMonth); 255 updateSpinners(); 256 updateCalendarView(); 257 notifyDateChanged(); 258 } 259 260 @Override getYear()261 public int getYear() { 262 return mCurrentDate.get(Calendar.YEAR); 263 } 264 265 @Override getMonth()266 public int getMonth() { 267 return mCurrentDate.get(Calendar.MONTH); 268 } 269 270 @Override getDayOfMonth()271 public int getDayOfMonth() { 272 return mCurrentDate.get(Calendar.DAY_OF_MONTH); 273 } 274 275 @Override setFirstDayOfWeek(int firstDayOfWeek)276 public void setFirstDayOfWeek(int firstDayOfWeek) { 277 mCalendarView.setFirstDayOfWeek(firstDayOfWeek); 278 } 279 280 @Override getFirstDayOfWeek()281 public int getFirstDayOfWeek() { 282 return mCalendarView.getFirstDayOfWeek(); 283 } 284 285 @Override setMinDate(long minDate)286 public void setMinDate(long minDate) { 287 mTempDate.setTimeInMillis(minDate); 288 if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) 289 && mTempDate.get(Calendar.DAY_OF_YEAR) == mMinDate.get(Calendar.DAY_OF_YEAR)) { 290 // Same day, no-op. 291 return; 292 } 293 mMinDate.setTimeInMillis(minDate); 294 mCalendarView.setMinDate(minDate); 295 if (mCurrentDate.before(mMinDate)) { 296 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); 297 updateCalendarView(); 298 } 299 updateSpinners(); 300 } 301 302 @Override getMinDate()303 public Calendar getMinDate() { 304 final Calendar minDate = Calendar.getInstance(); 305 minDate.setTimeInMillis(mCalendarView.getMinDate()); 306 return minDate; 307 } 308 309 @Override setMaxDate(long maxDate)310 public void setMaxDate(long maxDate) { 311 mTempDate.setTimeInMillis(maxDate); 312 if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) 313 && mTempDate.get(Calendar.DAY_OF_YEAR) == mMaxDate.get(Calendar.DAY_OF_YEAR)) { 314 // Same day, no-op. 315 return; 316 } 317 mMaxDate.setTimeInMillis(maxDate); 318 mCalendarView.setMaxDate(maxDate); 319 if (mCurrentDate.after(mMaxDate)) { 320 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); 321 updateCalendarView(); 322 } 323 updateSpinners(); 324 } 325 326 @Override getMaxDate()327 public Calendar getMaxDate() { 328 final Calendar maxDate = Calendar.getInstance(); 329 maxDate.setTimeInMillis(mCalendarView.getMaxDate()); 330 return maxDate; 331 } 332 333 @Override setEnabled(boolean enabled)334 public void setEnabled(boolean enabled) { 335 mDaySpinner.setEnabled(enabled); 336 mMonthSpinner.setEnabled(enabled); 337 mYearSpinner.setEnabled(enabled); 338 mCalendarView.setEnabled(enabled); 339 mIsEnabled = enabled; 340 } 341 342 @Override isEnabled()343 public boolean isEnabled() { 344 return mIsEnabled; 345 } 346 347 @Override getCalendarView()348 public CalendarView getCalendarView() { 349 return mCalendarView; 350 } 351 352 @Override setCalendarViewShown(boolean shown)353 public void setCalendarViewShown(boolean shown) { 354 mCalendarView.setVisibility(shown ? View.VISIBLE : View.GONE); 355 } 356 357 @Override getCalendarViewShown()358 public boolean getCalendarViewShown() { 359 return (mCalendarView.getVisibility() == View.VISIBLE); 360 } 361 362 @Override setSpinnersShown(boolean shown)363 public void setSpinnersShown(boolean shown) { 364 mSpinners.setVisibility(shown ? View.VISIBLE : View.GONE); 365 } 366 367 @Override getSpinnersShown()368 public boolean getSpinnersShown() { 369 return mSpinners.isShown(); 370 } 371 372 @Override onConfigurationChanged(Configuration newConfig)373 public void onConfigurationChanged(Configuration newConfig) { 374 setCurrentLocale(newConfig.locale); 375 } 376 377 @Override onSaveInstanceState(Parcelable superState)378 public Parcelable onSaveInstanceState(Parcelable superState) { 379 return new SavedState(superState, getYear(), getMonth(), getDayOfMonth(), 380 getMinDate().getTimeInMillis(), getMaxDate().getTimeInMillis()); 381 } 382 383 @Override onRestoreInstanceState(Parcelable state)384 public void onRestoreInstanceState(Parcelable state) { 385 if (state instanceof SavedState) { 386 final SavedState ss = (SavedState) state; 387 setDate(ss.getSelectedYear(), ss.getSelectedMonth(), ss.getSelectedDay()); 388 updateSpinners(); 389 updateCalendarView(); 390 } 391 } 392 393 @Override dispatchPopulateAccessibilityEvent(AccessibilityEvent event)394 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 395 onPopulateAccessibilityEvent(event); 396 return true; 397 } 398 399 /** 400 * Sets the current locale. 401 * 402 * @param locale The current locale. 403 */ 404 @Override setCurrentLocale(Locale locale)405 protected void setCurrentLocale(Locale locale) { 406 super.setCurrentLocale(locale); 407 408 mTempDate = getCalendarForLocale(mTempDate, locale); 409 mMinDate = getCalendarForLocale(mMinDate, locale); 410 mMaxDate = getCalendarForLocale(mMaxDate, locale); 411 mCurrentDate = getCalendarForLocale(mCurrentDate, locale); 412 413 mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1; 414 mShortMonths = new DateFormatSymbols().getShortMonths(); 415 416 if (usingNumericMonths()) { 417 // We're in a locale where a date should either be all-numeric, or all-text. 418 // All-text would require custom NumberPicker formatters for day and year. 419 mShortMonths = new String[mNumberOfMonths]; 420 for (int i = 0; i < mNumberOfMonths; ++i) { 421 mShortMonths[i] = String.format("%d", i + 1); 422 } 423 } 424 } 425 426 /** 427 * Tests whether the current locale is one where there are no real month names, 428 * such as Chinese, Japanese, or Korean locales. 429 */ usingNumericMonths()430 private boolean usingNumericMonths() { 431 return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0)); 432 } 433 434 /** 435 * Gets a calendar for locale bootstrapped with the value of a given calendar. 436 * 437 * @param oldCalendar The old calendar. 438 * @param locale The locale. 439 */ getCalendarForLocale(Calendar oldCalendar, Locale locale)440 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { 441 if (oldCalendar == null) { 442 return Calendar.getInstance(locale); 443 } else { 444 final long currentTimeMillis = oldCalendar.getTimeInMillis(); 445 Calendar newCalendar = Calendar.getInstance(locale); 446 newCalendar.setTimeInMillis(currentTimeMillis); 447 return newCalendar; 448 } 449 } 450 451 /** 452 * Reorders the spinners according to the date format that is 453 * explicitly set by the user and if no such is set fall back 454 * to the current locale's default format. 455 */ reorderSpinners()456 private void reorderSpinners() { 457 mSpinners.removeAllViews(); 458 // We use numeric spinners for year and day, but textual months. Ask icu4c what 459 // order the user's locale uses for that combination. http://b/7207103. 460 String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd"); 461 char[] order = DateFormat.getDateFormatOrder(pattern); 462 final int spinnerCount = order.length; 463 for (int i = 0; i < spinnerCount; i++) { 464 switch (order[i]) { 465 case 'd': 466 mSpinners.addView(mDaySpinner); 467 setImeOptions(mDaySpinner, spinnerCount, i); 468 break; 469 case 'M': 470 mSpinners.addView(mMonthSpinner); 471 setImeOptions(mMonthSpinner, spinnerCount, i); 472 break; 473 case 'y': 474 mSpinners.addView(mYearSpinner); 475 setImeOptions(mYearSpinner, spinnerCount, i); 476 break; 477 default: 478 throw new IllegalArgumentException(Arrays.toString(order)); 479 } 480 } 481 } 482 483 /** 484 * Parses the given <code>date</code> and in case of success sets the result 485 * to the <code>outDate</code>. 486 * 487 * @return True if the date was parsed. 488 */ parseDate(String date, Calendar outDate)489 private boolean parseDate(String date, Calendar outDate) { 490 try { 491 outDate.setTime(mDateFormat.parse(date)); 492 return true; 493 } catch (ParseException e) { 494 e.printStackTrace(); 495 return false; 496 } 497 } 498 isNewDate(int year, int month, int dayOfMonth)499 private boolean isNewDate(int year, int month, int dayOfMonth) { 500 return (mCurrentDate.get(Calendar.YEAR) != year 501 || mCurrentDate.get(Calendar.MONTH) != month 502 || mCurrentDate.get(Calendar.DAY_OF_MONTH) != dayOfMonth); 503 } 504 505 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) setDate(int year, int month, int dayOfMonth)506 private void setDate(int year, int month, int dayOfMonth) { 507 mCurrentDate.set(year, month, dayOfMonth); 508 resetAutofilledValue(); 509 if (mCurrentDate.before(mMinDate)) { 510 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); 511 } else if (mCurrentDate.after(mMaxDate)) { 512 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); 513 } 514 } 515 516 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) updateSpinners()517 private void updateSpinners() { 518 // set the spinner ranges respecting the min and max dates 519 if (mCurrentDate.equals(mMinDate)) { 520 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 521 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 522 mDaySpinner.setWrapSelectorWheel(false); 523 mMonthSpinner.setDisplayedValues(null); 524 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH)); 525 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH)); 526 mMonthSpinner.setWrapSelectorWheel(false); 527 } else if (mCurrentDate.equals(mMaxDate)) { 528 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH)); 529 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 530 mDaySpinner.setWrapSelectorWheel(false); 531 mMonthSpinner.setDisplayedValues(null); 532 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH)); 533 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH)); 534 mMonthSpinner.setWrapSelectorWheel(false); 535 } else { 536 mDaySpinner.setMinValue(1); 537 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 538 mDaySpinner.setWrapSelectorWheel(true); 539 mMonthSpinner.setDisplayedValues(null); 540 mMonthSpinner.setMinValue(0); 541 mMonthSpinner.setMaxValue(11); 542 mMonthSpinner.setWrapSelectorWheel(true); 543 } 544 545 // make sure the month names are a zero based array 546 // with the months in the month spinner 547 String[] displayedValues = Arrays.copyOfRange(mShortMonths, 548 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1); 549 mMonthSpinner.setDisplayedValues(displayedValues); 550 551 // year spinner range does not change based on the current date 552 mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR)); 553 mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR)); 554 mYearSpinner.setWrapSelectorWheel(false); 555 556 // set the spinner values 557 mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR)); 558 mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH)); 559 mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 560 561 if (usingNumericMonths()) { 562 mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER); 563 } 564 } 565 566 /** 567 * Updates the calendar view with the current date. 568 */ 569 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) updateCalendarView()570 private void updateCalendarView() { 571 mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false); 572 } 573 574 575 /** 576 * Notifies the listener, if such, for a change in the selected date. 577 */ 578 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) notifyDateChanged()579 private void notifyDateChanged() { 580 mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 581 if (mOnDateChangedListener != null) { 582 mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(), 583 getDayOfMonth()); 584 } 585 if (mAutoFillChangeListener != null) { 586 mAutoFillChangeListener.onDateChanged(mDelegator, getYear(), getMonth(), 587 getDayOfMonth()); 588 } 589 } 590 591 /** 592 * Sets the IME options for a spinner based on its ordering. 593 * 594 * @param spinner The spinner. 595 * @param spinnerCount The total spinner count. 596 * @param spinnerIndex The index of the given spinner. 597 */ setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex)598 private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) { 599 final int imeOptions; 600 if (spinnerIndex < spinnerCount - 1) { 601 imeOptions = EditorInfo.IME_ACTION_NEXT; 602 } else { 603 imeOptions = EditorInfo.IME_ACTION_DONE; 604 } 605 TextView input = (TextView) spinner.findViewById(com.android.internal.R.id.numberpicker_input); 606 input.setImeOptions(imeOptions); 607 } 608 setContentDescriptions()609 private void setContentDescriptions() { 610 // Day 611 trySetContentDescription(mDaySpinner, com.android.internal.R.id.increment, 612 com.android.internal.R.string.date_picker_increment_day_button); 613 trySetContentDescription(mDaySpinner, com.android.internal.R.id.decrement, 614 com.android.internal.R.string.date_picker_decrement_day_button); 615 // Month 616 trySetContentDescription(mMonthSpinner, com.android.internal.R.id.increment, 617 com.android.internal.R.string.date_picker_increment_month_button); 618 trySetContentDescription(mMonthSpinner, com.android.internal.R.id.decrement, 619 com.android.internal.R.string.date_picker_decrement_month_button); 620 // Year 621 trySetContentDescription(mYearSpinner, com.android.internal.R.id.increment, 622 com.android.internal.R.string.date_picker_increment_year_button); 623 trySetContentDescription(mYearSpinner, com.android.internal.R.id.decrement, 624 com.android.internal.R.string.date_picker_decrement_year_button); 625 } 626 trySetContentDescription(View root, int viewId, int contDescResId)627 private void trySetContentDescription(View root, int viewId, int contDescResId) { 628 View target = root.findViewById(viewId); 629 if (target != null) { 630 target.setContentDescription(mContext.getString(contDescResId)); 631 } 632 } 633 634 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) updateInputState()635 private void updateInputState() { 636 // Make sure that if the user changes the value and the IME is active 637 // for one of the inputs if this widget, the IME is closed. If the user 638 // changed the value via the IME and there is a next input the IME will 639 // be shown, otherwise the user chose another means of changing the 640 // value and having the IME up makes no sense. 641 InputMethodManager inputMethodManager = mContext.getSystemService(InputMethodManager.class); 642 if (inputMethodManager != null) { 643 if (inputMethodManager.isActive(mYearSpinnerInput)) { 644 mYearSpinnerInput.clearFocus(); 645 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); 646 } else if (inputMethodManager.isActive(mMonthSpinnerInput)) { 647 mMonthSpinnerInput.clearFocus(); 648 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); 649 } else if (inputMethodManager.isActive(mDaySpinnerInput)) { 650 mDaySpinnerInput.clearFocus(); 651 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); 652 } 653 } 654 } 655 } 656