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