Workspace.java revision 59d5245b0fb8014fc16d4791fb3a031cc29799aa
1/* 2 * Copyright (C) 2008 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 com.android.launcher2; 18 19import android.app.WallpaperManager; 20import android.appwidget.AppWidgetManager; 21import android.appwidget.AppWidgetProviderInfo; 22import android.content.Context; 23import android.content.Intent; 24import android.content.ComponentName; 25import android.content.pm.ProviderInfo; 26import android.content.res.TypedArray; 27import android.content.pm.PackageManager; 28import android.graphics.Canvas; 29import android.graphics.Rect; 30import android.graphics.drawable.Drawable; 31import android.net.Uri; 32import android.os.Parcelable; 33import android.os.Parcel; 34import android.util.AttributeSet; 35import android.view.MotionEvent; 36import android.view.VelocityTracker; 37import android.view.View; 38import android.view.ViewConfiguration; 39import android.view.ViewGroup; 40import android.view.ViewParent; 41import android.widget.Scroller; 42import android.widget.TextView; 43 44import java.util.ArrayList; 45 46/** 47 * The workspace is a wide area with a wallpaper and a finite number of screens. Each 48 * screen contains a number of icons, folders or widgets the user can interact with. 49 * A workspace is meant to be used with a fixed width only. 50 */ 51public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller { 52 @SuppressWarnings({"UnusedDeclaration"}) 53 private static final String TAG = "Launcher.Workspace"; 54 private static final int INVALID_SCREEN = -1; 55 56 /** 57 * The velocity at which a fling gesture will cause us to snap to the next screen 58 */ 59 private static final int SNAP_VELOCITY = 1000; 60 61 private final WallpaperManager mWallpaperManager; 62 63 private int mDefaultScreen; 64 65 private boolean mFirstLayout = true; 66 67 private int mCurrentScreen; 68 private int mNextScreen = INVALID_SCREEN; 69 private Scroller mScroller; 70 private VelocityTracker mVelocityTracker; 71 72 /** 73 * CellInfo for the cell that is currently being dragged 74 */ 75 private CellLayout.CellInfo mDragInfo; 76 77 /** 78 * Target drop area calculated during last acceptDrop call. 79 */ 80 private int[] mTargetCell = null; 81 82 private float mLastMotionX; 83 private float mLastMotionY; 84 85 private final static int TOUCH_STATE_REST = 0; 86 private final static int TOUCH_STATE_SCROLLING = 1; 87 88 private int mTouchState = TOUCH_STATE_REST; 89 90 private OnLongClickListener mLongClickListener; 91 92 private Launcher mLauncher; 93 private IconCache mIconCache; 94 private DragController mDragController; 95 96 /** 97 * Cache of vacant cells, used during drag events and invalidated as needed. 98 */ 99 private CellLayout.CellInfo mVacantCache = null; 100 101 private int[] mTempCell = new int[2]; 102 private int[] mTempEstimate = new int[2]; 103 104 private boolean mAllowLongPress = true; 105 106 private int mTouchSlop; 107 private int mMaximumVelocity; 108 109 private Drawable mPreviousIndicator; 110 private Drawable mNextIndicator; 111 112 /** 113 * Used to inflate the Workspace from XML. 114 * 115 * @param context The application's context. 116 * @param attrs The attribtues set containing the Workspace's customization values. 117 */ 118 public Workspace(Context context, AttributeSet attrs) { 119 this(context, attrs, 0); 120 } 121 122 /** 123 * Used to inflate the Workspace from XML. 124 * 125 * @param context The application's context. 126 * @param attrs The attribtues set containing the Workspace's customization values. 127 * @param defStyle Unused. 128 */ 129 public Workspace(Context context, AttributeSet attrs, int defStyle) { 130 super(context, attrs, defStyle); 131 132 mWallpaperManager = WallpaperManager.getInstance(context); 133 134 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0); 135 mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1); 136 a.recycle(); 137 138 setHapticFeedbackEnabled(false); 139 initWorkspace(); 140 } 141 142 /** 143 * Initializes various states for this workspace. 144 */ 145 private void initWorkspace() { 146 Context context = getContext(); 147 mScroller = new Scroller(context); 148 mCurrentScreen = mDefaultScreen; 149 Launcher.setScreen(mCurrentScreen); 150 LauncherApplication app = (LauncherApplication)context.getApplicationContext(); 151 mIconCache = app.getIconCache(); 152 153 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 154 mTouchSlop = configuration.getScaledTouchSlop(); 155 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 156 } 157 158 @Override 159 public void addView(View child, int index, LayoutParams params) { 160 if (!(child instanceof CellLayout)) { 161 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 162 } 163 super.addView(child, index, params); 164 } 165 166 @Override 167 public void addView(View child) { 168 if (!(child instanceof CellLayout)) { 169 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 170 } 171 super.addView(child); 172 } 173 174 @Override 175 public void addView(View child, int index) { 176 if (!(child instanceof CellLayout)) { 177 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 178 } 179 super.addView(child, index); 180 } 181 182 @Override 183 public void addView(View child, int width, int height) { 184 if (!(child instanceof CellLayout)) { 185 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 186 } 187 super.addView(child, width, height); 188 } 189 190 @Override 191 public void addView(View child, LayoutParams params) { 192 if (!(child instanceof CellLayout)) { 193 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 194 } 195 super.addView(child, params); 196 } 197 198 /** 199 * @return The open folder on the current screen, or null if there is none 200 */ 201 Folder getOpenFolder() { 202 CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen); 203 int count = currentScreen.getChildCount(); 204 for (int i = 0; i < count; i++) { 205 View child = currentScreen.getChildAt(i); 206 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 207 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { 208 return (Folder) child; 209 } 210 } 211 return null; 212 } 213 214 ArrayList<Folder> getOpenFolders() { 215 final int screens = getChildCount(); 216 ArrayList<Folder> folders = new ArrayList<Folder>(screens); 217 218 for (int screen = 0; screen < screens; screen++) { 219 CellLayout currentScreen = (CellLayout) getChildAt(screen); 220 int count = currentScreen.getChildCount(); 221 for (int i = 0; i < count; i++) { 222 View child = currentScreen.getChildAt(i); 223 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 224 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { 225 folders.add((Folder) child); 226 break; 227 } 228 } 229 } 230 231 return folders; 232 } 233 234 boolean isDefaultScreenShowing() { 235 return mCurrentScreen == mDefaultScreen; 236 } 237 238 /** 239 * Returns the index of the currently displayed screen. 240 * 241 * @return The index of the currently displayed screen. 242 */ 243 int getCurrentScreen() { 244 return mCurrentScreen; 245 } 246 247 /** 248 * Sets the current screen. 249 * 250 * @param currentScreen 251 */ 252 void setCurrentScreen(int currentScreen) { 253 if (!mScroller.isFinished()) mScroller.abortAnimation(); 254 clearVacantCache(); 255 mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1)); 256 mPreviousIndicator.setLevel(mCurrentScreen); 257 mNextIndicator.setLevel(mCurrentScreen); 258 scrollTo(mCurrentScreen * getWidth(), 0); 259 invalidate(); 260 } 261 262 /** 263 * Adds the specified child in the current screen. The position and dimension of 264 * the child are defined by x, y, spanX and spanY. 265 * 266 * @param child The child to add in one of the workspace's screens. 267 * @param x The X position of the child in the screen's grid. 268 * @param y The Y position of the child in the screen's grid. 269 * @param spanX The number of cells spanned horizontally by the child. 270 * @param spanY The number of cells spanned vertically by the child. 271 */ 272 void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) { 273 addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false); 274 } 275 276 /** 277 * Adds the specified child in the current screen. The position and dimension of 278 * the child are defined by x, y, spanX and spanY. 279 * 280 * @param child The child to add in one of the workspace's screens. 281 * @param x The X position of the child in the screen's grid. 282 * @param y The Y position of the child in the screen's grid. 283 * @param spanX The number of cells spanned horizontally by the child. 284 * @param spanY The number of cells spanned vertically by the child. 285 * @param insert When true, the child is inserted at the beginning of the children list. 286 */ 287 void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) { 288 addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert); 289 } 290 291 /** 292 * Adds the specified child in the specified screen. The position and dimension of 293 * the child are defined by x, y, spanX and spanY. 294 * 295 * @param child The child to add in one of the workspace's screens. 296 * @param screen The screen in which to add the child. 297 * @param x The X position of the child in the screen's grid. 298 * @param y The Y position of the child in the screen's grid. 299 * @param spanX The number of cells spanned horizontally by the child. 300 * @param spanY The number of cells spanned vertically by the child. 301 */ 302 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) { 303 addInScreen(child, screen, x, y, spanX, spanY, false); 304 } 305 306 /** 307 * Adds the specified child in the specified screen. The position and dimension of 308 * the child are defined by x, y, spanX and spanY. 309 * 310 * @param child The child to add in one of the workspace's screens. 311 * @param screen The screen in which to add the child. 312 * @param x The X position of the child in the screen's grid. 313 * @param y The Y position of the child in the screen's grid. 314 * @param spanX The number of cells spanned horizontally by the child. 315 * @param spanY The number of cells spanned vertically by the child. 316 * @param insert When true, the child is inserted at the beginning of the children list. 317 */ 318 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) { 319 if (screen < 0 || screen >= getChildCount()) { 320 throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount()); 321 } 322 323 clearVacantCache(); 324 325 final CellLayout group = (CellLayout) getChildAt(screen); 326 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 327 if (lp == null) { 328 lp = new CellLayout.LayoutParams(x, y, spanX, spanY); 329 } else { 330 lp.cellX = x; 331 lp.cellY = y; 332 lp.cellHSpan = spanX; 333 lp.cellVSpan = spanY; 334 } 335 group.addView(child, insert ? 0 : -1, lp); 336 if (!(child instanceof Folder)) { 337 child.setHapticFeedbackEnabled(false); 338 child.setOnLongClickListener(mLongClickListener); 339 } 340 if (child instanceof DropTarget) { 341 mDragController.addDropTarget((DropTarget)child); 342 } 343 } 344 345 CellLayout.CellInfo findAllVacantCells(boolean[] occupied) { 346 CellLayout group = (CellLayout) getChildAt(mCurrentScreen); 347 if (group != null) { 348 return group.findAllVacantCells(occupied, null); 349 } 350 return null; 351 } 352 353 private void clearVacantCache() { 354 if (mVacantCache != null) { 355 mVacantCache.clearVacantCells(); 356 mVacantCache = null; 357 } 358 } 359 360 /** 361 * Registers the specified listener on each screen contained in this workspace. 362 * 363 * @param l The listener used to respond to long clicks. 364 */ 365 @Override 366 public void setOnLongClickListener(OnLongClickListener l) { 367 mLongClickListener = l; 368 final int count = getChildCount(); 369 for (int i = 0; i < count; i++) { 370 getChildAt(i).setOnLongClickListener(l); 371 } 372 } 373 374 private void updateWallpaperOffset() { 375 updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft)); 376 } 377 378 private void updateWallpaperOffset(int scrollRange) { 379 mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 ); 380 mWallpaperManager.setWallpaperOffsets(getWindowToken(), mScrollX / (float) scrollRange, 0); 381 } 382 383 @Override 384 public void computeScroll() { 385 if (mScroller.computeScrollOffset()) { 386 mScrollX = mScroller.getCurrX(); 387 mScrollY = mScroller.getCurrY(); 388 updateWallpaperOffset(); 389 postInvalidate(); 390 } else if (mNextScreen != INVALID_SCREEN) { 391 mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1)); 392 mPreviousIndicator.setLevel(mCurrentScreen); 393 mNextIndicator.setLevel(mCurrentScreen); 394 Launcher.setScreen(mCurrentScreen); 395 mNextScreen = INVALID_SCREEN; 396 clearChildrenCache(); 397 } 398 } 399 400 @Override 401 protected void dispatchDraw(Canvas canvas) { 402 boolean restore = false; 403 int restoreCount = 0; 404 405 // ViewGroup.dispatchDraw() supports many features we don't need: 406 // clip to padding, layout animation, animation listener, disappearing 407 // children, etc. The following implementation attempts to fast-track 408 // the drawing dispatch by drawing only what we know needs to be drawn. 409 410 boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN; 411 // If we are not scrolling or flinging, draw only the current screen 412 if (fastDraw) { 413 drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime()); 414 } else { 415 final long drawingTime = getDrawingTime(); 416 // If we are flinging, draw only the current screen and the target screen 417 if (mNextScreen >= 0 && mNextScreen < getChildCount() && 418 Math.abs(mCurrentScreen - mNextScreen) == 1) { 419 drawChild(canvas, getChildAt(mCurrentScreen), drawingTime); 420 drawChild(canvas, getChildAt(mNextScreen), drawingTime); 421 } else { 422 // If we are scrolling, draw all of our children 423 final int count = getChildCount(); 424 for (int i = 0; i < count; i++) { 425 drawChild(canvas, getChildAt(i), drawingTime); 426 } 427 } 428 } 429 430 if (restore) { 431 canvas.restoreToCount(restoreCount); 432 } 433 } 434 435 protected void onAttachedToWindow() { 436 super.onAttachedToWindow(); 437 mDragController.setWindowToken(getWindowToken()); 438 } 439 440 @Override 441 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 442 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 443 444 final int width = MeasureSpec.getSize(widthMeasureSpec); 445 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 446 if (widthMode != MeasureSpec.EXACTLY) { 447 throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); 448 } 449 450 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 451 if (heightMode != MeasureSpec.EXACTLY) { 452 throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); 453 } 454 455 // The children are given the same width and height as the workspace 456 final int count = getChildCount(); 457 for (int i = 0; i < count; i++) { 458 getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); 459 } 460 461 462 if (mFirstLayout) { 463 setHorizontalScrollBarEnabled(false); 464 scrollTo(mCurrentScreen * width, 0); 465 setHorizontalScrollBarEnabled(true); 466 updateWallpaperOffset(width * (getChildCount() - 1)); 467 mFirstLayout = false; 468 } 469 } 470 471 @Override 472 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 473 int childLeft = 0; 474 475 final int count = getChildCount(); 476 for (int i = 0; i < count; i++) { 477 final View child = getChildAt(i); 478 if (child.getVisibility() != View.GONE) { 479 final int childWidth = child.getMeasuredWidth(); 480 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); 481 childLeft += childWidth; 482 } 483 } 484 } 485 486 @Override 487 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { 488 int screen = indexOfChild(child); 489 if (screen != mCurrentScreen || !mScroller.isFinished()) { 490 if (!mLauncher.isWorkspaceLocked()) { 491 snapToScreen(screen); 492 } 493 return true; 494 } 495 return false; 496 } 497 498 @Override 499 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 500 if (!mLauncher.isAllAppsVisible()) { 501 final Folder openFolder = getOpenFolder(); 502 if (openFolder != null) { 503 return openFolder.requestFocus(direction, previouslyFocusedRect); 504 } else { 505 int focusableScreen; 506 if (mNextScreen != INVALID_SCREEN) { 507 focusableScreen = mNextScreen; 508 } else { 509 focusableScreen = mCurrentScreen; 510 } 511 getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect); 512 } 513 } 514 return false; 515 } 516 517 @Override 518 public boolean dispatchUnhandledMove(View focused, int direction) { 519 if (direction == View.FOCUS_LEFT) { 520 if (getCurrentScreen() > 0) { 521 snapToScreen(getCurrentScreen() - 1); 522 return true; 523 } 524 } else if (direction == View.FOCUS_RIGHT) { 525 if (getCurrentScreen() < getChildCount() - 1) { 526 snapToScreen(getCurrentScreen() + 1); 527 return true; 528 } 529 } 530 return super.dispatchUnhandledMove(focused, direction); 531 } 532 533 @Override 534 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 535 if (!mLauncher.isAllAppsVisible()) { 536 final Folder openFolder = getOpenFolder(); 537 if (openFolder == null) { 538 getChildAt(mCurrentScreen).addFocusables(views, direction); 539 if (direction == View.FOCUS_LEFT) { 540 if (mCurrentScreen > 0) { 541 getChildAt(mCurrentScreen - 1).addFocusables(views, direction); 542 } 543 } else if (direction == View.FOCUS_RIGHT){ 544 if (mCurrentScreen < getChildCount() - 1) { 545 getChildAt(mCurrentScreen + 1).addFocusables(views, direction); 546 } 547 } 548 } else { 549 openFolder.addFocusables(views, direction); 550 } 551 } 552 } 553 554 @Override 555 public boolean dispatchTouchEvent(MotionEvent ev) { 556 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 557 if (mLauncher.isWorkspaceLocked() || mLauncher.isAllAppsVisible()) { 558 return false; 559 } 560 } 561 return super.dispatchTouchEvent(ev); 562 } 563 564 @Override 565 public boolean onInterceptTouchEvent(MotionEvent ev) { 566 final boolean workspaceLocked = mLauncher.isWorkspaceLocked(); 567 final boolean allAppsVisible = mLauncher.isAllAppsVisible(); 568 if (workspaceLocked || allAppsVisible) { 569 return false; // We don't want the events. Let them fall through to the all apps view. 570 } 571 572 /* 573 * This method JUST determines whether we want to intercept the motion. 574 * If we return true, onTouchEvent will be called and we do the actual 575 * scrolling there. 576 */ 577 578 /* 579 * Shortcut the most recurring case: the user is in the dragging 580 * state and he is moving his finger. We want to intercept this 581 * motion. 582 */ 583 final int action = ev.getAction(); 584 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { 585 return true; 586 } 587 588 final float x = ev.getX(); 589 final float y = ev.getY(); 590 591 switch (action) { 592 case MotionEvent.ACTION_MOVE: 593 /* 594 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 595 * whether the user has moved far enough from his original down touch. 596 */ 597 598 /* 599 * Locally do absolute value. mLastMotionX is set to the y value 600 * of the down event. 601 */ 602 final int xDiff = (int) Math.abs(x - mLastMotionX); 603 final int yDiff = (int) Math.abs(y - mLastMotionY); 604 605 final int touchSlop = mTouchSlop; 606 boolean xMoved = xDiff > touchSlop; 607 boolean yMoved = yDiff > touchSlop; 608 609 if (xMoved || yMoved) { 610 611 if (xMoved) { 612 // Scroll if the user moved far enough along the X axis 613 mTouchState = TOUCH_STATE_SCROLLING; 614 enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1); 615 } 616 // Either way, cancel any pending longpress 617 if (mAllowLongPress) { 618 mAllowLongPress = false; 619 // Try canceling the long press. It could also have been scheduled 620 // by a distant descendant, so use the mAllowLongPress flag to block 621 // everything 622 final View currentScreen = getChildAt(mCurrentScreen); 623 currentScreen.cancelLongPress(); 624 } 625 } 626 break; 627 628 case MotionEvent.ACTION_DOWN: 629 // Remember location of down touch 630 mLastMotionX = x; 631 mLastMotionY = y; 632 mAllowLongPress = true; 633 634 /* 635 * If being flinged and user touches the screen, initiate drag; 636 * otherwise don't. mScroller.isFinished should be false when 637 * being flinged. 638 */ 639 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; 640 break; 641 642 case MotionEvent.ACTION_CANCEL: 643 case MotionEvent.ACTION_UP: 644 645 if (mTouchState != TOUCH_STATE_SCROLLING) { 646 final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen); 647 if (!currentScreen.lastDownOnOccupiedCell()) { 648 getLocationOnScreen(mTempCell); 649 // Send a tap to the wallpaper if the last down was on empty space 650 mWallpaperManager.sendWallpaperCommand(getWindowToken(), 651 "android.wallpaper.tap", 652 mTempCell[0] + (int) ev.getX(), 653 mTempCell[1] + (int) ev.getY(), 0, null); 654 } 655 } 656 657 // Release the drag 658 clearChildrenCache(); 659 mTouchState = TOUCH_STATE_REST; 660 mAllowLongPress = false; 661 662 break; 663 } 664 665 /* 666 * The only time we want to intercept motion events is if we are in the 667 * drag mode. 668 */ 669 return mTouchState != TOUCH_STATE_REST; 670 } 671 672 /** 673 * If one of our descendant views decides that it could be focused now, only 674 * pass that along if it's on the current screen. 675 * 676 * This happens when live folders requery, and if they're off screen, they 677 * end up calling requestFocus, which pulls it on screen. 678 */ 679 @Override 680 public void focusableViewAvailable(View focused) { 681 View current = getChildAt(mCurrentScreen); 682 View v = focused; 683 while (true) { 684 if (v == current) { 685 super.focusableViewAvailable(focused); 686 return; 687 } 688 if (v == this) { 689 return; 690 } 691 ViewParent parent = v.getParent(); 692 if (parent instanceof View) { 693 v = (View)v.getParent(); 694 } else { 695 return; 696 } 697 } 698 } 699 700 void enableChildrenCache(int fromScreen, int toScreen) { 701 if (fromScreen > toScreen) { 702 fromScreen = toScreen; 703 toScreen = fromScreen; 704 } 705 706 final int count = getChildCount(); 707 708 fromScreen = Math.max(fromScreen, 0); 709 toScreen = Math.min(toScreen, count - 1); 710 711 for (int i = fromScreen; i <= toScreen; i++) { 712 final CellLayout layout = (CellLayout) getChildAt(i); 713 layout.setChildrenDrawnWithCacheEnabled(true); 714 layout.setChildrenDrawingCacheEnabled(true); 715 } 716 } 717 718 void clearChildrenCache() { 719 final int count = getChildCount(); 720 for (int i = 0; i < count; i++) { 721 final CellLayout layout = (CellLayout) getChildAt(i); 722 layout.setChildrenDrawnWithCacheEnabled(false); 723 } 724 } 725 726 @Override 727 public boolean onTouchEvent(MotionEvent ev) { 728 729 if (mLauncher.isWorkspaceLocked()) { 730 return false; // We don't want the events. Let them fall through to the all apps view. 731 } 732 if (mLauncher.isAllAppsVisible()) { 733 // Cancel any scrolling that is in progress. 734 if (!mScroller.isFinished()) { 735 mScroller.abortAnimation(); 736 } 737 snapToScreen(mCurrentScreen); 738 return false; // We don't want the events. Let them fall through to the all apps view. 739 } 740 741 if (mVelocityTracker == null) { 742 mVelocityTracker = VelocityTracker.obtain(); 743 } 744 mVelocityTracker.addMovement(ev); 745 746 final int action = ev.getAction(); 747 final float x = ev.getX(); 748 749 switch (action) { 750 case MotionEvent.ACTION_DOWN: 751 /* 752 * If being flinged and user touches, stop the fling. isFinished 753 * will be false if being flinged. 754 */ 755 if (!mScroller.isFinished()) { 756 mScroller.abortAnimation(); 757 } 758 759 // Remember where the motion event started 760 mLastMotionX = x; 761 break; 762 case MotionEvent.ACTION_MOVE: 763 if (mTouchState == TOUCH_STATE_SCROLLING) { 764 // Scroll to follow the motion event 765 final int deltaX = (int) (mLastMotionX - x); 766 mLastMotionX = x; 767 768 if (deltaX < 0) { 769 if (mScrollX > 0) { 770 scrollBy(Math.max(-mScrollX, deltaX), 0); 771 updateWallpaperOffset(); 772 } 773 } else if (deltaX > 0) { 774 final int availableToScroll = getChildAt(getChildCount() - 1).getRight() - 775 mScrollX - getWidth(); 776 if (availableToScroll > 0) { 777 scrollBy(Math.min(availableToScroll, deltaX), 0); 778 updateWallpaperOffset(); 779 } 780 } else { 781 awakenScrollBars(); 782 } 783 } 784 break; 785 case MotionEvent.ACTION_UP: 786 if (mTouchState == TOUCH_STATE_SCROLLING) { 787 final VelocityTracker velocityTracker = mVelocityTracker; 788 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 789 int velocityX = (int) velocityTracker.getXVelocity(); 790 791 if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { 792 // Fling hard enough to move left 793 snapToScreen(mCurrentScreen - 1); 794 } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) { 795 // Fling hard enough to move right 796 snapToScreen(mCurrentScreen + 1); 797 } else { 798 snapToDestination(); 799 } 800 801 if (mVelocityTracker != null) { 802 mVelocityTracker.recycle(); 803 mVelocityTracker = null; 804 } 805 } 806 mTouchState = TOUCH_STATE_REST; 807 break; 808 case MotionEvent.ACTION_CANCEL: 809 mTouchState = TOUCH_STATE_REST; 810 } 811 812 return true; 813 } 814 815 private void snapToDestination() { 816 final int screenWidth = getWidth(); 817 final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth; 818 819 snapToScreen(whichScreen); 820 } 821 822 void snapToScreen(int whichScreen) { 823 //if (!mScroller.isFinished()) return; 824 825 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); 826 827 clearVacantCache(); 828 enableChildrenCache(mCurrentScreen, whichScreen); 829 830 final int screenDelta = Math.abs(whichScreen - mCurrentScreen); 831 832 mNextScreen = whichScreen; 833 834 mPreviousIndicator.setLevel(mNextScreen); 835 mNextIndicator.setLevel(mNextScreen); 836 837 View focusedChild = getFocusedChild(); 838 if (focusedChild != null && screenDelta != 0 && focusedChild == getChildAt(mCurrentScreen)) { 839 focusedChild.clearFocus(); 840 } 841 842 final int newX = whichScreen * getWidth(); 843 final int delta = newX - mScrollX; 844 final int duration = screenDelta * 300; 845 awakenScrollBars(duration); 846 847 if (!mScroller.isFinished()) mScroller.abortAnimation(); 848 mScroller.startScroll(mScrollX, 0, delta, 0, duration); 849 invalidate(); 850 } 851 852 void startDrag(CellLayout.CellInfo cellInfo) { 853 View child = cellInfo.cell; 854 855 // Make sure the drag was started by a long press as opposed to a long click. 856 if (!child.isInTouchMode()) { 857 return; 858 } 859 860 mDragInfo = cellInfo; 861 mDragInfo.screen = mCurrentScreen; 862 863 CellLayout current = ((CellLayout) getChildAt(mCurrentScreen)); 864 865 current.onDragChild(child); 866 mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE); 867 invalidate(); 868 } 869 870 @Override 871 protected Parcelable onSaveInstanceState() { 872 final SavedState state = new SavedState(super.onSaveInstanceState()); 873 state.currentScreen = mCurrentScreen; 874 return state; 875 } 876 877 @Override 878 protected void onRestoreInstanceState(Parcelable state) { 879 SavedState savedState = (SavedState) state; 880 super.onRestoreInstanceState(savedState.getSuperState()); 881 if (savedState.currentScreen != -1) { 882 mCurrentScreen = savedState.currentScreen; 883 Launcher.setScreen(mCurrentScreen); 884 } 885 } 886 887 void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo) { 888 addApplicationShortcut(info, cellInfo, false); 889 } 890 891 void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo, 892 boolean insertAtFirst) { 893 final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen); 894 final int[] result = new int[2]; 895 896 layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result); 897 onDropExternal(result[0], result[1], info, layout, insertAtFirst); 898 } 899 900 public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, 901 DragView dragView, Object dragInfo) { 902 final CellLayout cellLayout = getCurrentDropLayout(); 903 if (source != this) { 904 onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout); 905 } else { 906 // Move internally 907 if (mDragInfo != null) { 908 final View cell = mDragInfo.cell; 909 int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen; 910 if (index != mDragInfo.screen) { 911 final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen); 912 originalCellLayout.removeView(cell); 913 cellLayout.addView(cell); 914 } 915 mTargetCell = estimateDropCell(x - xOffset, y - yOffset, 916 mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell); 917 cellLayout.onDropChild(cell, mTargetCell); 918 919 final ItemInfo info = (ItemInfo) cell.getTag(); 920 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 921 LauncherModel.moveItemInDatabase(mLauncher, info, 922 LauncherSettings.Favorites.CONTAINER_DESKTOP, index, lp.cellX, lp.cellY); 923 } 924 } 925 } 926 927 public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, 928 DragView dragView, Object dragInfo) { 929 clearVacantCache(); 930 } 931 932 public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, 933 DragView dragView, Object dragInfo) { 934 } 935 936 public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, 937 DragView dragView, Object dragInfo) { 938 clearVacantCache(); 939 } 940 941 private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) { 942 onDropExternal(x, y, dragInfo, cellLayout, false); 943 } 944 945 private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout, 946 boolean insertAtFirst) { 947 // Drag from somewhere else 948 ItemInfo info = (ItemInfo) dragInfo; 949 950 View view; 951 952 switch (info.itemType) { 953 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 954 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 955 if (info.container == NO_ID) { 956 // Came from all apps -- make a copy 957 info = new ShortcutInfo((ApplicationInfo)info); 958 } 959 view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo)info); 960 break; 961 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 962 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, 963 (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info)); 964 break; 965 default: 966 throw new IllegalStateException("Unknown item type: " + info.itemType); 967 } 968 969 cellLayout.addView(view, insertAtFirst ? 0 : -1); 970 view.setHapticFeedbackEnabled(false); 971 view.setOnLongClickListener(mLongClickListener); 972 if (view instanceof DropTarget) { 973 mDragController.addDropTarget((DropTarget) view); 974 } 975 976 mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell); 977 cellLayout.onDropChild(view, mTargetCell); 978 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 979 980 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 981 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY); 982 } 983 984 /** 985 * Return the current {@link CellLayout}, correctly picking the destination 986 * screen while a scroll is in progress. 987 */ 988 private CellLayout getCurrentDropLayout() { 989 int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen; 990 return (CellLayout) getChildAt(index); 991 } 992 993 /** 994 * {@inheritDoc} 995 */ 996 public boolean acceptDrop(DragSource source, int x, int y, 997 int xOffset, int yOffset, DragView dragView, Object dragInfo) { 998 final CellLayout layout = getCurrentDropLayout(); 999 final CellLayout.CellInfo cellInfo = mDragInfo; 1000 final int spanX = cellInfo == null ? 1 : cellInfo.spanX; 1001 final int spanY = cellInfo == null ? 1 : cellInfo.spanY; 1002 1003 if (mVacantCache == null) { 1004 final View ignoreView = cellInfo == null ? null : cellInfo.cell; 1005 mVacantCache = layout.findAllVacantCells(null, ignoreView); 1006 } 1007 1008 return mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false); 1009 } 1010 1011 /** 1012 * {@inheritDoc} 1013 */ 1014 public Rect estimateDropLocation(DragSource source, int x, int y, 1015 int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) { 1016 final CellLayout layout = getCurrentDropLayout(); 1017 1018 final CellLayout.CellInfo cellInfo = mDragInfo; 1019 final int spanX = cellInfo == null ? 1 : cellInfo.spanX; 1020 final int spanY = cellInfo == null ? 1 : cellInfo.spanY; 1021 final View ignoreView = cellInfo == null ? null : cellInfo.cell; 1022 1023 final Rect location = recycle != null ? recycle : new Rect(); 1024 1025 // Find drop cell and convert into rectangle 1026 int[] dropCell = estimateDropCell(x - xOffset, y - yOffset, 1027 spanX, spanY, ignoreView, layout, mTempCell); 1028 1029 if (dropCell == null) { 1030 return null; 1031 } 1032 1033 layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate); 1034 location.left = mTempEstimate[0]; 1035 location.top = mTempEstimate[1]; 1036 1037 layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate); 1038 location.right = mTempEstimate[0]; 1039 location.bottom = mTempEstimate[1]; 1040 1041 return location; 1042 } 1043 1044 /** 1045 * Calculate the nearest cell where the given object would be dropped. 1046 */ 1047 private int[] estimateDropCell(int pixelX, int pixelY, 1048 int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) { 1049 // Create vacant cell cache if none exists 1050 if (mVacantCache == null) { 1051 mVacantCache = layout.findAllVacantCells(null, ignoreView); 1052 } 1053 1054 // Find the best target drop location 1055 return layout.findNearestVacantArea(pixelX, pixelY, 1056 spanX, spanY, mVacantCache, recycle); 1057 } 1058 1059 void setLauncher(Launcher launcher) { 1060 mLauncher = launcher; 1061 } 1062 1063 public void setDragController(DragController dragController) { 1064 mDragController = dragController; 1065 } 1066 1067 public void onDropCompleted(View target, boolean success) { 1068 clearVacantCache(); 1069 1070 if (success){ 1071 if (target != this && mDragInfo != null) { 1072 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1073 cellLayout.removeView(mDragInfo.cell); 1074 if (mDragInfo.cell instanceof DropTarget) { 1075 mDragController.removeDropTarget((DropTarget)mDragInfo.cell); 1076 } 1077 //final Object tag = mDragInfo.cell.getTag(); 1078 } 1079 } else { 1080 if (mDragInfo != null) { 1081 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1082 cellLayout.onDropAborted(mDragInfo.cell); 1083 } 1084 } 1085 1086 mDragInfo = null; 1087 } 1088 1089 public void scrollLeft() { 1090 clearVacantCache(); 1091 if (mScroller.isFinished()) { 1092 if (mCurrentScreen > 0) snapToScreen(mCurrentScreen - 1); 1093 } else { 1094 if (mNextScreen > 0) snapToScreen(mNextScreen - 1); 1095 } 1096 } 1097 1098 public void scrollRight() { 1099 clearVacantCache(); 1100 if (mScroller.isFinished()) { 1101 if (mCurrentScreen < getChildCount() -1) snapToScreen(mCurrentScreen + 1); 1102 } else { 1103 if (mNextScreen < getChildCount() -1) snapToScreen(mNextScreen + 1); 1104 } 1105 } 1106 1107 public int getScreenForView(View v) { 1108 int result = -1; 1109 if (v != null) { 1110 ViewParent vp = v.getParent(); 1111 int count = getChildCount(); 1112 for (int i = 0; i < count; i++) { 1113 if (vp == getChildAt(i)) { 1114 return i; 1115 } 1116 } 1117 } 1118 return result; 1119 } 1120 1121 public Folder getFolderForTag(Object tag) { 1122 int screenCount = getChildCount(); 1123 for (int screen = 0; screen < screenCount; screen++) { 1124 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1125 int count = currentScreen.getChildCount(); 1126 for (int i = 0; i < count; i++) { 1127 View child = currentScreen.getChildAt(i); 1128 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 1129 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { 1130 Folder f = (Folder) child; 1131 if (f.getInfo() == tag) { 1132 return f; 1133 } 1134 } 1135 } 1136 } 1137 return null; 1138 } 1139 1140 public View getViewForTag(Object tag) { 1141 int screenCount = getChildCount(); 1142 for (int screen = 0; screen < screenCount; screen++) { 1143 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1144 int count = currentScreen.getChildCount(); 1145 for (int i = 0; i < count; i++) { 1146 View child = currentScreen.getChildAt(i); 1147 if (child.getTag() == tag) { 1148 return child; 1149 } 1150 } 1151 } 1152 return null; 1153 } 1154 1155 /** 1156 * @return True is long presses are still allowed for the current touch 1157 */ 1158 public boolean allowLongPress() { 1159 return mAllowLongPress; 1160 } 1161 1162 /** 1163 * Set true to allow long-press events to be triggered, usually checked by 1164 * {@link Launcher} to accept or block dpad-initiated long-presses. 1165 */ 1166 public void setAllowLongPress(boolean allowLongPress) { 1167 mAllowLongPress = allowLongPress; 1168 } 1169 1170 void removeItemsForPackage(final String packageName) { 1171 final int count = getChildCount(); 1172 final PackageManager manager = getContext().getPackageManager(); 1173 final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); 1174 1175 for (int i = 0; i < count; i++) { 1176 final CellLayout layout = (CellLayout) getChildAt(i); 1177 1178 // Avoid ANRs by treating each screen separately 1179 post(new Runnable() { 1180 public void run() { 1181 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 1182 childrenToRemove.clear(); 1183 1184 int childCount = layout.getChildCount(); 1185 for (int j = 0; j < childCount; j++) { 1186 final View view = layout.getChildAt(j); 1187 Object tag = view.getTag(); 1188 1189 if (tag instanceof ShortcutInfo) { 1190 final ShortcutInfo info = (ShortcutInfo) tag; 1191 // We need to check for ACTION_MAIN otherwise getComponent() might 1192 // return null for some shortcuts (for instance, for shortcuts to 1193 // web pages.) 1194 final Intent intent = info.intent; 1195 final ComponentName name = intent.getComponent(); 1196 1197 if (Intent.ACTION_MAIN.equals(intent.getAction()) && 1198 name != null && packageName.equals(name.getPackageName())) { 1199 // TODO: This should probably be done on a worker thread 1200 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1201 childrenToRemove.add(view); 1202 } 1203 } else if (tag instanceof UserFolderInfo) { 1204 final UserFolderInfo info = (UserFolderInfo) tag; 1205 final ArrayList<ShortcutInfo> contents = info.contents; 1206 final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1); 1207 final int contentsCount = contents.size(); 1208 boolean removedFromFolder = false; 1209 1210 for (int k = 0; k < contentsCount; k++) { 1211 final ShortcutInfo appInfo = contents.get(k); 1212 final Intent intent = appInfo.intent; 1213 final ComponentName name = intent.getComponent(); 1214 1215 if (Intent.ACTION_MAIN.equals(intent.getAction()) && 1216 name != null && packageName.equals(name.getPackageName())) { 1217 toRemove.add(appInfo); 1218 // TODO: This should probably be done on a worker thread 1219 LauncherModel.deleteItemFromDatabase(mLauncher, appInfo); 1220 removedFromFolder = true; 1221 } 1222 } 1223 1224 contents.removeAll(toRemove); 1225 if (removedFromFolder) { 1226 final Folder folder = getOpenFolder(); 1227 if (folder != null) folder.notifyDataSetChanged(); 1228 } 1229 } else if (tag instanceof LiveFolderInfo) { 1230 final LiveFolderInfo info = (LiveFolderInfo) tag; 1231 final Uri uri = info.uri; 1232 final ProviderInfo providerInfo = manager.resolveContentProvider( 1233 uri.getAuthority(), 0); 1234 1235 if (providerInfo == null || 1236 packageName.equals(providerInfo.packageName)) { 1237 // TODO: This should probably be done on a worker thread 1238 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1239 childrenToRemove.add(view); 1240 } 1241 } else if (tag instanceof LauncherAppWidgetInfo) { 1242 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; 1243 final AppWidgetProviderInfo provider = 1244 widgets.getAppWidgetInfo(info.appWidgetId); 1245 if (provider == null || 1246 packageName.equals(provider.provider.getPackageName())) { 1247 // TODO: This should probably be done on a worker thread 1248 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1249 childrenToRemove.add(view); 1250 } 1251 } 1252 } 1253 1254 childCount = childrenToRemove.size(); 1255 for (int j = 0; j < childCount; j++) { 1256 View child = childrenToRemove.get(j); 1257 layout.removeViewInLayout(child); 1258 if (child instanceof DropTarget) { 1259 mDragController.removeDropTarget((DropTarget)child); 1260 } 1261 } 1262 1263 if (childCount > 0) { 1264 layout.requestLayout(); 1265 layout.invalidate(); 1266 } 1267 } 1268 }); 1269 } 1270 } 1271 1272 void updateShortcutsForPackage(String packageName) { 1273 final PackageManager pm = mLauncher.getPackageManager(); 1274 1275 final int count = getChildCount(); 1276 for (int i = 0; i < count; i++) { 1277 final CellLayout layout = (CellLayout) getChildAt(i); 1278 int childCount = layout.getChildCount(); 1279 for (int j = 0; j < childCount; j++) { 1280 final View view = layout.getChildAt(j); 1281 Object tag = view.getTag(); 1282 if (tag instanceof ShortcutInfo) { 1283 ShortcutInfo info = (ShortcutInfo)tag; 1284 // We need to check for ACTION_MAIN otherwise getComponent() might 1285 // return null for some shortcuts (for instance, for shortcuts to 1286 // web pages.) 1287 final Intent intent = info.intent; 1288 final ComponentName name = intent.getComponent(); 1289 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 1290 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null && 1291 packageName.equals(name.getPackageName())) { 1292 1293 info.setIcon(mIconCache.getIcon(info.intent)); 1294 ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(null, 1295 new FastBitmapDrawable(info.getIcon(mIconCache)), null, null); 1296 } 1297 } 1298 } 1299 } 1300 } 1301 1302 void moveToDefaultScreen(boolean animate) { 1303 if (animate) { 1304 snapToScreen(mDefaultScreen); 1305 } else { 1306 setCurrentScreen(mDefaultScreen); 1307 } 1308 getChildAt(mDefaultScreen).requestFocus(); 1309 } 1310 1311 void setIndicators(Drawable previous, Drawable next) { 1312 mPreviousIndicator = previous; 1313 mNextIndicator = next; 1314 previous.setLevel(mCurrentScreen); 1315 next.setLevel(mCurrentScreen); 1316 } 1317 1318 public static class SavedState extends BaseSavedState { 1319 int currentScreen = -1; 1320 1321 SavedState(Parcelable superState) { 1322 super(superState); 1323 } 1324 1325 private SavedState(Parcel in) { 1326 super(in); 1327 currentScreen = in.readInt(); 1328 } 1329 1330 @Override 1331 public void writeToParcel(Parcel out, int flags) { 1332 super.writeToParcel(out, flags); 1333 out.writeInt(currentScreen); 1334 } 1335 1336 public static final Parcelable.Creator<SavedState> CREATOR = 1337 new Parcelable.Creator<SavedState>() { 1338 public SavedState createFromParcel(Parcel in) { 1339 return new SavedState(in); 1340 } 1341 1342 public SavedState[] newArray(int size) { 1343 return new SavedState[size]; 1344 } 1345 }; 1346 } 1347} 1348