TimePickerClockDelegate.java revision 3053b2fdcf7486f2e2f572f9b05ce65dacdd2b4c
1/* 2 * Copyright (C) 2013 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.graphics.Color; 23import android.os.Parcel; 24import android.os.Parcelable; 25import android.text.format.DateFormat; 26import android.text.format.DateUtils; 27import android.util.AttributeSet; 28import android.view.LayoutInflater; 29import android.view.View; 30import android.view.ViewGroup; 31import android.view.accessibility.AccessibilityEvent; 32import android.view.accessibility.AccessibilityNodeInfo; 33import android.view.inputmethod.EditorInfo; 34import android.view.inputmethod.InputMethodManager; 35import com.android.internal.R; 36 37import java.text.DateFormatSymbols; 38import java.util.Calendar; 39import java.util.Locale; 40 41import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; 42import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; 43 44/** 45 * A delegate implementing the basic TimePicker 46 */ 47class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { 48 49 private static final boolean DEFAULT_ENABLED_STATE = true; 50 51 private static final int HOURS_IN_HALF_DAY = 12; 52 53 // state 54 private boolean mIs24HourView; 55 56 private boolean mIsAm; 57 58 // ui components 59 private final NumberPicker mHourSpinner; 60 61 private final NumberPicker mMinuteSpinner; 62 63 private final NumberPicker mAmPmSpinner; 64 65 private final EditText mHourSpinnerInput; 66 67 private final EditText mMinuteSpinnerInput; 68 69 private final EditText mAmPmSpinnerInput; 70 71 private final TextView mDivider; 72 73 // Note that the legacy implementation of the TimePicker is 74 // using a button for toggling between AM/PM while the new 75 // version uses a NumberPicker spinner. Therefore the code 76 // accommodates these two cases to be backwards compatible. 77 private final Button mAmPmButton; 78 79 // May be null if layout has no done button 80 private final View mDoneButton; 81 private boolean mShowDoneButton; 82 private TimePicker.TimePickerDismissCallback mDismissCallback; 83 84 private final String[] mAmPmStrings; 85 86 private boolean mIsEnabled = DEFAULT_ENABLED_STATE; 87 88 private Calendar mTempCalendar; 89 90 private boolean mHourWithTwoDigit; 91 private char mHourFormat; 92 93 /** 94 * A no-op callback used in the constructor to avoid null checks later in 95 * the code. 96 */ 97 private static final TimePicker.OnTimeChangedListener NO_OP_CHANGE_LISTENER = 98 new TimePicker.OnTimeChangedListener() { 99 public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { 100 } 101 }; 102 103 public TimePickerClockDelegate(TimePicker delegator, Context context, AttributeSet attrs, 104 int defStyleAttr, int defStyleRes) { 105 super(delegator, context); 106 107 // process style attributes 108 final TypedArray a = mContext.obtainStyledAttributes( 109 attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes); 110 final int layoutResourceId = a.getResourceId( 111 R.styleable.TimePicker_legacyLayout, R.layout.time_picker_legacy); 112 a.recycle(); 113 114 final LayoutInflater inflater = LayoutInflater.from(mContext); 115 inflater.inflate(layoutResourceId, mDelegator, true); 116 117 // hour 118 mHourSpinner = (NumberPicker) delegator.findViewById(R.id.hour); 119 mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { 120 public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { 121 updateInputState(); 122 if (!is24HourView()) { 123 if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) || 124 (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) { 125 mIsAm = !mIsAm; 126 updateAmPmControl(); 127 } 128 } 129 onTimeChanged(); 130 } 131 }); 132 mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input); 133 mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); 134 135 // divider (only for the new widget style) 136 mDivider = (TextView) mDelegator.findViewById(R.id.divider); 137 if (mDivider != null) { 138 setDividerText(); 139 } 140 141 // minute 142 mMinuteSpinner = (NumberPicker) mDelegator.findViewById(R.id.minute); 143 mMinuteSpinner.setMinValue(0); 144 mMinuteSpinner.setMaxValue(59); 145 mMinuteSpinner.setOnLongPressUpdateInterval(100); 146 mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); 147 mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { 148 public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { 149 updateInputState(); 150 int minValue = mMinuteSpinner.getMinValue(); 151 int maxValue = mMinuteSpinner.getMaxValue(); 152 if (oldVal == maxValue && newVal == minValue) { 153 int newHour = mHourSpinner.getValue() + 1; 154 if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) { 155 mIsAm = !mIsAm; 156 updateAmPmControl(); 157 } 158 mHourSpinner.setValue(newHour); 159 } else if (oldVal == minValue && newVal == maxValue) { 160 int newHour = mHourSpinner.getValue() - 1; 161 if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) { 162 mIsAm = !mIsAm; 163 updateAmPmControl(); 164 } 165 mHourSpinner.setValue(newHour); 166 } 167 onTimeChanged(); 168 } 169 }); 170 mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input); 171 mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); 172 173 /* Get the localized am/pm strings and use them in the spinner */ 174 mAmPmStrings = new DateFormatSymbols().getAmPmStrings(); 175 176 // am/pm 177 View amPmView = mDelegator.findViewById(R.id.amPm); 178 if (amPmView instanceof Button) { 179 mAmPmSpinner = null; 180 mAmPmSpinnerInput = null; 181 mAmPmButton = (Button) amPmView; 182 mAmPmButton.setOnClickListener(new View.OnClickListener() { 183 public void onClick(View button) { 184 button.requestFocus(); 185 mIsAm = !mIsAm; 186 updateAmPmControl(); 187 onTimeChanged(); 188 } 189 }); 190 } else { 191 mAmPmButton = null; 192 mAmPmSpinner = (NumberPicker) amPmView; 193 mAmPmSpinner.setMinValue(0); 194 mAmPmSpinner.setMaxValue(1); 195 mAmPmSpinner.setDisplayedValues(mAmPmStrings); 196 mAmPmSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { 197 public void onValueChange(NumberPicker picker, int oldVal, int newVal) { 198 updateInputState(); 199 picker.requestFocus(); 200 mIsAm = !mIsAm; 201 updateAmPmControl(); 202 onTimeChanged(); 203 } 204 }); 205 mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input); 206 mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); 207 } 208 209 if (isAmPmAtStart()) { 210 // Move the am/pm view to the beginning 211 ViewGroup amPmParent = (ViewGroup) delegator.findViewById(R.id.timePickerLayout); 212 amPmParent.removeView(amPmView); 213 amPmParent.addView(amPmView, 0); 214 // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme 215 // for example and not for Holo Theme) 216 ViewGroup.MarginLayoutParams lp = 217 (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams(); 218 final int startMargin = lp.getMarginStart(); 219 final int endMargin = lp.getMarginEnd(); 220 if (startMargin != endMargin) { 221 lp.setMarginStart(endMargin); 222 lp.setMarginEnd(startMargin); 223 } 224 } 225 226 mDoneButton = delegator.findViewById(R.id.done_button); 227 mShowDoneButton = (mDoneButton != null); 228 if (mShowDoneButton) { 229 mDoneButton.setOnClickListener(new View.OnClickListener() { 230 @Override 231 public void onClick(View v) { 232 if (mDismissCallback != null) { 233 mDismissCallback.dismiss(mDelegator, false, getCurrentHour(), 234 getCurrentMinute()); 235 } 236 } 237 }); 238 } 239 240 getHourFormatData(); 241 242 // update controls to initial state 243 updateHourControl(); 244 updateMinuteControl(); 245 updateAmPmControl(); 246 247 setOnTimeChangedListener(NO_OP_CHANGE_LISTENER); 248 249 // set to current time 250 setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY)); 251 setCurrentMinute(mTempCalendar.get(Calendar.MINUTE)); 252 253 if (!isEnabled()) { 254 setEnabled(false); 255 } 256 257 // set the content descriptions 258 setContentDescriptions(); 259 260 // If not explicitly specified this view is important for accessibility. 261 if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 262 mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 263 } 264 265 updateDoneButton(); 266 } 267 268 private void getHourFormatData() { 269 final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, 270 (mIs24HourView) ? "Hm" : "hm"); 271 final int lengthPattern = bestDateTimePattern.length(); 272 mHourWithTwoDigit = false; 273 char hourFormat = '\0'; 274 // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save 275 // the hour format that we found. 276 for (int i = 0; i < lengthPattern; i++) { 277 final char c = bestDateTimePattern.charAt(i); 278 if (c == 'H' || c == 'h' || c == 'K' || c == 'k') { 279 mHourFormat = c; 280 if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) { 281 mHourWithTwoDigit = true; 282 } 283 break; 284 } 285 } 286 } 287 288 private boolean isAmPmAtStart() { 289 final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, 290 "hm" /* skeleton */); 291 292 return bestDateTimePattern.startsWith("a"); 293 } 294 295 /** 296 * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". 297 * 298 * See http://unicode.org/cldr/trac/browser/trunk/common/main 299 * 300 * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the 301 * separator as the character which is just after the hour marker in the returned pattern. 302 */ 303 private void setDividerText() { 304 final String skeleton = (mIs24HourView) ? "Hm" : "hm"; 305 final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, 306 skeleton); 307 final String separatorText; 308 int hourIndex = bestDateTimePattern.lastIndexOf('H'); 309 if (hourIndex == -1) { 310 hourIndex = bestDateTimePattern.lastIndexOf('h'); 311 } 312 if (hourIndex == -1) { 313 // Default case 314 separatorText = ":"; 315 } else { 316 int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1); 317 if (minuteIndex == -1) { 318 separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1)); 319 } else { 320 separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex); 321 } 322 } 323 mDivider.setText(separatorText); 324 } 325 326 @Override 327 public void setCurrentHour(Integer currentHour) { 328 setCurrentHour(currentHour, true); 329 } 330 331 private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) { 332 // why was Integer used in the first place? 333 if (currentHour == null || currentHour == getCurrentHour()) { 334 return; 335 } 336 if (!is24HourView()) { 337 // convert [0,23] ordinal to wall clock display 338 if (currentHour >= HOURS_IN_HALF_DAY) { 339 mIsAm = false; 340 if (currentHour > HOURS_IN_HALF_DAY) { 341 currentHour = currentHour - HOURS_IN_HALF_DAY; 342 } 343 } else { 344 mIsAm = true; 345 if (currentHour == 0) { 346 currentHour = HOURS_IN_HALF_DAY; 347 } 348 } 349 updateAmPmControl(); 350 } 351 mHourSpinner.setValue(currentHour); 352 if (notifyTimeChanged) { 353 onTimeChanged(); 354 } 355 } 356 357 @Override 358 public Integer getCurrentHour() { 359 int currentHour = mHourSpinner.getValue(); 360 if (is24HourView()) { 361 return currentHour; 362 } else if (mIsAm) { 363 return currentHour % HOURS_IN_HALF_DAY; 364 } else { 365 return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; 366 } 367 } 368 369 @Override 370 public void setCurrentMinute(Integer currentMinute) { 371 if (currentMinute == getCurrentMinute()) { 372 return; 373 } 374 mMinuteSpinner.setValue(currentMinute); 375 onTimeChanged(); 376 } 377 378 @Override 379 public Integer getCurrentMinute() { 380 return mMinuteSpinner.getValue(); 381 } 382 383 @Override 384 public void setIs24HourView(Boolean is24HourView) { 385 if (mIs24HourView == is24HourView) { 386 return; 387 } 388 // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!! 389 int currentHour = getCurrentHour(); 390 // Order is important here. 391 mIs24HourView = is24HourView; 392 getHourFormatData(); 393 updateHourControl(); 394 // set value after spinner range is updated 395 setCurrentHour(currentHour, false); 396 updateMinuteControl(); 397 updateAmPmControl(); 398 } 399 400 @Override 401 public boolean is24HourView() { 402 return mIs24HourView; 403 } 404 405 @Override 406 public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener onTimeChangedListener) { 407 mOnTimeChangedListener = onTimeChangedListener; 408 } 409 410 @Override 411 public void setEnabled(boolean enabled) { 412 mMinuteSpinner.setEnabled(enabled); 413 if (mDivider != null) { 414 mDivider.setEnabled(enabled); 415 } 416 mHourSpinner.setEnabled(enabled); 417 if (mAmPmSpinner != null) { 418 mAmPmSpinner.setEnabled(enabled); 419 } else { 420 mAmPmButton.setEnabled(enabled); 421 } 422 mIsEnabled = enabled; 423 } 424 425 @Override 426 public boolean isEnabled() { 427 return mIsEnabled; 428 } 429 430 @Override 431 public void setShowDoneButton(boolean showDoneButton) { 432 if (mDoneButton != null) { 433 mShowDoneButton = showDoneButton; 434 updateDoneButton(); 435 } 436 } 437 438 @Override 439 public boolean isShowDoneButton() { 440 return mShowDoneButton; 441 } 442 443 private void updateDoneButton() { 444 if (mDoneButton != null) { 445 mDoneButton.setVisibility(mShowDoneButton ? View.VISIBLE : View.GONE); 446 } 447 } 448 449 @Override 450 public void setDismissCallback(TimePicker.TimePickerDismissCallback callback) { 451 mDismissCallback = callback; 452 } 453 454 @Override 455 public int getBaseline() { 456 return mHourSpinner.getBaseline(); 457 } 458 459 @Override 460 public void onConfigurationChanged(Configuration newConfig) { 461 setCurrentLocale(newConfig.locale); 462 } 463 464 @Override 465 public Parcelable onSaveInstanceState(Parcelable superState) { 466 return new SavedState(superState, getCurrentHour(), getCurrentMinute(), 467 isShowDoneButton()); 468 } 469 470 @Override 471 public void onRestoreInstanceState(Parcelable state) { 472 SavedState ss = (SavedState) state; 473 setCurrentHour(ss.getHour()); 474 setCurrentMinute(ss.getMinute()); 475 setShowDoneButton(ss.isShowDoneButton()); 476 } 477 478 @Override 479 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 480 onPopulateAccessibilityEvent(event); 481 return true; 482 } 483 484 @Override 485 public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 486 int flags = DateUtils.FORMAT_SHOW_TIME; 487 if (mIs24HourView) { 488 flags |= DateUtils.FORMAT_24HOUR; 489 } else { 490 flags |= DateUtils.FORMAT_12HOUR; 491 } 492 mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour()); 493 mTempCalendar.set(Calendar.MINUTE, getCurrentMinute()); 494 String selectedDateUtterance = DateUtils.formatDateTime(mContext, 495 mTempCalendar.getTimeInMillis(), flags); 496 event.getText().add(selectedDateUtterance); 497 } 498 499 @Override 500 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 501 event.setClassName(TimePicker.class.getName()); 502 } 503 504 @Override 505 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 506 info.setClassName(TimePicker.class.getName()); 507 } 508 509 private void updateInputState() { 510 // Make sure that if the user changes the value and the IME is active 511 // for one of the inputs if this widget, the IME is closed. If the user 512 // changed the value via the IME and there is a next input the IME will 513 // be shown, otherwise the user chose another means of changing the 514 // value and having the IME up makes no sense. 515 InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); 516 if (inputMethodManager != null) { 517 if (inputMethodManager.isActive(mHourSpinnerInput)) { 518 mHourSpinnerInput.clearFocus(); 519 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); 520 } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) { 521 mMinuteSpinnerInput.clearFocus(); 522 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); 523 } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) { 524 mAmPmSpinnerInput.clearFocus(); 525 inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0); 526 } 527 } 528 } 529 530 private void updateAmPmControl() { 531 if (is24HourView()) { 532 if (mAmPmSpinner != null) { 533 mAmPmSpinner.setVisibility(View.GONE); 534 } else { 535 mAmPmButton.setVisibility(View.GONE); 536 } 537 } else { 538 int index = mIsAm ? Calendar.AM : Calendar.PM; 539 if (mAmPmSpinner != null) { 540 mAmPmSpinner.setValue(index); 541 mAmPmSpinner.setVisibility(View.VISIBLE); 542 } else { 543 mAmPmButton.setText(mAmPmStrings[index]); 544 mAmPmButton.setVisibility(View.VISIBLE); 545 } 546 } 547 mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 548 } 549 550 /** 551 * Sets the current locale. 552 * 553 * @param locale The current locale. 554 */ 555 @Override 556 public void setCurrentLocale(Locale locale) { 557 super.setCurrentLocale(locale); 558 mTempCalendar = Calendar.getInstance(locale); 559 } 560 561 private void onTimeChanged() { 562 mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 563 if (mOnTimeChangedListener != null) { 564 mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), 565 getCurrentMinute()); 566 } 567 } 568 569 private void updateHourControl() { 570 if (is24HourView()) { 571 // 'k' means 1-24 hour 572 if (mHourFormat == 'k') { 573 mHourSpinner.setMinValue(1); 574 mHourSpinner.setMaxValue(24); 575 } else { 576 mHourSpinner.setMinValue(0); 577 mHourSpinner.setMaxValue(23); 578 } 579 } else { 580 // 'K' means 0-11 hour 581 if (mHourFormat == 'K') { 582 mHourSpinner.setMinValue(0); 583 mHourSpinner.setMaxValue(11); 584 } else { 585 mHourSpinner.setMinValue(1); 586 mHourSpinner.setMaxValue(12); 587 } 588 } 589 mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null); 590 } 591 592 private void updateMinuteControl() { 593 if (is24HourView()) { 594 mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); 595 } else { 596 mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); 597 } 598 } 599 600 private void setContentDescriptions() { 601 // Minute 602 trySetContentDescription(mMinuteSpinner, R.id.increment, 603 R.string.time_picker_increment_minute_button); 604 trySetContentDescription(mMinuteSpinner, R.id.decrement, 605 R.string.time_picker_decrement_minute_button); 606 // Hour 607 trySetContentDescription(mHourSpinner, R.id.increment, 608 R.string.time_picker_increment_hour_button); 609 trySetContentDescription(mHourSpinner, R.id.decrement, 610 R.string.time_picker_decrement_hour_button); 611 // AM/PM 612 if (mAmPmSpinner != null) { 613 trySetContentDescription(mAmPmSpinner, R.id.increment, 614 R.string.time_picker_increment_set_pm_button); 615 trySetContentDescription(mAmPmSpinner, R.id.decrement, 616 R.string.time_picker_decrement_set_am_button); 617 } 618 } 619 620 private void trySetContentDescription(View root, int viewId, int contDescResId) { 621 View target = root.findViewById(viewId); 622 if (target != null) { 623 target.setContentDescription(mContext.getString(contDescResId)); 624 } 625 } 626 627 /** 628 * Used to save / restore state of time picker 629 */ 630 private static class SavedState extends View.BaseSavedState { 631 632 private final int mHour; 633 634 private final int mMinute; 635 636 private final boolean mShowDoneButton; 637 638 private SavedState(Parcelable superState, int hour, int minute, boolean showDoneButton) { 639 super(superState); 640 mHour = hour; 641 mMinute = minute; 642 mShowDoneButton = showDoneButton; 643 } 644 645 private SavedState(Parcel in) { 646 super(in); 647 mHour = in.readInt(); 648 mMinute = in.readInt(); 649 mShowDoneButton = (in.readInt() == 1); 650 } 651 652 public int getHour() { 653 return mHour; 654 } 655 656 public int getMinute() { 657 return mMinute; 658 } 659 660 public boolean isShowDoneButton() { 661 return mShowDoneButton; 662 } 663 664 @Override 665 public void writeToParcel(Parcel dest, int flags) { 666 super.writeToParcel(dest, flags); 667 dest.writeInt(mHour); 668 dest.writeInt(mMinute); 669 dest.writeInt(mShowDoneButton ? 1 : 0); 670 } 671 672 @SuppressWarnings({"unused", "hiding"}) 673 public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { 674 public SavedState createFromParcel(Parcel in) { 675 return new SavedState(in); 676 } 677 678 public SavedState[] newArray(int size) { 679 return new SavedState[size]; 680 } 681 }; 682 } 683} 684 685