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