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