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