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