Rahul Ravikumar | 0533600 | 2019-10-14 15:04:32 -0700 | [diff] [blame] | 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 | } |