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