Workspace.java revision c28de51eedb26848abf9245ddd19e021d30be318
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 com.android.launcher.R; 20import com.android.launcher2.CellLayout.CellInfo; 21 22import android.animation.Animatable; 23import android.animation.PropertyAnimator; 24import android.animation.Sequencer; 25import android.animation.Animatable.AnimatableListener; 26import android.app.WallpaperManager; 27import android.appwidget.AppWidgetManager; 28import android.appwidget.AppWidgetProviderInfo; 29import android.content.ComponentName; 30import android.content.Context; 31import android.content.Intent; 32import android.content.pm.PackageManager; 33import android.content.pm.ProviderInfo; 34import android.content.res.Resources; 35import android.content.res.TypedArray; 36import android.graphics.Canvas; 37import android.graphics.Paint; 38import android.graphics.Rect; 39import android.graphics.drawable.Drawable; 40import android.net.Uri; 41import android.os.IBinder; 42import android.os.Parcel; 43import android.os.Parcelable; 44import android.util.AttributeSet; 45import android.util.Log; 46import android.view.MotionEvent; 47import android.view.VelocityTracker; 48import android.view.View; 49import android.view.ViewConfiguration; 50import android.view.ViewGroup; 51import android.view.ViewParent; 52import android.view.animation.Interpolator; 53import android.widget.Scroller; 54import android.widget.TextView; 55import android.widget.Toast; 56 57import java.util.ArrayList; 58import java.util.HashSet; 59 60/** 61 * The workspace is a wide area with a wallpaper and a finite number of screens. 62 * Each screen contains a number of icons, folders or widgets the user can 63 * interact with. A workspace is meant to be used with a fixed width only. 64 */ 65public class Workspace extends ViewGroup 66 implements DropTarget, DragSource, DragScroller, View.OnTouchListener { 67 @SuppressWarnings({"UnusedDeclaration"}) 68 private static final String TAG = "Launcher.Workspace"; 69 private static final int INVALID_SCREEN = -1; 70 // This is how much the workspace shrinks when we enter all apps or 71 // customization mode 72 private static final float SHRINK_FACTOR = 0.16f; 73 74 /** 75 * The velocity at which a fling gesture will cause us to snap to the next 76 * screen 77 */ 78 private static final int SNAP_VELOCITY = 600; 79 80 private final WallpaperManager mWallpaperManager; 81 82 private int mDefaultScreen; 83 84 private boolean mFirstLayout = true; 85 private boolean mWaitingToShrinkToBottom = false; 86 87 private int mCurrentScreen; 88 private int mNextScreen = INVALID_SCREEN; 89 private Scroller mScroller; 90 private VelocityTracker mVelocityTracker; 91 92 /** 93 * CellInfo for the cell that is currently being dragged 94 */ 95 private CellLayout.CellInfo mDragInfo; 96 97 /** 98 * Target drop area calculated during last acceptDrop call. 99 */ 100 private int[] mTargetCell = null; 101 102 /** 103 * The CellLayout that is currently being dragged over 104 */ 105 private CellLayout mDragTargetLayout = null; 106 107 private float mLastMotionX; 108 private float mLastMotionY; 109 110 private final static int TOUCH_STATE_REST = 0; 111 private final static int TOUCH_STATE_SCROLLING = 1; 112 113 private int mTouchState = TOUCH_STATE_REST; 114 115 private OnLongClickListener mLongClickListener; 116 117 private Launcher mLauncher; 118 private IconCache mIconCache; 119 private DragController mDragController; 120 121 122 private int[] mTempCell = new int[2]; 123 private int[] mTempEstimate = new int[2]; 124 125 private boolean mAllowLongPress = true; 126 127 private int mTouchSlop; 128 private int mMaximumVelocity; 129 130 private static final int INVALID_POINTER = -1; 131 private static final int DEFAULT_CELL_COUNT_X = 4; 132 private static final int DEFAULT_CELL_COUNT_Y = 4; 133 134 private int mActivePointerId = INVALID_POINTER; 135 136 private Drawable mPreviousIndicator; 137 private Drawable mNextIndicator; 138 139 private static final float NANOTIME_DIV = 1000000000.0f; 140 private static final float SMOOTHING_SPEED = 0.75f; 141 private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED)); 142 private float mSmoothingTime; 143 private float mTouchX; 144 145 private WorkspaceOvershootInterpolator mScrollInterpolator; 146 147 private static final float BASELINE_FLING_VELOCITY = 2500.f; 148 private static final float FLING_VELOCITY_INFLUENCE = 0.4f; 149 150 private Paint mDropIndicatorPaint; 151 152 // State variable that indicated whether the screens are small (ie when you're 153 // in all apps or customize mode) 154 private boolean mIsSmall; 155 private AnimatableListener mUnshrinkAnimationListener; 156 157 private static class WorkspaceOvershootInterpolator implements Interpolator { 158 private static final float DEFAULT_TENSION = 1.3f; 159 private float mTension; 160 161 public WorkspaceOvershootInterpolator() { 162 mTension = DEFAULT_TENSION; 163 } 164 165 public void setDistance(int distance) { 166 mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION; 167 } 168 169 public void disableSettle() { 170 mTension = 0.f; 171 } 172 173 public float getInterpolation(float t) { 174 // _o(t) = t * t * ((tension + 1) * t + tension) 175 // o(t) = _o(t - 1) + 1 176 t -= 1.0f; 177 return t * t * ((mTension + 1) * t + mTension) + 1.0f; 178 } 179 } 180 181 /** 182 * Used to inflate the Workspace from XML. 183 * 184 * @param context The application's context. 185 * @param attrs The attribtues set containing the Workspace's customization values. 186 */ 187 public Workspace(Context context, AttributeSet attrs) { 188 this(context, attrs, 0); 189 } 190 191 /** 192 * Used to inflate the Workspace from XML. 193 * 194 * @param context The application's context. 195 * @param attrs The attribtues set containing the Workspace's customization values. 196 * @param defStyle Unused. 197 */ 198 public Workspace(Context context, AttributeSet attrs, int defStyle) { 199 super(context, attrs, defStyle); 200 201 mWallpaperManager = WallpaperManager.getInstance(context); 202 203 TypedArray a = context.obtainStyledAttributes(attrs, 204 R.styleable.Workspace, defStyle, 0); 205 int cellCountX = a.getInt(R.styleable.Workspace_cellCountX, DEFAULT_CELL_COUNT_X); 206 int cellCountY = a.getInt(R.styleable.Workspace_cellCountY, DEFAULT_CELL_COUNT_Y); 207 mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1); 208 a.recycle(); 209 210 LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY); 211 setHapticFeedbackEnabled(false); 212 initWorkspace(); 213 } 214 215 /** 216 * Initializes various states for this workspace. 217 */ 218 private void initWorkspace() { 219 Context context = getContext(); 220 mScrollInterpolator = new WorkspaceOvershootInterpolator(); 221 mScroller = new Scroller(context, mScrollInterpolator); 222 mCurrentScreen = mDefaultScreen; 223 Launcher.setScreen(mCurrentScreen); 224 LauncherApplication app = (LauncherApplication)context.getApplicationContext(); 225 mIconCache = app.getIconCache(); 226 227 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 228 mTouchSlop = configuration.getScaledTouchSlop(); 229 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 230 mUnshrinkAnimationListener = new AnimatableListener() { 231 public void onAnimationStart(Animatable animation) {} 232 public void onAnimationEnd(Animatable animation) { 233 mIsSmall = false; 234 } 235 public void onAnimationCancel(Animatable animation) {} 236 public void onAnimationRepeat(Animatable animation) {} 237 }; 238 } 239 240 @Override 241 public void addView(View child, int index, LayoutParams params) { 242 if (!(child instanceof CellLayout)) { 243 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 244 } 245 super.addView(child, index, params); 246 } 247 248 @Override 249 public void addView(View child) { 250 if (!(child instanceof CellLayout)) { 251 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 252 } 253 super.addView(child); 254 } 255 256 @Override 257 public void addView(View child, int index) { 258 if (!(child instanceof CellLayout)) { 259 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 260 } 261 super.addView(child, index); 262 } 263 264 @Override 265 public void addView(View child, int width, int height) { 266 if (!(child instanceof CellLayout)) { 267 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 268 } 269 super.addView(child, width, height); 270 } 271 272 @Override 273 public void addView(View child, LayoutParams params) { 274 if (!(child instanceof CellLayout)) { 275 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 276 } 277 super.addView(child, params); 278 } 279 280 /** 281 * @return The open folder on the current screen, or null if there is none 282 */ 283 Folder getOpenFolder() { 284 CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen); 285 int count = currentScreen.getChildCount(); 286 for (int i = 0; i < count; i++) { 287 View child = currentScreen.getChildAt(i); 288 if (child instanceof Folder) { 289 Folder folder = (Folder) child; 290 if (folder.getInfo().opened) 291 return folder; 292 } 293 } 294 return null; 295 } 296 297 ArrayList<Folder> getOpenFolders() { 298 final int screenCount = getChildCount(); 299 ArrayList<Folder> folders = new ArrayList<Folder>(screenCount); 300 301 for (int screen = 0; screen < screenCount; screen++) { 302 CellLayout currentScreen = (CellLayout) getChildAt(screen); 303 int count = currentScreen.getChildCount(); 304 for (int i = 0; i < count; i++) { 305 View child = currentScreen.getChildAt(i); 306 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child 307 .getLayoutParams(); 308 if (child instanceof Folder) { 309 Folder folder = (Folder) child; 310 if (folder.getInfo().opened) 311 folders.add(folder); 312 break; 313 } 314 } 315 } 316 317 return folders; 318 } 319 320 boolean isDefaultScreenShowing() { 321 return mCurrentScreen == mDefaultScreen; 322 } 323 324 /** 325 * Returns the index of the currently displayed screen. 326 * 327 * @return The index of the currently displayed screen. 328 */ 329 int getCurrentScreen() { 330 return mCurrentScreen; 331 } 332 333 /** 334 * Sets the current screen. 335 * 336 * @param currentScreen 337 */ 338 void setCurrentScreen(int currentScreen) { 339 setCurrentScreen(currentScreen, true); 340 } 341 342 void setCurrentScreen(int currentScreen, boolean animateScrolling) { 343 setCurrentScreen(currentScreen, animateScrolling, getWidth()); 344 } 345 346 void setCurrentScreen(int currentScreen, boolean animateScrolling, int screenWidth) { 347 if (!mScroller.isFinished()) 348 mScroller.abortAnimation(); 349 mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1)); 350 if (mPreviousIndicator != null) { 351 mPreviousIndicator.setLevel(mCurrentScreen); 352 mNextIndicator.setLevel(mCurrentScreen); 353 } 354 if (animateScrolling) { 355 scrollTo(mCurrentScreen * screenWidth, 0); 356 } else { 357 mScrollX = mCurrentScreen * screenWidth; 358 } 359 updateWallpaperOffset(screenWidth * (getChildCount() - 1)); 360 invalidate(); 361 } 362 363 /** 364 * Adds the specified child in the current screen. The position and dimension of 365 * the child are defined by x, y, spanX and spanY. 366 * 367 * @param child The child to add in one of the workspace's screens. 368 * @param x The X position of the child in the screen's grid. 369 * @param y The Y position of the child in the screen's grid. 370 * @param spanX The number of cells spanned horizontally by the child. 371 * @param spanY The number of cells spanned vertically by the child. 372 */ 373 void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) { 374 addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false); 375 } 376 377 /** 378 * Adds the specified child in the current screen. The position and dimension of 379 * the child are defined by x, y, spanX and spanY. 380 * 381 * @param child The child to add in one of the workspace's screens. 382 * @param x The X position of the child in the screen's grid. 383 * @param y The Y position of the child in the screen's grid. 384 * @param spanX The number of cells spanned horizontally by the child. 385 * @param spanY The number of cells spanned vertically by the child. 386 * @param insert When true, the child is inserted at the beginning of the children list. 387 */ 388 void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) { 389 addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert); 390 } 391 392 /** 393 * Adds the specified child in the specified screen. The position and dimension of 394 * the child are defined by x, y, spanX and spanY. 395 * 396 * @param child The child to add in one of the workspace's screens. 397 * @param screen The screen in which to add the child. 398 * @param x The X position of the child in the screen's grid. 399 * @param y The Y position of the child in the screen's grid. 400 * @param spanX The number of cells spanned horizontally by the child. 401 * @param spanY The number of cells spanned vertically by the child. 402 */ 403 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) { 404 addInScreen(child, screen, x, y, spanX, spanY, false); 405 } 406 407 void addInFullScreen(View child, int screen) { 408 addInScreen(child, screen, 0, 0, -1, -1); 409 } 410 411 /** 412 * Adds the specified child in the specified screen. The position and dimension of 413 * the child are defined by x, y, spanX and spanY. 414 * 415 * @param child The child to add in one of the workspace's screens. 416 * @param screen The screen in which to add the child. 417 * @param x The X position of the child in the screen's grid. 418 * @param y The Y position of the child in the screen's grid. 419 * @param spanX The number of cells spanned horizontally by the child. 420 * @param spanY The number of cells spanned vertically by the child. 421 * @param insert When true, the child is inserted at the beginning of the children list. 422 */ 423 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) { 424 if (screen < 0 || screen >= getChildCount()) { 425 Log.e(TAG, "The screen must be >= 0 and < " + getChildCount() 426 + " (was " + screen + "); skipping child"); 427 return; 428 } 429 430 final CellLayout group = (CellLayout) getChildAt(screen); 431 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 432 if (lp == null) { 433 lp = new CellLayout.LayoutParams(x, y, spanX, spanY); 434 } else { 435 lp.cellX = x; 436 lp.cellY = y; 437 lp.cellHSpan = spanX; 438 lp.cellVSpan = spanY; 439 } 440 441 // Get the canonical child id to uniquely represent this view in this screen 442 int childId = LauncherModel.getCellLayoutChildId(child.getId(), screen, x, y, spanX, spanY); 443 if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp)) { 444 // TODO: This branch occurs when the workspace is adding views 445 // outside of the defined grid 446 // maybe we should be deleting these items from the LauncherModel? 447 Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout"); 448 } 449 450 if (!(child instanceof Folder)) { 451 child.setHapticFeedbackEnabled(false); 452 child.setOnLongClickListener(mLongClickListener); 453 } 454 if (child instanceof DropTarget) { 455 mDragController.addDropTarget((DropTarget) child); 456 } 457 } 458 459 CellLayout.CellInfo updateOccupiedCells(boolean[] occupied) { 460 CellLayout group = (CellLayout) getChildAt(mCurrentScreen); 461 if (group != null) { 462 return group.updateOccupiedCells(occupied, null); 463 } 464 return null; 465 } 466 467 public boolean onTouch(View v, MotionEvent event) { 468 // this is an intercepted event being forwarded from a cell layout 469 if (mIsSmall) { 470 unshrink((CellLayout)v); 471 mLauncher.onWorkspaceUnshrink(); 472 } 473 return false; 474 } 475 476 /** 477 * Registers the specified listener on each screen contained in this workspace. 478 * 479 * @param l The listener used to respond to long clicks. 480 */ 481 @Override 482 public void setOnLongClickListener(OnLongClickListener l) { 483 mLongClickListener = l; 484 final int screenCount = getChildCount(); 485 for (int i = 0; i < screenCount; i++) { 486 getChildAt(i).setOnLongClickListener(l); 487 } 488 } 489 490 private void updateWallpaperOffset() { 491 updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft)); 492 } 493 494 private void updateWallpaperOffset(int scrollRange) { 495 IBinder token = getWindowToken(); 496 if (token != null) { 497 mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 ); 498 mWallpaperManager.setWallpaperOffsets(getWindowToken(), 499 Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0); 500 } 501 } 502 503 @Override 504 public void scrollTo(int x, int y) { 505 super.scrollTo(x, y); 506 mTouchX = x; 507 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 508 } 509 510 @Override 511 public void computeScroll() { 512 if (mScroller.computeScrollOffset()) { 513 mTouchX = mScrollX = mScroller.getCurrX(); 514 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 515 mScrollY = mScroller.getCurrY(); 516 updateWallpaperOffset(); 517 postInvalidate(); 518 } else if (mNextScreen != INVALID_SCREEN) { 519 mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1)); 520 if (mPreviousIndicator != null) { 521 mPreviousIndicator.setLevel(mCurrentScreen); 522 mNextIndicator.setLevel(mCurrentScreen); 523 } 524 Launcher.setScreen(mCurrentScreen); 525 mNextScreen = INVALID_SCREEN; 526 clearChildrenCache(); 527 } else if (mTouchState == TOUCH_STATE_SCROLLING) { 528 final float now = System.nanoTime() / NANOTIME_DIV; 529 final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT); 530 final float dx = mTouchX - mScrollX; 531 mScrollX += dx * e; 532 mSmoothingTime = now; 533 534 // Keep generating points as long as we're more than 1px away from the target 535 if (dx > 1.f || dx < -1.f) { 536 updateWallpaperOffset(); 537 postInvalidate(); 538 } 539 } 540 } 541 542 @Override 543 protected void dispatchDraw(Canvas canvas) { 544 boolean restore = false; 545 int restoreCount = 0; 546 547 // ViewGroup.dispatchDraw() supports many features we don't need: 548 // clip to padding, layout animation, animation listener, disappearing 549 // children, etc. The following implementation attempts to fast-track 550 // the drawing dispatch by drawing only what we know needs to be drawn. 551 552 boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN; 553 554 // if the screens are all small, we need to draw all the screens since 555 // they're most likely all visible 556 if (mIsSmall) { 557 final int screenCount = getChildCount(); 558 for (int i = 0; i < screenCount; i++) { 559 CellLayout cl = (CellLayout)getChildAt(i); 560 drawChild(canvas, cl, getDrawingTime()); 561 } 562 } else if (fastDraw) { 563 // If we are not scrolling or flinging, draw only the current screen 564 drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime()); 565 } else { 566 final long drawingTime = getDrawingTime(); 567 final float scrollPos = (float) mScrollX / getWidth(); 568 final int leftScreen = (int) scrollPos; 569 final int rightScreen = leftScreen + 1; 570 if (leftScreen >= 0) { 571 drawChild(canvas, getChildAt(leftScreen), drawingTime); 572 } 573 if (scrollPos != leftScreen && rightScreen < getChildCount()) { 574 drawChild(canvas, getChildAt(rightScreen), drawingTime); 575 } 576 } 577 578 if (restore) { 579 canvas.restoreToCount(restoreCount); 580 } 581 } 582 583 protected void onAttachedToWindow() { 584 super.onAttachedToWindow(); 585 computeScroll(); 586 mDragController.setWindowToken(getWindowToken()); 587 } 588 589 @Override 590 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 591 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 592 593 final int width = MeasureSpec.getSize(widthMeasureSpec); 594 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 595 if (widthMode != MeasureSpec.EXACTLY) { 596 throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); 597 } 598 599 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 600 if (heightMode != MeasureSpec.EXACTLY) { 601 throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); 602 } 603 604 // The children are given the same width and height as the workspace 605 final int screenCount = getChildCount(); 606 for (int i = 0; i < screenCount; i++) { 607 getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); 608 } 609 610 if (mFirstLayout) { 611 setHorizontalScrollBarEnabled(false); 612 setCurrentScreen(mCurrentScreen, false, width); 613 setHorizontalScrollBarEnabled(true); 614 } 615 } 616 617 @Override 618 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 619 if (mFirstLayout) { 620 mFirstLayout = false; 621 } 622 int childLeft = 0; 623 final int screenCount = getChildCount(); 624 for (int i = 0; i < screenCount; i++) { 625 final View child = getChildAt(i); 626 if (child.getVisibility() != View.GONE) { 627 final int childWidth = child.getMeasuredWidth(); 628 child.layout(childLeft, 0, 629 childLeft + childWidth, child.getMeasuredHeight()); 630 childLeft += childWidth; 631 } 632 } 633 634 // if shrinkToBottom() is called on initialization, it has to be deferred 635 // until after the first call to onLayout so that it has the correct width 636 if (mWaitingToShrinkToBottom) { 637 shrinkToBottom(false); 638 mWaitingToShrinkToBottom = false; 639 } 640 641 if (LauncherApplication.isInPlaceRotationEnabled()) { 642 // When the device is rotated, the scroll position of the current screen 643 // needs to be refreshed 644 setCurrentScreen(getCurrentScreen()); 645 } 646 } 647 648 @Override 649 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { 650 int screen = indexOfChild(child); 651 if (screen != mCurrentScreen || !mScroller.isFinished()) { 652 if (!mLauncher.isWorkspaceLocked()) { 653 snapToScreen(screen); 654 } 655 return true; 656 } 657 return false; 658 } 659 660 @Override 661 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 662 if (!mLauncher.isAllAppsVisible()) { 663 final Folder openFolder = getOpenFolder(); 664 if (openFolder != null) { 665 return openFolder.requestFocus(direction, previouslyFocusedRect); 666 } else { 667 int focusableScreen; 668 if (mNextScreen != INVALID_SCREEN) { 669 focusableScreen = mNextScreen; 670 } else { 671 focusableScreen = mCurrentScreen; 672 } 673 getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect); 674 } 675 } 676 return false; 677 } 678 679 @Override 680 public boolean dispatchUnhandledMove(View focused, int direction) { 681 if (direction == View.FOCUS_LEFT) { 682 if (getCurrentScreen() > 0) { 683 snapToScreen(getCurrentScreen() - 1); 684 return true; 685 } 686 } else if (direction == View.FOCUS_RIGHT) { 687 if (getCurrentScreen() < getChildCount() - 1) { 688 snapToScreen(getCurrentScreen() + 1); 689 return true; 690 } 691 } 692 return super.dispatchUnhandledMove(focused, direction); 693 } 694 695 @Override 696 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 697 if (!mLauncher.isAllAppsVisible()) { 698 final Folder openFolder = getOpenFolder(); 699 if (openFolder == null) { 700 getChildAt(mCurrentScreen).addFocusables(views, direction); 701 if (direction == View.FOCUS_LEFT) { 702 if (mCurrentScreen > 0) { 703 getChildAt(mCurrentScreen - 1).addFocusables(views, direction); 704 } 705 } else if (direction == View.FOCUS_RIGHT) { 706 if (mCurrentScreen < getChildCount() - 1) { 707 getChildAt(mCurrentScreen + 1).addFocusables(views, direction); 708 } 709 } 710 } else { 711 openFolder.addFocusables(views, direction); 712 } 713 } 714 } 715 716 @Override 717 public boolean dispatchTouchEvent(MotionEvent ev) { 718 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 719 // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps 720 // ie when you click on a mini-screen, it zooms back to that screen) 721 if (mLauncher.isWorkspaceLocked() || 722 (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible())) { 723 return false; 724 } 725 } 726 return super.dispatchTouchEvent(ev); 727 } 728 729 /** 730 * {@inheritDoc} 731 */ 732 @Override 733 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 734 if (disallowIntercept) { 735 // We need to make sure to cancel our long press if 736 // a scrollable widget takes over touch events 737 final View currentScreen = getChildAt(mCurrentScreen); 738 currentScreen.cancelLongPress(); 739 } 740 super.requestDisallowInterceptTouchEvent(disallowIntercept); 741 } 742 743 @Override 744 public boolean onInterceptTouchEvent(MotionEvent ev) { 745 final boolean workspaceLocked = mLauncher.isWorkspaceLocked(); 746 final boolean allAppsVisible = mLauncher.isAllAppsVisible(); 747 748 // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps 749 // ie when you click on a mini-screen, it zooms back to that screen) 750 if (workspaceLocked || (!LauncherApplication.isScreenXLarge() && allAppsVisible)) { 751 return false; // We don't want the events. Let them fall through to the all apps view. 752 } 753 754 /* 755 * This method JUST determines whether we want to intercept the motion. 756 * If we return true, onTouchEvent will be called and we do the actual 757 * scrolling there. 758 */ 759 760 /* 761 * Shortcut the most recurring case: the user is in the dragging 762 * state and he is moving his finger. We want to intercept this 763 * motion. 764 */ 765 final int action = ev.getAction(); 766 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { 767 return true; 768 } 769 770 if (mVelocityTracker == null) { 771 mVelocityTracker = VelocityTracker.obtain(); 772 } 773 mVelocityTracker.addMovement(ev); 774 775 switch (action & MotionEvent.ACTION_MASK) { 776 case MotionEvent.ACTION_MOVE: { 777 /* 778 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 779 * whether the user has moved far enough from his original down touch. 780 */ 781 782 /* 783 * Locally do absolute value. mLastMotionX is set to the y value 784 * of the down event. 785 */ 786 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 787 final float x = ev.getX(pointerIndex); 788 final float y = ev.getY(pointerIndex); 789 final int xDiff = (int) Math.abs(x - mLastMotionX); 790 final int yDiff = (int) Math.abs(y - mLastMotionY); 791 792 final int touchSlop = mTouchSlop; 793 boolean xMoved = xDiff > touchSlop; 794 boolean yMoved = yDiff > touchSlop; 795 796 if (xMoved || yMoved) { 797 798 if (xMoved) { 799 // Scroll if the user moved far enough along the X axis 800 mTouchState = TOUCH_STATE_SCROLLING; 801 mLastMotionX = x; 802 mTouchX = mScrollX; 803 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 804 enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1); 805 } 806 // Either way, cancel any pending longpress 807 if (mAllowLongPress) { 808 mAllowLongPress = false; 809 // Try canceling the long press. It could also have been scheduled 810 // by a distant descendant, so use the mAllowLongPress flag to block 811 // everything 812 final View currentScreen = getChildAt(mCurrentScreen); 813 currentScreen.cancelLongPress(); 814 } 815 } 816 break; 817 } 818 819 case MotionEvent.ACTION_DOWN: { 820 final float x = ev.getX(); 821 final float y = ev.getY(); 822 // Remember location of down touch 823 mLastMotionX = x; 824 mLastMotionY = y; 825 mActivePointerId = ev.getPointerId(0); 826 mAllowLongPress = true; 827 828 /* 829 * If being flinged and user touches the screen, initiate drag; 830 * otherwise don't. mScroller.isFinished should be false when 831 * being flinged. 832 */ 833 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; 834 break; 835 } 836 837 case MotionEvent.ACTION_CANCEL: 838 case MotionEvent.ACTION_UP: 839 840 if (mTouchState != TOUCH_STATE_SCROLLING) { 841 final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen); 842 if (!currentScreen.lastDownOnOccupiedCell()) { 843 getLocationOnScreen(mTempCell); 844 // Send a tap to the wallpaper if the last down was on empty space 845 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 846 mWallpaperManager.sendWallpaperCommand(getWindowToken(), 847 "android.wallpaper.tap", 848 mTempCell[0] + (int) ev.getX(pointerIndex), 849 mTempCell[1] + (int) ev.getY(pointerIndex), 0, null); 850 } 851 } 852 853 // Release the drag 854 clearChildrenCache(); 855 mTouchState = TOUCH_STATE_REST; 856 mActivePointerId = INVALID_POINTER; 857 mAllowLongPress = false; 858 859 if (mVelocityTracker != null) { 860 mVelocityTracker.recycle(); 861 mVelocityTracker = null; 862 } 863 864 break; 865 866 case MotionEvent.ACTION_POINTER_UP: 867 onSecondaryPointerUp(ev); 868 break; 869 } 870 871 /* 872 * The only time we want to intercept motion events is if we are in the 873 * drag mode. 874 */ 875 return mTouchState != TOUCH_STATE_REST; 876 } 877 878 private void onSecondaryPointerUp(MotionEvent ev) { 879 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 880 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 881 final int pointerId = ev.getPointerId(pointerIndex); 882 if (pointerId == mActivePointerId) { 883 // This was our active pointer going up. Choose a new 884 // active pointer and adjust accordingly. 885 // TODO: Make this decision more intelligent. 886 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 887 mLastMotionX = ev.getX(newPointerIndex); 888 mLastMotionY = ev.getY(newPointerIndex); 889 mActivePointerId = ev.getPointerId(newPointerIndex); 890 if (mVelocityTracker != null) { 891 mVelocityTracker.clear(); 892 } 893 } 894 } 895 896 /** 897 * If one of our descendant views decides that it could be focused now, only 898 * pass that along if it's on the current screen. 899 * 900 * This happens when live folders requery, and if they're off screen, they 901 * end up calling requestFocus, which pulls it on screen. 902 */ 903 @Override 904 public void focusableViewAvailable(View focused) { 905 View current = getChildAt(mCurrentScreen); 906 View v = focused; 907 while (true) { 908 if (v == current) { 909 super.focusableViewAvailable(focused); 910 return; 911 } 912 if (v == this) { 913 return; 914 } 915 ViewParent parent = v.getParent(); 916 if (parent instanceof View) { 917 v = (View) v.getParent(); 918 } else { 919 return; 920 } 921 } 922 } 923 924 void enableChildrenCache(int fromScreen, int toScreen) { 925 if (fromScreen > toScreen) { 926 final int temp = fromScreen; 927 fromScreen = toScreen; 928 toScreen = temp; 929 } 930 931 final int screenCount = getChildCount(); 932 933 fromScreen = Math.max(fromScreen, 0); 934 toScreen = Math.min(toScreen, screenCount - 1); 935 936 for (int i = fromScreen; i <= toScreen; i++) { 937 final CellLayout layout = (CellLayout) getChildAt(i); 938 layout.setChildrenDrawnWithCacheEnabled(true); 939 layout.setChildrenDrawingCacheEnabled(true); 940 } 941 } 942 943 void clearChildrenCache() { 944 final int screenCount = getChildCount(); 945 for (int i = 0; i < screenCount; i++) { 946 final CellLayout layout = (CellLayout) getChildAt(i); 947 layout.setChildrenDrawnWithCacheEnabled(false); 948 } 949 } 950 951 @Override 952 public boolean onTouchEvent(MotionEvent ev) { 953 954 if (mLauncher.isWorkspaceLocked()) { 955 return false; // We don't want the events. Let them fall through to the all apps view. 956 } 957 if (mLauncher.isAllAppsVisible()) { 958 // Cancel any scrolling that is in progress. 959 if (!mScroller.isFinished()) { 960 mScroller.abortAnimation(); 961 } 962 snapToScreen(mCurrentScreen); 963 return false; // We don't want the events. Let them fall through to the all apps view. 964 } 965 966 if (mVelocityTracker == null) { 967 mVelocityTracker = VelocityTracker.obtain(); 968 } 969 mVelocityTracker.addMovement(ev); 970 971 final int action = ev.getAction(); 972 973 switch (action & MotionEvent.ACTION_MASK) { 974 case MotionEvent.ACTION_DOWN: 975 /* 976 * If being flinged and user touches, stop the fling. isFinished 977 * will be false if being flinged. 978 */ 979 if (!mScroller.isFinished()) { 980 mScroller.abortAnimation(); 981 } 982 983 // Remember where the motion event started 984 mLastMotionX = ev.getX(); 985 mActivePointerId = ev.getPointerId(0); 986 if (mTouchState == TOUCH_STATE_SCROLLING) { 987 enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1); 988 } 989 break; 990 case MotionEvent.ACTION_MOVE: 991 if (mTouchState == TOUCH_STATE_SCROLLING) { 992 // Scroll to follow the motion event 993 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 994 final float x = ev.getX(pointerIndex); 995 final float deltaX = mLastMotionX - x; 996 mLastMotionX = x; 997 998 if (deltaX < 0) { 999 if (mTouchX > 0) { 1000 mTouchX += Math.max(-mTouchX, deltaX); 1001 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 1002 invalidate(); 1003 } 1004 } else if (deltaX > 0) { 1005 final float availableToScroll = getChildAt(getChildCount() - 1).getRight() - 1006 mTouchX - getWidth(); 1007 if (availableToScroll > 0) { 1008 mTouchX += Math.min(availableToScroll, deltaX); 1009 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 1010 invalidate(); 1011 } 1012 } else { 1013 awakenScrollBars(); 1014 } 1015 } 1016 break; 1017 case MotionEvent.ACTION_UP: 1018 if (mTouchState == TOUCH_STATE_SCROLLING) { 1019 final VelocityTracker velocityTracker = mVelocityTracker; 1020 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1021 final int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId); 1022 1023 final int screenWidth = getWidth(); 1024 final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth; 1025 final float scrolledPos = (float) mScrollX / screenWidth; 1026 1027 if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { 1028 // Fling hard enough to move left. 1029 // Don't fling across more than one screen at a time. 1030 final int bound = scrolledPos < whichScreen ? 1031 mCurrentScreen - 1 : mCurrentScreen; 1032 snapToScreen(Math.min(whichScreen, bound), velocityX, true); 1033 } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) { 1034 // Fling hard enough to move right 1035 // Don't fling across more than one screen at a time. 1036 final int bound = scrolledPos > whichScreen ? 1037 mCurrentScreen + 1 : mCurrentScreen; 1038 snapToScreen(Math.max(whichScreen, bound), velocityX, true); 1039 } else { 1040 snapToScreen(whichScreen, 0, true); 1041 } 1042 1043 if (mVelocityTracker != null) { 1044 mVelocityTracker.recycle(); 1045 mVelocityTracker = null; 1046 } 1047 } 1048 mTouchState = TOUCH_STATE_REST; 1049 mActivePointerId = INVALID_POINTER; 1050 break; 1051 case MotionEvent.ACTION_CANCEL: 1052 mTouchState = TOUCH_STATE_REST; 1053 mActivePointerId = INVALID_POINTER; 1054 break; 1055 case MotionEvent.ACTION_POINTER_UP: 1056 onSecondaryPointerUp(ev); 1057 break; 1058 } 1059 1060 return true; 1061 } 1062 1063 void shrinkToTop() { 1064 shrink(true, true); 1065 } 1066 1067 void shrinkToBottom() { 1068 shrinkToBottom(true); 1069 } 1070 1071 void shrinkToBottom(boolean animated) { 1072 if (mFirstLayout) { 1073 // (mFirstLayout == "first layout has not happened yet") 1074 // if we get a call to shrink() as part of our initialization (for example, if 1075 // Launcher is started in All Apps mode) then we need to wait for a layout call 1076 // to get our width so we can layout the mini-screen views correctly 1077 mWaitingToShrinkToBottom = true; 1078 } else { 1079 shrink(false, animated); 1080 } 1081 } 1082 1083 // we use this to shrink the workspace for the all apps view and the customize view 1084 private void shrink(boolean shrinkToTop, boolean animated) { 1085 mIsSmall = true; 1086 final Resources res = getResources(); 1087 final int screenWidth = getWidth(); 1088 final int screenHeight = getHeight(); 1089 final int scaledScreenWidth = (int) (SHRINK_FACTOR * screenWidth); 1090 final int scaledScreenHeight = (int) (SHRINK_FACTOR * screenHeight); 1091 final float scaledSpacing = res.getDimension(R.dimen.smallScreenSpacing); 1092 1093 final int screenCount = getChildCount(); 1094 float totalWidth = screenCount * scaledScreenWidth + (screenCount - 1) * scaledSpacing; 1095 1096 float newY = getResources().getDimension(R.dimen.smallScreenVerticalMargin); 1097 if (!shrinkToTop) { 1098 newY = screenHeight - newY - scaledScreenHeight; 1099 } 1100 1101 // We animate all the screens to the centered position in workspace 1102 // At the same time, the screens become greyed/dimmed 1103 1104 // newX is initialized to the left-most position of the centered screens 1105 float newX = (mCurrentScreen + 1) * screenWidth - screenWidth / 2 - totalWidth / 2; 1106 Sequencer s = new Sequencer(); 1107 for (int i = 0; i < screenCount; i++) { 1108 CellLayout cl = (CellLayout) getChildAt(i); 1109 cl.setPivotX(0.0f); 1110 cl.setPivotY(0.0f); 1111 if (animated) { 1112 final int duration = res.getInteger(R.integer.config_workspaceShrinkTime); 1113 s.playTogether( 1114 new PropertyAnimator(duration, cl, "x", newX), 1115 new PropertyAnimator(duration, cl, "y", newY), 1116 new PropertyAnimator(duration, cl, "scaleX", SHRINK_FACTOR), 1117 new PropertyAnimator(duration, cl, "scaleY", SHRINK_FACTOR), 1118 new PropertyAnimator(duration, cl, "dimmedBitmapAlpha", 1.0f)); 1119 } else { 1120 cl.setX((int)newX); 1121 cl.setY((int)newY); 1122 cl.setScaleX(SHRINK_FACTOR); 1123 cl.setScaleY(SHRINK_FACTOR); 1124 cl.setDimmedBitmapAlpha(1.0f); 1125 } 1126 // increment newX for the next screen 1127 newX += scaledScreenWidth + scaledSpacing; 1128 cl.setOnInterceptTouchListener(this); 1129 } 1130 setChildrenDrawnWithCacheEnabled(true); 1131 if (animated) s.start(); 1132 } 1133 1134 // We call this when we trigger an unshrink by clicking on the CellLayout cl 1135 private void unshrink(CellLayout clThatWasClicked) { 1136 int newCurrentScreen = mCurrentScreen; 1137 final int screenCount = getChildCount(); 1138 for (int i = 0; i < screenCount; i++) { 1139 if (getChildAt(i) == clThatWasClicked) { 1140 newCurrentScreen = i; 1141 } 1142 } 1143 unshrink(newCurrentScreen); 1144 } 1145 1146 private void unshrink(int newCurrentScreen) { 1147 if (mIsSmall) { 1148 int delta = (newCurrentScreen - mCurrentScreen)*getWidth(); 1149 1150 final int screenCount = getChildCount(); 1151 for (int i = 0; i < screenCount; i++) { 1152 CellLayout cl = (CellLayout) getChildAt(i); 1153 cl.setX(cl.getX() + delta); 1154 } 1155 mScrollX = newCurrentScreen * getWidth(); 1156 1157 unshrink(); 1158 setCurrentScreen(newCurrentScreen); 1159 } 1160 } 1161 1162 void unshrink() { 1163 unshrink(true); 1164 } 1165 1166 void unshrink(boolean animated) { 1167 if (mIsSmall) { 1168 Sequencer s = new Sequencer(); 1169 final int screenCount = getChildCount(); 1170 1171 final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime); 1172 for (int i = 0; i < screenCount; i++) { 1173 final CellLayout cl = (CellLayout)getChildAt(i); 1174 cl.setPivotX(0.0f); 1175 cl.setPivotY(0.0f); 1176 if (animated) { 1177 s.playTogether( 1178 new PropertyAnimator(duration, cl, "translationX", 0.0f), 1179 new PropertyAnimator(duration, cl, "translationY", 0.0f), 1180 new PropertyAnimator(duration, cl, "scaleX", 1.0f), 1181 new PropertyAnimator(duration, cl, "scaleY", 1.0f), 1182 new PropertyAnimator(duration, cl, "dimmedBitmapAlpha", 0.0f)); 1183 } else { 1184 cl.setTranslationX(0.0f); 1185 cl.setTranslationY(0.0f); 1186 cl.setScaleX(1.0f); 1187 cl.setScaleY(1.0f); 1188 cl.setDimmedBitmapAlpha(0.0f); 1189 } 1190 } 1191 s.addListener(mUnshrinkAnimationListener); 1192 s.start(); 1193 } 1194 } 1195 1196 void snapToScreen(int whichScreen) { 1197 snapToScreen(whichScreen, 0, false); 1198 } 1199 1200 private void snapToScreen(int whichScreen, int velocity, boolean settle) { 1201 // if (!mScroller.isFinished()) return; 1202 1203 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); 1204 1205 enableChildrenCache(mCurrentScreen, whichScreen); 1206 1207 mNextScreen = whichScreen; 1208 1209 if (mPreviousIndicator != null) { 1210 mPreviousIndicator.setLevel(mNextScreen); 1211 mNextIndicator.setLevel(mNextScreen); 1212 } 1213 1214 View focusedChild = getFocusedChild(); 1215 if (focusedChild != null && whichScreen != mCurrentScreen && 1216 focusedChild == getChildAt(mCurrentScreen)) { 1217 focusedChild.clearFocus(); 1218 } 1219 1220 final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen)); 1221 final int newX = whichScreen * getWidth(); 1222 final int delta = newX - mScrollX; 1223 int duration = (screenDelta + 1) * 100; 1224 1225 if (!mScroller.isFinished()) { 1226 mScroller.abortAnimation(); 1227 } 1228 1229 if (settle) { 1230 mScrollInterpolator.setDistance(screenDelta); 1231 } else { 1232 mScrollInterpolator.disableSettle(); 1233 } 1234 1235 velocity = Math.abs(velocity); 1236 if (velocity > 0) { 1237 duration += (duration / (velocity / BASELINE_FLING_VELOCITY)) 1238 * FLING_VELOCITY_INFLUENCE; 1239 } else { 1240 duration += 100; 1241 } 1242 1243 awakenScrollBars(duration); 1244 mScroller.startScroll(mScrollX, 0, delta, 0, duration); 1245 invalidate(); 1246 } 1247 1248 void startDrag(CellLayout.CellInfo cellInfo) { 1249 View child = cellInfo.cell; 1250 1251 // Make sure the drag was started by a long press as opposed to a long click. 1252 if (!child.isInTouchMode()) { 1253 return; 1254 } 1255 1256 mDragInfo = cellInfo; 1257 mDragInfo.screen = mCurrentScreen; 1258 1259 CellLayout current = ((CellLayout) getChildAt(mCurrentScreen)); 1260 1261 current.onDragChild(child); 1262 mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE); 1263 invalidate(); 1264 } 1265 1266 @Override 1267 protected Parcelable onSaveInstanceState() { 1268 final SavedState state = new SavedState(super.onSaveInstanceState()); 1269 state.currentScreen = mCurrentScreen; 1270 return state; 1271 } 1272 1273 @Override 1274 protected void onRestoreInstanceState(Parcelable state) { 1275 SavedState savedState = (SavedState) state; 1276 super.onRestoreInstanceState(savedState.getSuperState()); 1277 if (savedState.currentScreen != -1) { 1278 setCurrentScreen(savedState.currentScreen, false); 1279 Launcher.setScreen(mCurrentScreen); 1280 } 1281 } 1282 1283 void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo) { 1284 addApplicationShortcut(info, cellInfo, false); 1285 } 1286 1287 void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo, 1288 boolean insertAtFirst) { 1289 final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen); 1290 final int[] result = new int[2]; 1291 1292 layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result); 1293 onDropExternal(result[0], result[1], info, layout, insertAtFirst); 1294 } 1295 1296 public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, 1297 DragView dragView, Object dragInfo) { 1298 final CellLayout cellLayout = getCurrentDropLayout(); 1299 if (source != this) { 1300 onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout); 1301 } else { 1302 // Move internally 1303 if (mDragInfo != null) { 1304 final View cell = mDragInfo.cell; 1305 int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen; 1306 if (index != mDragInfo.screen) { 1307 final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1308 originalCellLayout.removeView(cell); 1309 addInScreen(cell, index, mDragInfo.cellX, mDragInfo.cellY, 1310 mDragInfo.spanX, mDragInfo.spanY); 1311 } 1312 mTargetCell = estimateDropCell(x - xOffset, y - yOffset, 1313 mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, 1314 mTargetCell); 1315 cellLayout.onDropChild(cell); 1316 1317 // update the item's position after drop 1318 final ItemInfo info = (ItemInfo) cell.getTag(); 1319 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell 1320 .getLayoutParams(); 1321 lp.cellX = mTargetCell[0]; 1322 lp.cellY = mTargetCell[1]; 1323 1324 LauncherModel.moveItemInDatabase(mLauncher, info, 1325 LauncherSettings.Favorites.CONTAINER_DESKTOP, index, 1326 lp.cellX, lp.cellY); 1327 } 1328 } 1329 } 1330 1331 public void onDragEnter(DragSource source, int x, int y, int xOffset, 1332 int yOffset, DragView dragView, Object dragInfo) { 1333 } 1334 1335 public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset, 1336 DragView dragView, Object dragInfo) { 1337 1338 // We may need to delegate the drag to a child view. If a 1x1 item 1339 // would land in a cell occupied by a DragTarget (e.g. a Folder), 1340 // then drag events should be handled by that child. 1341 1342 ItemInfo item = (ItemInfo)dragInfo; 1343 CellLayout currentLayout = getCurrentDropLayout(); 1344 1345 int dragPointX, dragPointY; 1346 if (item.spanX == 1 && item.spanY == 1) { 1347 // For a 1x1, calculate the drop cell exactly as in onDragOver 1348 dragPointX = x - xOffset; 1349 dragPointY = y - yOffset; 1350 } else { 1351 // Otherwise, use the exact drag coordinates 1352 dragPointX = x; 1353 dragPointY = y; 1354 } 1355 1356 // If we are dragging over a cell that contains a DropTarget that will 1357 // accept the drop, delegate to that DropTarget. 1358 final int[] cellXY = mTempCell; 1359 currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY); 1360 View child = currentLayout.getChildAt(cellXY[0], cellXY[1]); 1361 if (child instanceof DropTarget) { 1362 DropTarget target = (DropTarget)child; 1363 if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) { 1364 return target; 1365 } 1366 } 1367 return null; 1368 } 1369 1370 public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, 1371 DragView dragView, Object dragInfo) { 1372 1373 final ItemInfo item = (ItemInfo)dragInfo; 1374 final CellLayout currentLayout = getCurrentDropLayout(); 1375 1376 if (dragInfo instanceof LauncherAppWidgetInfo) { 1377 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo; 1378 1379 if (widgetInfo.spanX == -1) { 1380 // Calculate the grid spans needed to fit this widget 1381 int[] spans = currentLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, null); 1382 item.spanX = spans[0]; 1383 item.spanY = spans[1]; 1384 } 1385 } 1386 if (currentLayout != mDragTargetLayout) { 1387 if (mDragTargetLayout != null) { 1388 mDragTargetLayout.onDragComplete(); 1389 } 1390 mDragTargetLayout = currentLayout; 1391 } 1392 1393 // Find the top left corner of the item 1394 int originX = x - xOffset; 1395 int originY = y - yOffset; 1396 1397 // If not dragging from the Workspace, the size of dragView might not match the cell size 1398 if (!source.equals(this)) { 1399 // Break the drag view up into evenly sized chunks based on its spans 1400 int chunkWidth = dragView.getWidth() / item.spanX; 1401 int chunkHeight = dragView.getHeight() / item.spanY; 1402 1403 // Adjust the origin for a cell centered at the top left chunk 1404 originX += (chunkWidth - currentLayout.getCellWidth()) / 2; 1405 originY += (chunkHeight - currentLayout.getCellHeight()) / 2; 1406 } 1407 1408 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 1409 currentLayout.visualizeDropLocation(child, originX, originY, item.spanX, item.spanY); 1410 } 1411 1412 public void onDragExit(DragSource source, int x, int y, int xOffset, 1413 int yOffset, DragView dragView, Object dragInfo) { 1414 if (mDragTargetLayout != null) { 1415 mDragTargetLayout.onDragComplete(); 1416 mDragTargetLayout = null; 1417 } 1418 } 1419 1420 private void onDropExternal(int x, int y, Object dragInfo, 1421 CellLayout cellLayout) { 1422 onDropExternal(x, y, dragInfo, cellLayout, false); 1423 } 1424 1425 private void onDropExternal(int x, int y, Object dragInfo, 1426 CellLayout cellLayout, boolean insertAtFirst) { 1427 // Drag from somewhere else 1428 ItemInfo info = (ItemInfo) dragInfo; 1429 1430 View view = null; 1431 1432 switch (info.itemType) { 1433 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1434 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1435 if (info.container == NO_ID && info instanceof ApplicationInfo) { 1436 // Came from all apps -- make a copy 1437 info = new ShortcutInfo((ApplicationInfo) info); 1438 } 1439 view = mLauncher.createShortcut(R.layout.application, cellLayout, 1440 (ShortcutInfo) info); 1441 break; 1442 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 1443 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, 1444 (ViewGroup) getChildAt(mCurrentScreen), 1445 ((UserFolderInfo) info)); 1446 break; 1447 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1448 cellLayout.setTagToCellInfoForPoint(x, y); 1449 mLauncher.addAppWidgetFromDrop(((LauncherAppWidgetInfo)dragInfo).providerName, cellLayout.getTag()); 1450 break; 1451 default: 1452 throw new IllegalStateException("Unknown item type: " 1453 + info.itemType); 1454 } 1455 1456 // If the view is null, it has already been added. 1457 if (view == null) { 1458 cellLayout.onDragComplete(); 1459 } else { 1460 mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell); 1461 addInScreen(view, indexOfChild(cellLayout), mTargetCell[0], 1462 mTargetCell[1], info.spanX, info.spanY, insertAtFirst); 1463 cellLayout.onDropChild(view); 1464 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 1465 1466 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 1467 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, 1468 lp.cellX, lp.cellY); 1469 } 1470 } 1471 1472 /** 1473 * Return the current {@link CellLayout}, correctly picking the destination 1474 * screen while a scroll is in progress. 1475 */ 1476 private CellLayout getCurrentDropLayout() { 1477 int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen; 1478 return (CellLayout) getChildAt(index); 1479 } 1480 1481 /** 1482 * {@inheritDoc} 1483 */ 1484 public boolean acceptDrop(DragSource source, int x, int y, 1485 int xOffset, int yOffset, DragView dragView, Object dragInfo) { 1486 final CellLayout layout = getCurrentDropLayout(); 1487 final CellLayout.CellInfo dragCellInfo = mDragInfo; 1488 final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX; 1489 final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY; 1490 1491 final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell; 1492 final CellLayout.CellInfo cellInfo = layout.updateOccupiedCells(null, ignoreView); 1493 1494 if (cellInfo.findCellForSpan(mTempEstimate, spanX, spanY)) { 1495 return true; 1496 } else { 1497 Toast.makeText(getContext(), getContext().getString(R.string.out_of_space), Toast.LENGTH_SHORT).show(); 1498 return false; 1499 } 1500 } 1501 1502 /** 1503 * {@inheritDoc} 1504 */ 1505 public Rect estimateDropLocation(DragSource source, int x, int y, 1506 int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) { 1507 final CellLayout layout = getCurrentDropLayout(); 1508 1509 final CellLayout.CellInfo cellInfo = mDragInfo; 1510 final int spanX = cellInfo == null ? 1 : cellInfo.spanX; 1511 final int spanY = cellInfo == null ? 1 : cellInfo.spanY; 1512 final View ignoreView = cellInfo == null ? null : cellInfo.cell; 1513 1514 final Rect location = recycle != null ? recycle : new Rect(); 1515 1516 // Find drop cell and convert into rectangle 1517 int[] dropCell = estimateDropCell(x - xOffset, y - yOffset, spanX, 1518 spanY, ignoreView, layout, mTempCell); 1519 1520 if (dropCell == null) { 1521 return null; 1522 } 1523 1524 layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate); 1525 location.left = mTempEstimate[0]; 1526 location.top = mTempEstimate[1]; 1527 1528 layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate); 1529 location.right = mTempEstimate[0]; 1530 location.bottom = mTempEstimate[1]; 1531 1532 return location; 1533 } 1534 1535 /** 1536 * Calculate the nearest cell where the given object would be dropped. 1537 */ 1538 private int[] estimateDropCell(int pixelX, int pixelY, 1539 int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) { 1540 1541 final int[] cellXY = mTempCell; 1542 layout.estimateDropCell(pixelX, pixelY, spanX, spanY, cellXY); 1543 layout.cellToPoint(cellXY[0], cellXY[1], mTempEstimate); 1544 1545 final CellLayout.CellInfo cellInfo = layout.updateOccupiedCells(null, ignoreView); 1546 // Find the best target drop location 1547 return layout.findNearestVacantArea(mTempEstimate[0], mTempEstimate[1], spanX, spanY, cellInfo, recycle); 1548 } 1549 1550 /** 1551 * Estimate the size that a child with the given dimensions will take in the current screen. 1552 */ 1553 void estimateChildSize(int minWidth, int minHeight, int[] result) { 1554 ((CellLayout)getChildAt(mCurrentScreen)).estimateChildSize(minWidth, minHeight, result); 1555 } 1556 1557 void setLauncher(Launcher launcher) { 1558 mLauncher = launcher; 1559 } 1560 1561 public void setDragController(DragController dragController) { 1562 mDragController = dragController; 1563 } 1564 1565 public void onDropCompleted(View target, boolean success) { 1566 if (success) { 1567 if (target != this && mDragInfo != null) { 1568 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1569 cellLayout.removeView(mDragInfo.cell); 1570 if (mDragInfo.cell instanceof DropTarget) { 1571 mDragController.removeDropTarget((DropTarget)mDragInfo.cell); 1572 } 1573 // final Object tag = mDragInfo.cell.getTag(); 1574 } 1575 } else { 1576 if (mDragInfo != null) { 1577 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1578 cellLayout.onDropAborted(mDragInfo.cell); 1579 } 1580 } 1581 1582 mDragInfo = null; 1583 } 1584 1585 public void scrollLeft() { 1586 if (mScroller.isFinished()) { 1587 if (mCurrentScreen > 0) 1588 snapToScreen(mCurrentScreen - 1); 1589 } else { 1590 if (mNextScreen > 0) 1591 snapToScreen(mNextScreen - 1); 1592 } 1593 } 1594 1595 public void scrollRight() { 1596 if (mScroller.isFinished()) { 1597 if (mCurrentScreen < getChildCount() - 1) 1598 snapToScreen(mCurrentScreen + 1); 1599 } else { 1600 if (mNextScreen < getChildCount() - 1) 1601 snapToScreen(mNextScreen + 1); 1602 } 1603 } 1604 1605 public int getScreenForView(View v) { 1606 int result = -1; 1607 if (v != null) { 1608 ViewParent vp = v.getParent(); 1609 final int screenCount = getChildCount(); 1610 for (int i = 0; i < screenCount; i++) { 1611 if (vp == getChildAt(i)) { 1612 return i; 1613 } 1614 } 1615 } 1616 return result; 1617 } 1618 1619 public Folder getFolderForTag(Object tag) { 1620 final int screenCount = getChildCount(); 1621 for (int screen = 0; screen < screenCount; screen++) { 1622 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1623 int count = currentScreen.getChildCount(); 1624 for (int i = 0; i < count; i++) { 1625 View child = currentScreen.getChildAt(i); 1626 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 1627 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { 1628 Folder f = (Folder) child; 1629 if (f.getInfo() == tag && f.getInfo().opened) { 1630 return f; 1631 } 1632 } 1633 } 1634 } 1635 return null; 1636 } 1637 1638 public View getViewForTag(Object tag) { 1639 int screenCount = getChildCount(); 1640 for (int screen = 0; screen < screenCount; screen++) { 1641 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1642 int count = currentScreen.getChildCount(); 1643 for (int i = 0; i < count; i++) { 1644 View child = currentScreen.getChildAt(i); 1645 if (child.getTag() == tag) { 1646 return child; 1647 } 1648 } 1649 } 1650 return null; 1651 } 1652 1653 /** 1654 * @return True is long presses are still allowed for the current touch 1655 */ 1656 public boolean allowLongPress() { 1657 return mAllowLongPress; 1658 } 1659 1660 /** 1661 * Set true to allow long-press events to be triggered, usually checked by 1662 * {@link Launcher} to accept or block dpad-initiated long-presses. 1663 */ 1664 public void setAllowLongPress(boolean allowLongPress) { 1665 mAllowLongPress = allowLongPress; 1666 } 1667 1668 void removeItems(final ArrayList<ApplicationInfo> apps) { 1669 final int screenCount = getChildCount(); 1670 final PackageManager manager = getContext().getPackageManager(); 1671 final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); 1672 1673 final HashSet<String> packageNames = new HashSet<String>(); 1674 final int appCount = apps.size(); 1675 for (int i = 0; i < appCount; i++) { 1676 packageNames.add(apps.get(i).componentName.getPackageName()); 1677 } 1678 1679 for (int i = 0; i < screenCount; i++) { 1680 final CellLayout layout = (CellLayout) getChildAt(i); 1681 1682 // Avoid ANRs by treating each screen separately 1683 post(new Runnable() { 1684 public void run() { 1685 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 1686 childrenToRemove.clear(); 1687 1688 int childCount = layout.getChildCount(); 1689 for (int j = 0; j < childCount; j++) { 1690 final View view = layout.getChildAt(j); 1691 Object tag = view.getTag(); 1692 1693 if (tag instanceof ShortcutInfo) { 1694 final ShortcutInfo info = (ShortcutInfo) tag; 1695 final Intent intent = info.intent; 1696 final ComponentName name = intent.getComponent(); 1697 1698 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1699 for (String packageName: packageNames) { 1700 if (packageName.equals(name.getPackageName())) { 1701 // TODO: This should probably be done on a worker thread 1702 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1703 childrenToRemove.add(view); 1704 } 1705 } 1706 } 1707 } else if (tag instanceof UserFolderInfo) { 1708 final UserFolderInfo info = (UserFolderInfo) tag; 1709 final ArrayList<ShortcutInfo> contents = info.contents; 1710 final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1); 1711 final int contentsCount = contents.size(); 1712 boolean removedFromFolder = false; 1713 1714 for (int k = 0; k < contentsCount; k++) { 1715 final ShortcutInfo appInfo = contents.get(k); 1716 final Intent intent = appInfo.intent; 1717 final ComponentName name = intent.getComponent(); 1718 1719 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1720 for (String packageName: packageNames) { 1721 if (packageName.equals(name.getPackageName())) { 1722 toRemove.add(appInfo); 1723 // TODO: This should probably be done on a worker thread 1724 LauncherModel.deleteItemFromDatabase( 1725 mLauncher, appInfo); 1726 removedFromFolder = true; 1727 } 1728 } 1729 } 1730 } 1731 1732 contents.removeAll(toRemove); 1733 if (removedFromFolder) { 1734 final Folder folder = getOpenFolder(); 1735 if (folder != null) 1736 folder.notifyDataSetChanged(); 1737 } 1738 } else if (tag instanceof LiveFolderInfo) { 1739 final LiveFolderInfo info = (LiveFolderInfo) tag; 1740 final Uri uri = info.uri; 1741 final ProviderInfo providerInfo = manager.resolveContentProvider( 1742 uri.getAuthority(), 0); 1743 1744 if (providerInfo != null) { 1745 for (String packageName: packageNames) { 1746 if (packageName.equals(providerInfo.packageName)) { 1747 // TODO: This should probably be done on a worker thread 1748 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1749 childrenToRemove.add(view); 1750 } 1751 } 1752 } 1753 } else if (tag instanceof LauncherAppWidgetInfo) { 1754 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; 1755 final AppWidgetProviderInfo provider = 1756 widgets.getAppWidgetInfo(info.appWidgetId); 1757 if (provider != null) { 1758 for (String packageName: packageNames) { 1759 if (packageName.equals(provider.provider.getPackageName())) { 1760 // TODO: This should probably be done on a worker thread 1761 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1762 childrenToRemove.add(view); 1763 } 1764 } 1765 } 1766 } 1767 } 1768 1769 childCount = childrenToRemove.size(); 1770 for (int j = 0; j < childCount; j++) { 1771 View child = childrenToRemove.get(j); 1772 layout.removeViewInLayout(child); 1773 if (child instanceof DropTarget) { 1774 mDragController.removeDropTarget((DropTarget)child); 1775 } 1776 } 1777 1778 if (childCount > 0) { 1779 layout.requestLayout(); 1780 layout.invalidate(); 1781 } 1782 } 1783 }); 1784 } 1785 } 1786 1787 void updateShortcuts(ArrayList<ApplicationInfo> apps) { 1788 final PackageManager pm = mLauncher.getPackageManager(); 1789 1790 final int screenCount = getChildCount(); 1791 for (int i = 0; i < screenCount; i++) { 1792 final CellLayout layout = (CellLayout) getChildAt(i); 1793 int childCount = layout.getChildCount(); 1794 for (int j = 0; j < childCount; j++) { 1795 final View view = layout.getChildAt(j); 1796 Object tag = view.getTag(); 1797 if (tag instanceof ShortcutInfo) { 1798 ShortcutInfo info = (ShortcutInfo)tag; 1799 // We need to check for ACTION_MAIN otherwise getComponent() might 1800 // return null for some shortcuts (for instance, for shortcuts to 1801 // web pages.) 1802 final Intent intent = info.intent; 1803 final ComponentName name = intent.getComponent(); 1804 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 1805 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1806 final int appCount = apps.size(); 1807 for (int k = 0; k < appCount; k++) { 1808 ApplicationInfo app = apps.get(k); 1809 if (app.componentName.equals(name)) { 1810 info.setIcon(mIconCache.getIcon(info.intent)); 1811 ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null, 1812 new FastBitmapDrawable(info.getIcon(mIconCache)), 1813 null, null); 1814 } 1815 } 1816 } 1817 } 1818 } 1819 } 1820 } 1821 1822 void moveToDefaultScreen(boolean animate) { 1823 if (animate) { 1824 if (mIsSmall) { 1825 unshrink(mDefaultScreen); 1826 } else { 1827 snapToScreen(mDefaultScreen); 1828 } 1829 } else { 1830 setCurrentScreen(mDefaultScreen); 1831 } 1832 getChildAt(mDefaultScreen).requestFocus(); 1833 } 1834 1835 void setIndicators(Drawable previous, Drawable next) { 1836 mPreviousIndicator = previous; 1837 mNextIndicator = next; 1838 previous.setLevel(mCurrentScreen); 1839 next.setLevel(mCurrentScreen); 1840 } 1841 1842 public static class SavedState extends BaseSavedState { 1843 int currentScreen = -1; 1844 1845 SavedState(Parcelable superState) { 1846 super(superState); 1847 } 1848 1849 private SavedState(Parcel in) { 1850 super(in); 1851 currentScreen = in.readInt(); 1852 } 1853 1854 @Override 1855 public void writeToParcel(Parcel out, int flags) { 1856 super.writeToParcel(out, flags); 1857 out.writeInt(currentScreen); 1858 } 1859 1860 public static final Parcelable.Creator<SavedState> CREATOR = 1861 new Parcelable.Creator<SavedState>() { 1862 public SavedState createFromParcel(Parcel in) { 1863 return new SavedState(in); 1864 } 1865 1866 public SavedState[] newArray(int size) { 1867 return new SavedState[size]; 1868 } 1869 }; 1870 } 1871} 1872