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