1 /* 2 * Copyright (C) 2007 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 org.mozilla.gecko.widget; 18 19 import java.text.SimpleDateFormat; 20 import java.util.Arrays; 21 import java.util.Calendar; 22 import java.util.Locale; 23 24 import org.mozilla.gecko.AppConstants; 25 import org.mozilla.gecko.AppConstants.Versions; 26 import org.mozilla.gecko.R; 27 28 import android.content.Context; 29 import android.text.format.DateFormat; 30 import android.text.format.DateUtils; 31 import android.util.DisplayMetrics; 32 import android.util.Log; 33 import android.util.TypedValue; 34 import android.view.Display; 35 import android.view.LayoutInflater; 36 import android.view.WindowManager; 37 import android.view.accessibility.AccessibilityEvent; 38 import android.view.inputmethod.InputMethodManager; 39 import android.widget.CalendarView; 40 import android.widget.EditText; 41 import android.widget.FrameLayout; 42 import android.widget.LinearLayout; 43 import android.widget.NumberPicker; 44 45 public class DateTimePicker extends FrameLayout { 46 private static final boolean DEBUG = true; 47 private static final String LOGTAG = "GeckoDateTimePicker"; 48 private static final int DEFAULT_START_YEAR = 1; 49 private static final int DEFAULT_END_YEAR = 9999; 50 private static final char DATE_FORMAT_DAY = 'd'; 51 private static final char DATE_FORMAT_MONTH = 'M'; 52 private static final char DATE_FORMAT_YEAR = 'y'; 53 54 boolean mYearEnabled = true; 55 boolean mMonthEnabled = true; 56 boolean mWeekEnabled; 57 boolean mDayEnabled = true; 58 boolean mHourEnabled = true; 59 boolean mMinuteEnabled = true; 60 boolean mIs12HourMode; 61 private boolean mCalendarEnabled; 62 63 // Size of the screen in inches; 64 private final int mScreenWidth; 65 private final int mScreenHeight; 66 private final OnValueChangeListener mOnChangeListener; 67 private final LinearLayout mPickers; 68 private final LinearLayout mDateSpinners; 69 private final LinearLayout mTimeSpinners; 70 71 final NumberPicker mDaySpinner; 72 final NumberPicker mMonthSpinner; 73 final NumberPicker mWeekSpinner; 74 final NumberPicker mYearSpinner; 75 final NumberPicker mHourSpinner; 76 final NumberPicker mMinuteSpinner; 77 final NumberPicker mAMPMSpinner; 78 private final CalendarView mCalendar; 79 private final EditText mDaySpinnerInput; 80 private final EditText mMonthSpinnerInput; 81 private final EditText mWeekSpinnerInput; 82 private final EditText mYearSpinnerInput; 83 private final EditText mHourSpinnerInput; 84 private final EditText mMinuteSpinnerInput; 85 private final EditText mAMPMSpinnerInput; 86 private Locale mCurrentLocale; 87 private String[] mShortMonths; 88 private String[] mShortAMPMs; 89 private int mNumberOfMonths; 90 91 Calendar mTempDate; 92 Calendar mCurrentDate; 93 private Calendar mMinDate; 94 private Calendar mMaxDate; 95 private final PickersState mState; 96 97 public static enum PickersState { DATE, MONTH, WEEK, TIME, DATETIME }; 98 99 public class OnValueChangeListener implements NumberPicker.OnValueChangeListener { 100 @Override onValueChange(NumberPicker picker, int oldVal, int newVal)101 public void onValueChange(NumberPicker picker, int oldVal, int newVal) { 102 updateInputState(); 103 mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis()); 104 if (DEBUG) { 105 Log.d(LOGTAG, "SDK version > 10, using new behavior"); 106 } 107 108 // The native date picker widget on these SDKs increments 109 // the next field when one field reaches the maximum. 110 if (picker == mDaySpinner && mDayEnabled) { 111 int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH); 112 int old = mTempDate.get(Calendar.DAY_OF_MONTH); 113 setTempDate(Calendar.DAY_OF_MONTH, old, newVal, 1, maxDayOfMonth); 114 } else if (picker == mMonthSpinner && mMonthEnabled) { 115 int old = mTempDate.get(Calendar.MONTH); 116 setTempDate(Calendar.MONTH, old, newVal, Calendar.JANUARY, Calendar.DECEMBER); 117 } else if (picker == mWeekSpinner) { 118 int old = mTempDate.get(Calendar.WEEK_OF_YEAR); 119 int maxWeekOfYear = mTempDate.getActualMaximum(Calendar.WEEK_OF_YEAR); 120 setTempDate(Calendar.WEEK_OF_YEAR, old, newVal, 0, maxWeekOfYear); 121 } else if (picker == mYearSpinner && mYearEnabled) { 122 int month = mTempDate.get(Calendar.MONTH); 123 mTempDate.set(Calendar.YEAR, newVal); 124 // Changing the year shouldn't change the month. (in case of non-leap year a Feb 29) 125 // change the day instead; 126 if (month != mTempDate.get(Calendar.MONTH)) { 127 mTempDate.set(Calendar.MONTH, month); 128 mTempDate.set(Calendar.DAY_OF_MONTH, 129 mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 130 } 131 } else if (picker == mHourSpinner && mHourEnabled) { 132 if (mIs12HourMode) { 133 setTempDate(Calendar.HOUR, oldVal, newVal, 1, 12); 134 } else { 135 setTempDate(Calendar.HOUR_OF_DAY, oldVal, newVal, 0, 23); 136 } 137 } else if (picker == mMinuteSpinner && mMinuteEnabled) { 138 setTempDate(Calendar.MINUTE, oldVal, newVal, 0, 59); 139 } else if (picker == mAMPMSpinner && mHourEnabled) { 140 mTempDate.set(Calendar.AM_PM, newVal); 141 } else { 142 throw new IllegalArgumentException(); 143 } 144 setDate(mTempDate); 145 if (mDayEnabled) { 146 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 147 } 148 if (mWeekEnabled) { 149 mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR)); 150 } 151 updateCalendar(); 152 updateSpinners(); 153 notifyDateChanged(); 154 } 155 setTempDate(int field, int oldVal, int newVal, int min, int max)156 private void setTempDate(int field, int oldVal, int newVal, int min, int max) { 157 if (oldVal == max && newVal == min) { 158 mTempDate.add(field, 1); 159 } else if (oldVal == min && newVal == max) { 160 mTempDate.add(field, -1); 161 } else { 162 mTempDate.add(field, newVal - oldVal); 163 } 164 } 165 } 166 167 private static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = new NumberPicker.Formatter() { 168 final StringBuilder mBuilder = new StringBuilder(); 169 170 final java.util.Formatter mFmt = new java.util.Formatter(mBuilder, java.util.Locale.US); 171 172 final Object[] mArgs = new Object[1]; 173 174 @Override 175 public String format(int value) { 176 mArgs[0] = value; 177 mBuilder.delete(0, mBuilder.length()); 178 mFmt.format("%02d", mArgs); 179 return mFmt.toString(); 180 } 181 }; 182 displayPickers()183 private void displayPickers() { 184 setWeekShown(false); 185 set12HourShown(mIs12HourMode); 186 if (mState == PickersState.DATETIME) { 187 return; 188 } 189 190 setHourShown(false); 191 setMinuteShown(false); 192 if (mState == PickersState.WEEK) { 193 setDayShown(false); 194 setMonthShown(false); 195 setWeekShown(true); 196 } else if (mState == PickersState.MONTH) { 197 setDayShown(false); 198 } 199 } 200 DateTimePicker(Context context)201 public DateTimePicker(Context context) { 202 this(context, "", "", PickersState.DATE, null, null); 203 } 204 DateTimePicker(Context context, String dateFormat, String dateTimeValue, PickersState state, String minDateValue, String maxDateValue)205 public DateTimePicker(Context context, String dateFormat, String dateTimeValue, PickersState state, String minDateValue, String maxDateValue) { 206 super(context); 207 208 setCurrentLocale(Locale.getDefault()); 209 210 mState = state; 211 LayoutInflater inflater = LayoutInflater.from(context); 212 inflater.inflate(R.layout.datetime_picker, this, true); 213 214 mOnChangeListener = new OnValueChangeListener(); 215 216 mDateSpinners = (LinearLayout)findViewById(R.id.date_spinners); 217 mTimeSpinners = (LinearLayout)findViewById(R.id.time_spinners); 218 mPickers = (LinearLayout)findViewById(R.id.datetime_picker); 219 220 // We will display differently according to the screen size width. 221 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 222 Display display = wm.getDefaultDisplay(); 223 DisplayMetrics dm = new DisplayMetrics(); 224 display.getMetrics(dm); 225 mScreenWidth = display.getWidth() / dm.densityDpi; 226 mScreenHeight = display.getHeight() / dm.densityDpi; 227 228 if (DEBUG) { 229 Log.d(LOGTAG, "screen width: " + mScreenWidth + " screen height: " + mScreenHeight); 230 } 231 232 // Set the min / max attribute. 233 try { 234 if (minDateValue != null && !minDateValue.equals("")) { 235 mMinDate.setTime(new SimpleDateFormat(dateFormat).parse(minDateValue)); 236 } else { 237 mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1); 238 } 239 } catch (Exception ex) { 240 Log.e(LOGTAG, "Error parsing format sting: " + ex); 241 mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1); 242 } 243 244 try { 245 if (maxDateValue != null && !maxDateValue.equals("")) { 246 mMaxDate.setTime(new SimpleDateFormat(dateFormat).parse(maxDateValue)); 247 } else { 248 mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31); 249 } 250 } catch (Exception ex) { 251 Log.e(LOGTAG, "Error parsing format string: " + ex); 252 mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31); 253 } 254 255 // Find the initial date from the constructor arguments. 256 try { 257 if (!dateTimeValue.equals("")) { 258 mTempDate.setTime(new SimpleDateFormat(dateFormat).parse(dateTimeValue)); 259 } else { 260 mTempDate.setTimeInMillis(System.currentTimeMillis()); 261 } 262 } catch (Exception ex) { 263 Log.e(LOGTAG, "Error parsing format string: " + ex); 264 mTempDate.setTimeInMillis(System.currentTimeMillis()); 265 } 266 267 if (mMaxDate.before(mMinDate)) { 268 // If the input date range is illogical/garbage, we should not restrict the input range (i.e. allow the 269 // user to select any date). If we try to make any assumptions based on the illogical min/max date we could 270 // potentially prevent the user from selecting dates that are in the developers intended range, so it's best 271 // to allow anything. 272 mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1); 273 mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31); 274 } 275 276 // mTempDate will either be a site-supplied value, or today's date otherwise. CalendarView implementations can 277 // crash if they're supplied an invalid date (i.e. a date not in the specified range), hence we need to set 278 // a sensible default date here. 279 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { 280 mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); 281 } 282 283 // If we're displaying a date, the screen is wide enough 284 // (and if we're using an SDK where the calendar view exists) 285 // then display a calendar. 286 if (mState == PickersState.DATE || mState == PickersState.DATETIME) { 287 mCalendar = new CalendarView(context); 288 mCalendar.setVisibility(GONE); 289 290 // Modify the time of mMaxDate and mMinDate to the end of the date and the beginning of the date. (Bug 1339884) 291 mMaxDate.set(Calendar.HOUR, 23); 292 mMaxDate.set(Calendar.MINUTE, 59); 293 mMaxDate.set(Calendar.SECOND, 59); 294 mMaxDate.set(Calendar.MILLISECOND, 999); 295 mMinDate.set(Calendar.HOUR, 0); 296 mMinDate.set(Calendar.MINUTE, 0); 297 mMinDate.set(Calendar.SECOND, 0); 298 mMinDate.set(Calendar.MILLISECOND, 0); 299 300 mCalendar.setFocusable(true); 301 mCalendar.setFocusableInTouchMode(true); 302 mCalendar.setMaxDate(mMaxDate.getTimeInMillis()); 303 mCalendar.setMinDate(mMinDate.getTimeInMillis()); 304 mCalendar.setDate(mTempDate.getTimeInMillis(), false, false); 305 306 mCalendar.setOnDateChangeListener(new CalendarView.OnDateChangeListener() { 307 @Override 308 public void onSelectedDayChange( 309 CalendarView view, int year, int month, int monthDay) { 310 mTempDate.set(year, month, monthDay); 311 setDate(mTempDate); 312 notifyDateChanged(); 313 } 314 }); 315 316 final int height; 317 if (Versions.preLollipopMR1) { 318 // Older versions of CalendarView don't request any height, resulting in 319 // the whole dialog not appearing unless we manually request height. 320 height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());; 321 } else { 322 height = LayoutParams.WRAP_CONTENT; 323 } 324 325 mPickers.addView(mCalendar, LayoutParams.MATCH_PARENT, height); 326 327 } else { 328 // If the screen is more wide than high, we are displaying day and 329 // time spinners, and if there is no calendar displayed, we should 330 // display the fields in one row. 331 if (mScreenWidth > mScreenHeight && mState == PickersState.DATETIME) { 332 mPickers.setOrientation(LinearLayout.HORIZONTAL); 333 } 334 mCalendar = null; 335 } 336 337 // Initialize all spinners. 338 mDaySpinner = setupSpinner(R.id.day, 1, 339 mTempDate.get(Calendar.DAY_OF_MONTH)); 340 mDaySpinner.setFormatter(TWO_DIGIT_FORMATTER); 341 mDaySpinnerInput = (EditText) mDaySpinner.getChildAt(1); 342 343 mMonthSpinner = setupSpinner(R.id.month, 1, 344 mTempDate.get(Calendar.MONTH) + 1); // Month is 0-based 345 mMonthSpinner.setFormatter(TWO_DIGIT_FORMATTER); 346 mMonthSpinner.setDisplayedValues(mShortMonths); 347 mMonthSpinnerInput = (EditText) mMonthSpinner.getChildAt(1); 348 349 mWeekSpinner = setupSpinner(R.id.week, 1, 350 mTempDate.get(Calendar.WEEK_OF_YEAR)); 351 mWeekSpinner.setFormatter(TWO_DIGIT_FORMATTER); 352 mWeekSpinnerInput = (EditText) mWeekSpinner.getChildAt(1); 353 354 mYearSpinner = setupSpinner(R.id.year, DEFAULT_START_YEAR, 355 DEFAULT_END_YEAR); 356 mYearSpinnerInput = (EditText) mYearSpinner.getChildAt(1); 357 358 mAMPMSpinner = setupSpinner(R.id.ampm, 0, 1); 359 mAMPMSpinner.setFormatter(TWO_DIGIT_FORMATTER); 360 361 if (mIs12HourMode) { 362 mHourSpinner = setupSpinner(R.id.hour, 1, 12); 363 mAMPMSpinnerInput = (EditText) mAMPMSpinner.getChildAt(1); 364 mAMPMSpinner.setDisplayedValues(mShortAMPMs); 365 } else { 366 mHourSpinner = setupSpinner(R.id.hour, 0, 23); 367 mAMPMSpinnerInput = null; 368 } 369 370 mHourSpinner.setFormatter(TWO_DIGIT_FORMATTER); 371 mHourSpinnerInput = (EditText) mHourSpinner.getChildAt(1); 372 373 mMinuteSpinner = setupSpinner(R.id.minute, 0, 59); 374 mMinuteSpinner.setFormatter(TWO_DIGIT_FORMATTER); 375 mMinuteSpinnerInput = (EditText) mMinuteSpinner.getChildAt(1); 376 377 // The order in which the spinners are displayed are locale-dependent 378 reorderDateSpinners(); 379 380 // Set the date to the initial date. Since this date can come from the user, 381 // it can fire an exception (out-of-bound date) 382 try { 383 updateDate(mTempDate); 384 } catch (Exception ex) { 385 } 386 387 // Display only the pickers needed for the current state. 388 displayPickers(); 389 } 390 setupSpinner(int id, int min, int max)391 public NumberPicker setupSpinner(int id, int min, int max) { 392 NumberPicker mSpinner = (NumberPicker) findViewById(id); 393 mSpinner.setMinValue(min); 394 mSpinner.setMaxValue(max); 395 mSpinner.setOnValueChangedListener(mOnChangeListener); 396 mSpinner.setOnLongPressUpdateInterval(100); 397 return mSpinner; 398 } 399 getTimeInMillis()400 public long getTimeInMillis() { 401 return mCurrentDate.getTimeInMillis(); 402 } 403 reorderDateSpinners()404 private void reorderDateSpinners() { 405 mDateSpinners.removeAllViews(); 406 char[] order = DateFormat.getDateFormatOrder(getContext()); 407 final int spinnerCount = order.length; 408 409 for (int i = 0; i < spinnerCount; i++) { 410 switch (order[i]) { 411 case DATE_FORMAT_DAY: 412 mDateSpinners.addView(mDaySpinner); 413 break; 414 case DATE_FORMAT_MONTH: 415 mDateSpinners.addView(mMonthSpinner); 416 break; 417 case DATE_FORMAT_YEAR: 418 mDateSpinners.addView(mYearSpinner); 419 break; 420 default: 421 throw new IllegalArgumentException(); 422 } 423 } 424 425 mDateSpinners.addView(mWeekSpinner); 426 } 427 setDate(Calendar calendar)428 void setDate(Calendar calendar) { 429 mCurrentDate = mTempDate; 430 if (mCurrentDate.before(mMinDate)) { 431 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); 432 } else if (mCurrentDate.after(mMaxDate)) { 433 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); 434 } 435 } 436 updateInputState()437 void updateInputState() { 438 InputMethodManager inputMethodManager = (InputMethodManager) 439 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 440 if (mYearEnabled && inputMethodManager.isActive(mYearSpinnerInput)) { 441 mYearSpinnerInput.clearFocus(); 442 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 443 } else if (mMonthEnabled && inputMethodManager.isActive(mMonthSpinnerInput)) { 444 mMonthSpinnerInput.clearFocus(); 445 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 446 } else if (mDayEnabled && inputMethodManager.isActive(mDaySpinnerInput)) { 447 mDaySpinnerInput.clearFocus(); 448 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 449 } else if (mHourEnabled && inputMethodManager.isActive(mHourSpinnerInput)) { 450 mHourSpinnerInput.clearFocus(); 451 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 452 } else if (mMinuteEnabled && inputMethodManager.isActive(mMinuteSpinnerInput)) { 453 mMinuteSpinnerInput.clearFocus(); 454 inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 455 } 456 } 457 updateSpinners()458 void updateSpinners() { 459 if (mDayEnabled) { 460 if (mCurrentDate.equals(mMinDate)) { 461 mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 462 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 463 } else if (mCurrentDate.equals(mMaxDate)) { 464 mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH)); 465 mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 466 } else { 467 mDaySpinner.setMinValue(1); 468 mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); 469 } 470 mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); 471 } 472 473 if (mWeekEnabled) { 474 mWeekSpinner.setMinValue(1); 475 mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR)); 476 mWeekSpinner.setValue(mCurrentDate.get(Calendar.WEEK_OF_YEAR)); 477 } 478 479 if (mMonthEnabled) { 480 mMonthSpinner.setDisplayedValues(null); 481 if (mCurrentDate.equals(mMinDate)) { 482 mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH)); 483 mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH)); 484 } else if (mCurrentDate.equals(mMaxDate)) { 485 mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH)); 486 mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH)); 487 } else { 488 mMonthSpinner.setMinValue(Calendar.JANUARY); 489 mMonthSpinner.setMaxValue(Calendar.DECEMBER); 490 } 491 492 String[] displayedValues = Arrays.copyOfRange(mShortMonths, 493 mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1); 494 mMonthSpinner.setDisplayedValues(displayedValues); 495 mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH)); 496 } 497 498 if (mYearEnabled) { 499 mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR)); 500 mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR)); 501 mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR)); 502 } 503 504 if (mHourEnabled) { 505 if (mIs12HourMode) { 506 mHourSpinner.setValue(mCurrentDate.get(Calendar.HOUR)); 507 mAMPMSpinner.setValue(mCurrentDate.get(Calendar.AM_PM)); 508 mAMPMSpinner.setDisplayedValues(mShortAMPMs); 509 } else { 510 mHourSpinner.setValue(mCurrentDate.get(Calendar.HOUR_OF_DAY)); 511 } 512 } 513 if (mMinuteEnabled) { 514 mMinuteSpinner.setValue(mCurrentDate.get(Calendar.MINUTE)); 515 } 516 } 517 updateCalendar()518 void updateCalendar() { 519 if (mCalendarEnabled) { 520 mCalendar.setDate(mCurrentDate.getTimeInMillis(), false, false); 521 } 522 } 523 notifyDateChanged()524 void notifyDateChanged() { 525 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 526 } 527 toggleCalendar(boolean shown)528 public void toggleCalendar(boolean shown) { 529 if ((mState != PickersState.DATE && mState != PickersState.DATETIME)) { 530 return; 531 } 532 533 if (shown) { 534 mCalendarEnabled = true; 535 mCalendar.setVisibility(VISIBLE); 536 setYearShown(false); 537 setWeekShown(false); 538 setMonthShown(false); 539 setDayShown(false); 540 } else { 541 mCalendar.setVisibility(GONE); 542 setYearShown(true); 543 setMonthShown(true); 544 setDayShown(true); 545 mPickers.setOrientation(LinearLayout.HORIZONTAL); 546 mCalendarEnabled = false; 547 } 548 } 549 setYearShown(boolean shown)550 private void setYearShown(boolean shown) { 551 if (shown) { 552 toggleCalendar(false); 553 mYearSpinner.setVisibility(VISIBLE); 554 mYearEnabled = true; 555 } else { 556 mYearSpinner.setVisibility(GONE); 557 mYearEnabled = false; 558 } 559 } 560 setWeekShown(boolean shown)561 private void setWeekShown(boolean shown) { 562 if (shown) { 563 toggleCalendar(false); 564 mWeekSpinner.setVisibility(VISIBLE); 565 mWeekEnabled = true; 566 } else { 567 mWeekSpinner.setVisibility(GONE); 568 mWeekEnabled = false; 569 } 570 } 571 setMonthShown(boolean shown)572 private void setMonthShown(boolean shown) { 573 if (shown) { 574 toggleCalendar(false); 575 mMonthSpinner.setVisibility(VISIBLE); 576 mMonthEnabled = true; 577 } else { 578 mMonthSpinner.setVisibility(GONE); 579 mMonthEnabled = false; 580 } 581 } 582 setDayShown(boolean shown)583 private void setDayShown(boolean shown) { 584 if (shown) { 585 toggleCalendar(false); 586 mDaySpinner.setVisibility(VISIBLE); 587 mDayEnabled = true; 588 } else { 589 mDaySpinner.setVisibility(GONE); 590 mDayEnabled = false; 591 } 592 } 593 set12HourShown(boolean shown)594 private void set12HourShown(boolean shown) { 595 if (shown) { 596 mAMPMSpinner.setVisibility(VISIBLE); 597 } else { 598 mAMPMSpinner.setVisibility(GONE); 599 } 600 } 601 setHourShown(boolean shown)602 private void setHourShown(boolean shown) { 603 if (shown) { 604 mHourSpinner.setVisibility(VISIBLE); 605 mHourEnabled = true; 606 } else { 607 mHourSpinner.setVisibility(GONE); 608 mAMPMSpinner.setVisibility(GONE); 609 mTimeSpinners.setVisibility(GONE); 610 mHourEnabled = false; 611 } 612 } 613 setMinuteShown(boolean shown)614 private void setMinuteShown(boolean shown) { 615 if (shown) { 616 mMinuteSpinner.setVisibility(VISIBLE); 617 mTimeSpinners.findViewById(R.id.mincolon).setVisibility(VISIBLE); 618 mMinuteEnabled = true; 619 } else { 620 mMinuteSpinner.setVisibility(GONE); 621 mTimeSpinners.findViewById(R.id.mincolon).setVisibility(GONE); 622 mMinuteEnabled = false; 623 } 624 } 625 setCurrentLocale(Locale locale)626 private void setCurrentLocale(Locale locale) { 627 if (locale.equals(mCurrentLocale)) { 628 return; 629 } 630 631 mCurrentLocale = locale; 632 mIs12HourMode = !DateFormat.is24HourFormat(getContext()); 633 mTempDate = getCalendarForLocale(mTempDate, locale); 634 mMinDate = getCalendarForLocale(mMinDate, locale); 635 mMaxDate = getCalendarForLocale(mMaxDate, locale); 636 mCurrentDate = getCalendarForLocale(mCurrentDate, locale); 637 638 mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1; 639 640 mShortAMPMs = new String[2]; 641 mShortAMPMs[0] = DateUtils.getAMPMString(Calendar.AM); 642 mShortAMPMs[1] = DateUtils.getAMPMString(Calendar.PM); 643 644 mShortMonths = new String[mNumberOfMonths]; 645 for (int i = 0; i < mNumberOfMonths; i++) { 646 mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i, 647 DateUtils.LENGTH_MEDIUM); 648 } 649 } 650 getCalendarForLocale(Calendar oldCalendar, Locale locale)651 private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { 652 if (oldCalendar == null) { 653 return Calendar.getInstance(locale); 654 } 655 656 final long currentTimeMillis = oldCalendar.getTimeInMillis(); 657 Calendar newCalendar = Calendar.getInstance(locale); 658 newCalendar.setTimeInMillis(currentTimeMillis); 659 return newCalendar; 660 } 661 updateDate(Calendar calendar)662 public void updateDate(Calendar calendar) { 663 if (mCurrentDate.equals(calendar)) { 664 return; 665 } 666 mCurrentDate.setTimeInMillis(calendar.getTimeInMillis()); 667 if (mCurrentDate.before(mMinDate)) { 668 mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); 669 } else if (mCurrentDate.after(mMaxDate)) { 670 mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); 671 } 672 updateSpinners(); 673 notifyDateChanged(); 674 } 675 } 676