Workspace.java revision 96226223d9849842bb2a67af051acbae9e0677d5
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 return true; 473 } 474 return false; 475 } 476 477 /** 478 * Registers the specified listener on each screen contained in this workspace. 479 * 480 * @param l The listener used to respond to long clicks. 481 */ 482 @Override 483 public void setOnLongClickListener(OnLongClickListener l) { 484 mLongClickListener = l; 485 final int screenCount = getChildCount(); 486 for (int i = 0; i < screenCount; i++) { 487 getChildAt(i).setOnLongClickListener(l); 488 } 489 } 490 491 private void updateWallpaperOffset() { 492 updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft)); 493 } 494 495 private void updateWallpaperOffset(int scrollRange) { 496 IBinder token = getWindowToken(); 497 if (token != null) { 498 mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 ); 499 mWallpaperManager.setWallpaperOffsets(getWindowToken(), 500 Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0); 501 } 502 } 503 504 @Override 505 public void scrollTo(int x, int y) { 506 super.scrollTo(x, y); 507 mTouchX = x; 508 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 509 } 510 511 @Override 512 public void computeScroll() { 513 if (mScroller.computeScrollOffset()) { 514 mTouchX = mScrollX = mScroller.getCurrX(); 515 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 516 mScrollY = mScroller.getCurrY(); 517 updateWallpaperOffset(); 518 postInvalidate(); 519 } else if (mNextScreen != INVALID_SCREEN) { 520 mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1)); 521 if (mPreviousIndicator != null) { 522 mPreviousIndicator.setLevel(mCurrentScreen); 523 mNextIndicator.setLevel(mCurrentScreen); 524 } 525 Launcher.setScreen(mCurrentScreen); 526 mNextScreen = INVALID_SCREEN; 527 clearChildrenCache(); 528 } else if (mTouchState == TOUCH_STATE_SCROLLING) { 529 final float now = System.nanoTime() / NANOTIME_DIV; 530 final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT); 531 final float dx = mTouchX - mScrollX; 532 mScrollX += dx * e; 533 mSmoothingTime = now; 534 535 // Keep generating points as long as we're more than 1px away from the target 536 if (dx > 1.f || dx < -1.f) { 537 updateWallpaperOffset(); 538 postInvalidate(); 539 } 540 } 541 } 542 543 @Override 544 protected void dispatchDraw(Canvas canvas) { 545 boolean restore = false; 546 int restoreCount = 0; 547 548 // ViewGroup.dispatchDraw() supports many features we don't need: 549 // clip to padding, layout animation, animation listener, disappearing 550 // children, etc. The following implementation attempts to fast-track 551 // the drawing dispatch by drawing only what we know needs to be drawn. 552 553 boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN; 554 555 // if the screens are all small, we need to draw all the screens since 556 // they're most likely all visible 557 if (mIsSmall) { 558 final int screenCount = getChildCount(); 559 for (int i = 0; i < screenCount; i++) { 560 CellLayout cl = (CellLayout)getChildAt(i); 561 drawChild(canvas, cl, getDrawingTime()); 562 } 563 } else if (fastDraw) { 564 // If we are not scrolling or flinging, draw only the current screen 565 drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime()); 566 } else { 567 final long drawingTime = getDrawingTime(); 568 final float scrollPos = (float) mScrollX / getWidth(); 569 final int leftScreen = (int) scrollPos; 570 final int rightScreen = leftScreen + 1; 571 if (leftScreen >= 0) { 572 drawChild(canvas, getChildAt(leftScreen), drawingTime); 573 } 574 if (scrollPos != leftScreen && rightScreen < getChildCount()) { 575 drawChild(canvas, getChildAt(rightScreen), drawingTime); 576 } 577 } 578 579 if (restore) { 580 canvas.restoreToCount(restoreCount); 581 } 582 } 583 584 protected void onAttachedToWindow() { 585 super.onAttachedToWindow(); 586 computeScroll(); 587 mDragController.setWindowToken(getWindowToken()); 588 } 589 590 @Override 591 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 592 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 593 594 final int width = MeasureSpec.getSize(widthMeasureSpec); 595 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 596 if (widthMode != MeasureSpec.EXACTLY) { 597 throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); 598 } 599 600 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 601 if (heightMode != MeasureSpec.EXACTLY) { 602 throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); 603 } 604 605 // The children are given the same width and height as the workspace 606 final int screenCount = getChildCount(); 607 for (int i = 0; i < screenCount; i++) { 608 getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); 609 } 610 611 if (mFirstLayout) { 612 setHorizontalScrollBarEnabled(false); 613 setCurrentScreen(mCurrentScreen, false, width); 614 setHorizontalScrollBarEnabled(true); 615 } 616 } 617 618 @Override 619 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 620 if (mFirstLayout) { 621 mFirstLayout = false; 622 } 623 int childLeft = 0; 624 final int screenCount = getChildCount(); 625 for (int i = 0; i < screenCount; i++) { 626 final View child = getChildAt(i); 627 if (child.getVisibility() != View.GONE) { 628 final int childWidth = child.getMeasuredWidth(); 629 child.layout(childLeft, 0, 630 childLeft + childWidth, child.getMeasuredHeight()); 631 childLeft += childWidth; 632 } 633 } 634 635 // if shrinkToBottom() is called on initialization, it has to be deferred 636 // until after the first call to onLayout so that it has the correct width 637 if (mWaitingToShrinkToBottom) { 638 shrinkToBottom(false); 639 mWaitingToShrinkToBottom = false; 640 } 641 642 if (LauncherApplication.isInPlaceRotationEnabled()) { 643 // When the device is rotated, the scroll position of the current screen 644 // needs to be refreshed 645 setCurrentScreen(getCurrentScreen()); 646 } 647 } 648 649 @Override 650 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { 651 int screen = indexOfChild(child); 652 if (screen != mCurrentScreen || !mScroller.isFinished()) { 653 if (!mLauncher.isWorkspaceLocked()) { 654 snapToScreen(screen); 655 } 656 return true; 657 } 658 return false; 659 } 660 661 @Override 662 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 663 if (!mLauncher.isAllAppsVisible()) { 664 final Folder openFolder = getOpenFolder(); 665 if (openFolder != null) { 666 return openFolder.requestFocus(direction, previouslyFocusedRect); 667 } else { 668 int focusableScreen; 669 if (mNextScreen != INVALID_SCREEN) { 670 focusableScreen = mNextScreen; 671 } else { 672 focusableScreen = mCurrentScreen; 673 } 674 getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect); 675 } 676 } 677 return false; 678 } 679 680 @Override 681 public boolean dispatchUnhandledMove(View focused, int direction) { 682 if (direction == View.FOCUS_LEFT) { 683 if (getCurrentScreen() > 0) { 684 snapToScreen(getCurrentScreen() - 1); 685 return true; 686 } 687 } else if (direction == View.FOCUS_RIGHT) { 688 if (getCurrentScreen() < getChildCount() - 1) { 689 snapToScreen(getCurrentScreen() + 1); 690 return true; 691 } 692 } 693 return super.dispatchUnhandledMove(focused, direction); 694 } 695 696 @Override 697 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 698 if (!mLauncher.isAllAppsVisible()) { 699 final Folder openFolder = getOpenFolder(); 700 if (openFolder == null) { 701 getChildAt(mCurrentScreen).addFocusables(views, direction); 702 if (direction == View.FOCUS_LEFT) { 703 if (mCurrentScreen > 0) { 704 getChildAt(mCurrentScreen - 1).addFocusables(views, direction); 705 } 706 } else if (direction == View.FOCUS_RIGHT) { 707 if (mCurrentScreen < getChildCount() - 1) { 708 getChildAt(mCurrentScreen + 1).addFocusables(views, direction); 709 } 710 } 711 } else { 712 openFolder.addFocusables(views, direction); 713 } 714 } 715 } 716 717 @Override 718 public boolean dispatchTouchEvent(MotionEvent ev) { 719 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 720 // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps 721 // ie when you click on a mini-screen, it zooms back to that screen) 722 if (mLauncher.isWorkspaceLocked() || 723 (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible())) { 724 return false; 725 } 726 } 727 return super.dispatchTouchEvent(ev); 728 } 729 730 /** 731 * {@inheritDoc} 732 */ 733 @Override 734 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 735 if (disallowIntercept) { 736 // We need to make sure to cancel our long press if 737 // a scrollable widget takes over touch events 738 final View currentScreen = getChildAt(mCurrentScreen); 739 currentScreen.cancelLongPress(); 740 } 741 super.requestDisallowInterceptTouchEvent(disallowIntercept); 742 } 743 744 @Override 745 public boolean onInterceptTouchEvent(MotionEvent ev) { 746 final boolean workspaceLocked = mLauncher.isWorkspaceLocked(); 747 final boolean allAppsVisible = mLauncher.isAllAppsVisible(); 748 749 // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps 750 // ie when you click on a mini-screen, it zooms back to that screen) 751 if (workspaceLocked || (!LauncherApplication.isScreenXLarge() && allAppsVisible)) { 752 return false; // We don't want the events. Let them fall through to the all apps view. 753 } 754 755 /* 756 * This method JUST determines whether we want to intercept the motion. 757 * If we return true, onTouchEvent will be called and we do the actual 758 * scrolling there. 759 */ 760 761 /* 762 * Shortcut the most recurring case: the user is in the dragging 763 * state and he is moving his finger. We want to intercept this 764 * motion. 765 */ 766 final int action = ev.getAction(); 767 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { 768 return true; 769 } 770 771 if (mVelocityTracker == null) { 772 mVelocityTracker = VelocityTracker.obtain(); 773 } 774 mVelocityTracker.addMovement(ev); 775 776 switch (action & MotionEvent.ACTION_MASK) { 777 case MotionEvent.ACTION_MOVE: { 778 /* 779 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 780 * whether the user has moved far enough from his original down touch. 781 */ 782 783 /* 784 * Locally do absolute value. mLastMotionX is set to the y value 785 * of the down event. 786 */ 787 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 788 final float x = ev.getX(pointerIndex); 789 final float y = ev.getY(pointerIndex); 790 final int xDiff = (int) Math.abs(x - mLastMotionX); 791 final int yDiff = (int) Math.abs(y - mLastMotionY); 792 793 final int touchSlop = mTouchSlop; 794 boolean xMoved = xDiff > touchSlop; 795 boolean yMoved = yDiff > touchSlop; 796 797 if (xMoved || yMoved) { 798 799 if (xMoved) { 800 // Scroll if the user moved far enough along the X axis 801 mTouchState = TOUCH_STATE_SCROLLING; 802 mLastMotionX = x; 803 mTouchX = mScrollX; 804 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 805 enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1); 806 } 807 // Either way, cancel any pending longpress 808 if (mAllowLongPress) { 809 mAllowLongPress = false; 810 // Try canceling the long press. It could also have been scheduled 811 // by a distant descendant, so use the mAllowLongPress flag to block 812 // everything 813 final View currentScreen = getChildAt(mCurrentScreen); 814 currentScreen.cancelLongPress(); 815 } 816 } 817 break; 818 } 819 820 case MotionEvent.ACTION_DOWN: { 821 final float x = ev.getX(); 822 final float y = ev.getY(); 823 // Remember location of down touch 824 mLastMotionX = x; 825 mLastMotionY = y; 826 mActivePointerId = ev.getPointerId(0); 827 mAllowLongPress = true; 828 829 /* 830 * If being flinged and user touches the screen, initiate drag; 831 * otherwise don't. mScroller.isFinished should be false when 832 * being flinged. 833 */ 834 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; 835 break; 836 } 837 838 case MotionEvent.ACTION_CANCEL: 839 case MotionEvent.ACTION_UP: 840 841 if (mTouchState != TOUCH_STATE_SCROLLING) { 842 final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen); 843 if (!currentScreen.lastDownOnOccupiedCell()) { 844 getLocationOnScreen(mTempCell); 845 // Send a tap to the wallpaper if the last down was on empty space 846 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 847 mWallpaperManager.sendWallpaperCommand(getWindowToken(), 848 "android.wallpaper.tap", 849 mTempCell[0] + (int) ev.getX(pointerIndex), 850 mTempCell[1] + (int) ev.getY(pointerIndex), 0, null); 851 } 852 } 853 854 // Release the drag 855 clearChildrenCache(); 856 mTouchState = TOUCH_STATE_REST; 857 mActivePointerId = INVALID_POINTER; 858 mAllowLongPress = false; 859 860 if (mVelocityTracker != null) { 861 mVelocityTracker.recycle(); 862 mVelocityTracker = null; 863 } 864 865 break; 866 867 case MotionEvent.ACTION_POINTER_UP: 868 onSecondaryPointerUp(ev); 869 break; 870 } 871 872 /* 873 * The only time we want to intercept motion events is if we are in the 874 * drag mode. 875 */ 876 return mTouchState != TOUCH_STATE_REST; 877 } 878 879 private void onSecondaryPointerUp(MotionEvent ev) { 880 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 881 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 882 final int pointerId = ev.getPointerId(pointerIndex); 883 if (pointerId == mActivePointerId) { 884 // This was our active pointer going up. Choose a new 885 // active pointer and adjust accordingly. 886 // TODO: Make this decision more intelligent. 887 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 888 mLastMotionX = ev.getX(newPointerIndex); 889 mLastMotionY = ev.getY(newPointerIndex); 890 mActivePointerId = ev.getPointerId(newPointerIndex); 891 if (mVelocityTracker != null) { 892 mVelocityTracker.clear(); 893 } 894 } 895 } 896 897 /** 898 * If one of our descendant views decides that it could be focused now, only 899 * pass that along if it's on the current screen. 900 * 901 * This happens when live folders requery, and if they're off screen, they 902 * end up calling requestFocus, which pulls it on screen. 903 */ 904 @Override 905 public void focusableViewAvailable(View focused) { 906 View current = getChildAt(mCurrentScreen); 907 View v = focused; 908 while (true) { 909 if (v == current) { 910 super.focusableViewAvailable(focused); 911 return; 912 } 913 if (v == this) { 914 return; 915 } 916 ViewParent parent = v.getParent(); 917 if (parent instanceof View) { 918 v = (View) v.getParent(); 919 } else { 920 return; 921 } 922 } 923 } 924 925 void enableChildrenCache(int fromScreen, int toScreen) { 926 if (fromScreen > toScreen) { 927 final int temp = fromScreen; 928 fromScreen = toScreen; 929 toScreen = temp; 930 } 931 932 final int screenCount = getChildCount(); 933 934 fromScreen = Math.max(fromScreen, 0); 935 toScreen = Math.min(toScreen, screenCount - 1); 936 937 for (int i = fromScreen; i <= toScreen; i++) { 938 final CellLayout layout = (CellLayout) getChildAt(i); 939 layout.setChildrenDrawnWithCacheEnabled(true); 940 layout.setChildrenDrawingCacheEnabled(true); 941 } 942 } 943 944 void clearChildrenCache() { 945 final int screenCount = getChildCount(); 946 for (int i = 0; i < screenCount; i++) { 947 final CellLayout layout = (CellLayout) getChildAt(i); 948 layout.setChildrenDrawnWithCacheEnabled(false); 949 } 950 } 951 952 @Override 953 public boolean onTouchEvent(MotionEvent ev) { 954 955 if (mLauncher.isWorkspaceLocked()) { 956 return false; // We don't want the events. Let them fall through to the all apps view. 957 } 958 if (mLauncher.isAllAppsVisible()) { 959 // Cancel any scrolling that is in progress. 960 if (!mScroller.isFinished()) { 961 mScroller.abortAnimation(); 962 } 963 snapToScreen(mCurrentScreen); 964 return false; // We don't want the events. Let them fall through to the all apps view. 965 } 966 967 if (mVelocityTracker == null) { 968 mVelocityTracker = VelocityTracker.obtain(); 969 } 970 mVelocityTracker.addMovement(ev); 971 972 final int action = ev.getAction(); 973 974 switch (action & MotionEvent.ACTION_MASK) { 975 case MotionEvent.ACTION_DOWN: 976 /* 977 * If being flinged and user touches, stop the fling. isFinished 978 * will be false if being flinged. 979 */ 980 if (!mScroller.isFinished()) { 981 mScroller.abortAnimation(); 982 } 983 984 // Remember where the motion event started 985 mLastMotionX = ev.getX(); 986 mActivePointerId = ev.getPointerId(0); 987 if (mTouchState == TOUCH_STATE_SCROLLING) { 988 enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1); 989 } 990 break; 991 case MotionEvent.ACTION_MOVE: 992 if (mTouchState == TOUCH_STATE_SCROLLING) { 993 // Scroll to follow the motion event 994 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 995 final float x = ev.getX(pointerIndex); 996 final float deltaX = mLastMotionX - x; 997 mLastMotionX = x; 998 999 if (deltaX < 0) { 1000 if (mTouchX > 0) { 1001 mTouchX += Math.max(-mTouchX, deltaX); 1002 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 1003 invalidate(); 1004 } 1005 } else if (deltaX > 0) { 1006 final float availableToScroll = getChildAt(getChildCount() - 1).getRight() - 1007 mTouchX - getWidth(); 1008 if (availableToScroll > 0) { 1009 mTouchX += Math.min(availableToScroll, deltaX); 1010 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 1011 invalidate(); 1012 } 1013 } else { 1014 awakenScrollBars(); 1015 } 1016 } 1017 break; 1018 case MotionEvent.ACTION_UP: 1019 if (mTouchState == TOUCH_STATE_SCROLLING) { 1020 final VelocityTracker velocityTracker = mVelocityTracker; 1021 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1022 final int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId); 1023 1024 final int screenWidth = getWidth(); 1025 final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth; 1026 final float scrolledPos = (float) mScrollX / screenWidth; 1027 1028 if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { 1029 // Fling hard enough to move left. 1030 // Don't fling across more than one screen at a time. 1031 final int bound = scrolledPos < whichScreen ? 1032 mCurrentScreen - 1 : mCurrentScreen; 1033 snapToScreen(Math.min(whichScreen, bound), velocityX, true); 1034 } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) { 1035 // Fling hard enough to move right 1036 // Don't fling across more than one screen at a time. 1037 final int bound = scrolledPos > whichScreen ? 1038 mCurrentScreen + 1 : mCurrentScreen; 1039 snapToScreen(Math.max(whichScreen, bound), velocityX, true); 1040 } else { 1041 snapToScreen(whichScreen, 0, true); 1042 } 1043 1044 if (mVelocityTracker != null) { 1045 mVelocityTracker.recycle(); 1046 mVelocityTracker = null; 1047 } 1048 } 1049 mTouchState = TOUCH_STATE_REST; 1050 mActivePointerId = INVALID_POINTER; 1051 break; 1052 case MotionEvent.ACTION_CANCEL: 1053 mTouchState = TOUCH_STATE_REST; 1054 mActivePointerId = INVALID_POINTER; 1055 break; 1056 case MotionEvent.ACTION_POINTER_UP: 1057 onSecondaryPointerUp(ev); 1058 break; 1059 } 1060 1061 return true; 1062 } 1063 1064 void shrinkToTop() { 1065 shrink(true, true); 1066 } 1067 1068 void shrinkToBottom() { 1069 shrinkToBottom(true); 1070 } 1071 1072 void shrinkToBottom(boolean animated) { 1073 if (mFirstLayout) { 1074 // (mFirstLayout == "first layout has not happened yet") 1075 // if we get a call to shrink() as part of our initialization (for example, if 1076 // Launcher is started in All Apps mode) then we need to wait for a layout call 1077 // to get our width so we can layout the mini-screen views correctly 1078 mWaitingToShrinkToBottom = true; 1079 } else { 1080 shrink(false, animated); 1081 } 1082 } 1083 1084 // we use this to shrink the workspace for the all apps view and the customize view 1085 private void shrink(boolean shrinkToTop, boolean animated) { 1086 mIsSmall = true; 1087 final Resources res = getResources(); 1088 final int screenWidth = getWidth(); 1089 final int screenHeight = getHeight(); 1090 final int scaledScreenWidth = (int) (SHRINK_FACTOR * screenWidth); 1091 final int scaledScreenHeight = (int) (SHRINK_FACTOR * screenHeight); 1092 final float scaledSpacing = res.getDimension(R.dimen.smallScreenSpacing); 1093 1094 final int screenCount = getChildCount(); 1095 float totalWidth = screenCount * scaledScreenWidth + (screenCount - 1) * scaledSpacing; 1096 1097 float newY = getResources().getDimension(R.dimen.smallScreenVerticalMargin); 1098 if (!shrinkToTop) { 1099 newY = screenHeight - newY - scaledScreenHeight; 1100 } 1101 1102 // We animate all the screens to the centered position in workspace 1103 // At the same time, the screens become greyed/dimmed 1104 1105 // newX is initialized to the left-most position of the centered screens 1106 float newX = (mCurrentScreen + 1) * screenWidth - screenWidth / 2 - totalWidth / 2; 1107 Sequencer s = new Sequencer(); 1108 for (int i = 0; i < screenCount; i++) { 1109 CellLayout cl = (CellLayout) getChildAt(i); 1110 cl.setPivotX(0.0f); 1111 cl.setPivotY(0.0f); 1112 if (animated) { 1113 final int duration = res.getInteger(R.integer.config_workspaceShrinkTime); 1114 s.playTogether( 1115 new PropertyAnimator(duration, cl, "x", newX), 1116 new PropertyAnimator(duration, cl, "y", newY), 1117 new PropertyAnimator(duration, cl, "scaleX", SHRINK_FACTOR), 1118 new PropertyAnimator(duration, cl, "scaleY", SHRINK_FACTOR), 1119 new PropertyAnimator(duration, cl, "dimmedBitmapAlpha", 1.0f)); 1120 } else { 1121 cl.setX((int)newX); 1122 cl.setY((int)newY); 1123 cl.setScaleX(SHRINK_FACTOR); 1124 cl.setScaleY(SHRINK_FACTOR); 1125 cl.setDimmedBitmapAlpha(1.0f); 1126 } 1127 // increment newX for the next screen 1128 newX += scaledScreenWidth + scaledSpacing; 1129 cl.setOnInterceptTouchListener(this); 1130 } 1131 setChildrenDrawnWithCacheEnabled(true); 1132 if (animated) s.start(); 1133 } 1134 1135 // We call this when we trigger an unshrink by clicking on the CellLayout cl 1136 private void unshrink(CellLayout clThatWasClicked) { 1137 int newCurrentScreen = mCurrentScreen; 1138 final int screenCount = getChildCount(); 1139 for (int i = 0; i < screenCount; i++) { 1140 if (getChildAt(i) == clThatWasClicked) { 1141 newCurrentScreen = i; 1142 } 1143 } 1144 unshrink(newCurrentScreen); 1145 } 1146 1147 private void unshrink(int newCurrentScreen) { 1148 if (mIsSmall) { 1149 int delta = (newCurrentScreen - mCurrentScreen)*getWidth(); 1150 1151 final int screenCount = getChildCount(); 1152 for (int i = 0; i < screenCount; i++) { 1153 CellLayout cl = (CellLayout) getChildAt(i); 1154 cl.setX(cl.getX() + delta); 1155 } 1156 mScrollX = newCurrentScreen * getWidth(); 1157 1158 unshrink(); 1159 setCurrentScreen(newCurrentScreen); 1160 } 1161 } 1162 1163 void unshrink() { 1164 unshrink(true); 1165 } 1166 1167 void unshrink(boolean animated) { 1168 if (mIsSmall) { 1169 Sequencer s = new Sequencer(); 1170 final int screenCount = getChildCount(); 1171 1172 final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime); 1173 for (int i = 0; i < screenCount; i++) { 1174 final CellLayout cl = (CellLayout)getChildAt(i); 1175 cl.setPivotX(0.0f); 1176 cl.setPivotY(0.0f); 1177 if (animated) { 1178 s.playTogether( 1179 new PropertyAnimator(duration, cl, "translationX", 0.0f), 1180 new PropertyAnimator(duration, cl, "translationY", 0.0f), 1181 new PropertyAnimator(duration, cl, "scaleX", 1.0f), 1182 new PropertyAnimator(duration, cl, "scaleY", 1.0f), 1183 new PropertyAnimator(duration, cl, "dimmedBitmapAlpha", 0.0f)); 1184 } else { 1185 cl.setTranslationX(0.0f); 1186 cl.setTranslationY(0.0f); 1187 cl.setScaleX(1.0f); 1188 cl.setScaleY(1.0f); 1189 cl.setDimmedBitmapAlpha(0.0f); 1190 } 1191 } 1192 s.addListener(mUnshrinkAnimationListener); 1193 s.start(); 1194 } 1195 } 1196 1197 void snapToScreen(int whichScreen) { 1198 snapToScreen(whichScreen, 0, false); 1199 } 1200 1201 private void snapToScreen(int whichScreen, int velocity, boolean settle) { 1202 // if (!mScroller.isFinished()) return; 1203 1204 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); 1205 1206 enableChildrenCache(mCurrentScreen, whichScreen); 1207 1208 mNextScreen = whichScreen; 1209 1210 if (mPreviousIndicator != null) { 1211 mPreviousIndicator.setLevel(mNextScreen); 1212 mNextIndicator.setLevel(mNextScreen); 1213 } 1214 1215 View focusedChild = getFocusedChild(); 1216 if (focusedChild != null && whichScreen != mCurrentScreen && 1217 focusedChild == getChildAt(mCurrentScreen)) { 1218 focusedChild.clearFocus(); 1219 } 1220 1221 final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen)); 1222 final int newX = whichScreen * getWidth(); 1223 final int delta = newX - mScrollX; 1224 int duration = (screenDelta + 1) * 100; 1225 1226 if (!mScroller.isFinished()) { 1227 mScroller.abortAnimation(); 1228 } 1229 1230 if (settle) { 1231 mScrollInterpolator.setDistance(screenDelta); 1232 } else { 1233 mScrollInterpolator.disableSettle(); 1234 } 1235 1236 velocity = Math.abs(velocity); 1237 if (velocity > 0) { 1238 duration += (duration / (velocity / BASELINE_FLING_VELOCITY)) 1239 * FLING_VELOCITY_INFLUENCE; 1240 } else { 1241 duration += 100; 1242 } 1243 1244 awakenScrollBars(duration); 1245 mScroller.startScroll(mScrollX, 0, delta, 0, duration); 1246 invalidate(); 1247 } 1248 1249 void startDrag(CellLayout.CellInfo cellInfo) { 1250 View child = cellInfo.cell; 1251 1252 // Make sure the drag was started by a long press as opposed to a long click. 1253 if (!child.isInTouchMode()) { 1254 return; 1255 } 1256 1257 mDragInfo = cellInfo; 1258 mDragInfo.screen = mCurrentScreen; 1259 1260 CellLayout current = ((CellLayout) getChildAt(mCurrentScreen)); 1261 1262 current.onDragChild(child); 1263 mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE); 1264 invalidate(); 1265 } 1266 1267 @Override 1268 protected Parcelable onSaveInstanceState() { 1269 final SavedState state = new SavedState(super.onSaveInstanceState()); 1270 state.currentScreen = mCurrentScreen; 1271 return state; 1272 } 1273 1274 @Override 1275 protected void onRestoreInstanceState(Parcelable state) { 1276 SavedState savedState = (SavedState) state; 1277 super.onRestoreInstanceState(savedState.getSuperState()); 1278 if (savedState.currentScreen != -1) { 1279 setCurrentScreen(savedState.currentScreen, false); 1280 Launcher.setScreen(mCurrentScreen); 1281 } 1282 } 1283 1284 void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo) { 1285 addApplicationShortcut(info, cellInfo, false); 1286 } 1287 1288 void addApplicationShortcut(ShortcutInfo info, CellLayout.CellInfo cellInfo, 1289 boolean insertAtFirst) { 1290 final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen); 1291 final int[] result = new int[2]; 1292 1293 layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result); 1294 onDropExternal(result[0], result[1], info, layout, insertAtFirst); 1295 } 1296 1297 public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, 1298 DragView dragView, Object dragInfo) { 1299 final CellLayout cellLayout = getCurrentDropLayout(); 1300 if (source != this) { 1301 onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout); 1302 } else { 1303 // Move internally 1304 if (mDragInfo != null) { 1305 final View cell = mDragInfo.cell; 1306 int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen; 1307 if (index != mDragInfo.screen) { 1308 final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1309 originalCellLayout.removeView(cell); 1310 addInScreen(cell, index, mDragInfo.cellX, mDragInfo.cellY, 1311 mDragInfo.spanX, mDragInfo.spanY); 1312 } 1313 mTargetCell = estimateDropCell(x - xOffset, y - yOffset, 1314 mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, 1315 mTargetCell); 1316 cellLayout.onDropChild(cell); 1317 1318 // update the item's position after drop 1319 final ItemInfo info = (ItemInfo) cell.getTag(); 1320 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell 1321 .getLayoutParams(); 1322 lp.cellX = mTargetCell[0]; 1323 lp.cellY = mTargetCell[1]; 1324 1325 LauncherModel.moveItemInDatabase(mLauncher, info, 1326 LauncherSettings.Favorites.CONTAINER_DESKTOP, index, 1327 lp.cellX, lp.cellY); 1328 } 1329 } 1330 } 1331 1332 public void onDragEnter(DragSource source, int x, int y, int xOffset, 1333 int yOffset, DragView dragView, Object dragInfo) { 1334 } 1335 1336 public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset, 1337 DragView dragView, Object dragInfo) { 1338 1339 // We may need to delegate the drag to a child view. If a 1x1 item 1340 // would land in a cell occupied by a DragTarget (e.g. a Folder), 1341 // then drag events should be handled by that child. 1342 1343 ItemInfo item = (ItemInfo)dragInfo; 1344 CellLayout currentLayout = getCurrentDropLayout(); 1345 1346 int dragPointX, dragPointY; 1347 if (item.spanX == 1 && item.spanY == 1) { 1348 // For a 1x1, calculate the drop cell exactly as in onDragOver 1349 dragPointX = x - xOffset; 1350 dragPointY = y - yOffset; 1351 } else { 1352 // Otherwise, use the exact drag coordinates 1353 dragPointX = x; 1354 dragPointY = y; 1355 } 1356 1357 // If we are dragging over a cell that contains a DropTarget that will 1358 // accept the drop, delegate to that DropTarget. 1359 final int[] cellXY = mTempCell; 1360 currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY); 1361 View child = currentLayout.getChildAt(cellXY[0], cellXY[1]); 1362 if (child instanceof DropTarget) { 1363 DropTarget target = (DropTarget)child; 1364 if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) { 1365 return target; 1366 } 1367 } 1368 return null; 1369 } 1370 1371 public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, 1372 DragView dragView, Object dragInfo) { 1373 1374 final ItemInfo item = (ItemInfo)dragInfo; 1375 final CellLayout currentLayout = getCurrentDropLayout(); 1376 1377 if (dragInfo instanceof LauncherAppWidgetInfo) { 1378 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo; 1379 1380 if (widgetInfo.spanX == -1) { 1381 // Calculate the grid spans needed to fit this widget 1382 int[] spans = currentLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, null); 1383 item.spanX = spans[0]; 1384 item.spanY = spans[1]; 1385 } 1386 } 1387 if (currentLayout != mDragTargetLayout) { 1388 if (mDragTargetLayout != null) { 1389 mDragTargetLayout.onDragComplete(); 1390 } 1391 mDragTargetLayout = currentLayout; 1392 } 1393 1394 // Find the top left corner of the item 1395 int originX = x - xOffset; 1396 int originY = y - yOffset; 1397 1398 // If not dragging from the Workspace, the size of dragView might not match the cell size 1399 if (!source.equals(this)) { 1400 // Break the drag view up into evenly sized chunks based on its spans 1401 int chunkWidth = dragView.getWidth() / item.spanX; 1402 int chunkHeight = dragView.getHeight() / item.spanY; 1403 1404 // Adjust the origin for a cell centered at the top left chunk 1405 originX += (chunkWidth - currentLayout.getCellWidth()) / 2; 1406 originY += (chunkHeight - currentLayout.getCellHeight()) / 2; 1407 } 1408 1409 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 1410 currentLayout.visualizeDropLocation(child, originX, originY, item.spanX, item.spanY); 1411 } 1412 1413 public void onDragExit(DragSource source, int x, int y, int xOffset, 1414 int yOffset, DragView dragView, Object dragInfo) { 1415 if (mDragTargetLayout != null) { 1416 mDragTargetLayout.onDragComplete(); 1417 mDragTargetLayout = null; 1418 } 1419 } 1420 1421 private void onDropExternal(int x, int y, Object dragInfo, 1422 CellLayout cellLayout) { 1423 onDropExternal(x, y, dragInfo, cellLayout, false); 1424 } 1425 1426 private void onDropExternal(int x, int y, Object dragInfo, 1427 CellLayout cellLayout, boolean insertAtFirst) { 1428 // Drag from somewhere else 1429 ItemInfo info = (ItemInfo) dragInfo; 1430 1431 View view = null; 1432 1433 switch (info.itemType) { 1434 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1435 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1436 if (info.container == NO_ID && info instanceof ApplicationInfo) { 1437 // Came from all apps -- make a copy 1438 info = new ShortcutInfo((ApplicationInfo) info); 1439 } 1440 view = mLauncher.createShortcut(R.layout.application, cellLayout, 1441 (ShortcutInfo) info); 1442 break; 1443 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 1444 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, 1445 (ViewGroup) getChildAt(mCurrentScreen), 1446 ((UserFolderInfo) info)); 1447 break; 1448 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1449 cellLayout.setTagToCellInfoForPoint(x, y); 1450 mLauncher.addAppWidgetFromDrop(((LauncherAppWidgetInfo)dragInfo).providerName, cellLayout.getTag()); 1451 break; 1452 default: 1453 throw new IllegalStateException("Unknown item type: " 1454 + info.itemType); 1455 } 1456 1457 // If the view is null, it has already been added. 1458 if (view == null) { 1459 cellLayout.onDragComplete(); 1460 } else { 1461 mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell); 1462 addInScreen(view, indexOfChild(cellLayout), mTargetCell[0], 1463 mTargetCell[1], info.spanX, info.spanY, insertAtFirst); 1464 cellLayout.onDropChild(view); 1465 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 1466 1467 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 1468 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, 1469 lp.cellX, lp.cellY); 1470 } 1471 } 1472 1473 /** 1474 * Return the current {@link CellLayout}, correctly picking the destination 1475 * screen while a scroll is in progress. 1476 */ 1477 private CellLayout getCurrentDropLayout() { 1478 int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen; 1479 return (CellLayout) getChildAt(index); 1480 } 1481 1482 /** 1483 * {@inheritDoc} 1484 */ 1485 public boolean acceptDrop(DragSource source, int x, int y, 1486 int xOffset, int yOffset, DragView dragView, Object dragInfo) { 1487 final CellLayout layout = getCurrentDropLayout(); 1488 final CellLayout.CellInfo dragCellInfo = mDragInfo; 1489 final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX; 1490 final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY; 1491 1492 final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell; 1493 final CellLayout.CellInfo cellInfo = layout.updateOccupiedCells(null, ignoreView); 1494 1495 if (cellInfo.findCellForSpan(mTempEstimate, spanX, spanY)) { 1496 return true; 1497 } else { 1498 Toast.makeText(getContext(), getContext().getString(R.string.out_of_space), Toast.LENGTH_SHORT).show(); 1499 return false; 1500 } 1501 } 1502 1503 /** 1504 * {@inheritDoc} 1505 */ 1506 public Rect estimateDropLocation(DragSource source, int x, int y, 1507 int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) { 1508 final CellLayout layout = getCurrentDropLayout(); 1509 1510 final CellLayout.CellInfo cellInfo = mDragInfo; 1511 final int spanX = cellInfo == null ? 1 : cellInfo.spanX; 1512 final int spanY = cellInfo == null ? 1 : cellInfo.spanY; 1513 final View ignoreView = cellInfo == null ? null : cellInfo.cell; 1514 1515 final Rect location = recycle != null ? recycle : new Rect(); 1516 1517 // Find drop cell and convert into rectangle 1518 int[] dropCell = estimateDropCell(x - xOffset, y - yOffset, spanX, 1519 spanY, ignoreView, layout, mTempCell); 1520 1521 if (dropCell == null) { 1522 return null; 1523 } 1524 1525 layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate); 1526 location.left = mTempEstimate[0]; 1527 location.top = mTempEstimate[1]; 1528 1529 layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate); 1530 location.right = mTempEstimate[0]; 1531 location.bottom = mTempEstimate[1]; 1532 1533 return location; 1534 } 1535 1536 /** 1537 * Calculate the nearest cell where the given object would be dropped. 1538 */ 1539 private int[] estimateDropCell(int pixelX, int pixelY, 1540 int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) { 1541 1542 final int[] cellXY = mTempCell; 1543 layout.estimateDropCell(pixelX, pixelY, spanX, spanY, cellXY); 1544 layout.cellToPoint(cellXY[0], cellXY[1], mTempEstimate); 1545 1546 final CellLayout.CellInfo cellInfo = layout.updateOccupiedCells(null, ignoreView); 1547 // Find the best target drop location 1548 return layout.findNearestVacantArea(mTempEstimate[0], mTempEstimate[1], spanX, spanY, cellInfo, recycle); 1549 } 1550 1551 /** 1552 * Estimate the size that a child with the given dimensions will take in the current screen. 1553 */ 1554 void estimateChildSize(int minWidth, int minHeight, int[] result) { 1555 ((CellLayout)getChildAt(mCurrentScreen)).estimateChildSize(minWidth, minHeight, result); 1556 } 1557 1558 void setLauncher(Launcher launcher) { 1559 mLauncher = launcher; 1560 } 1561 1562 public void setDragController(DragController dragController) { 1563 mDragController = dragController; 1564 } 1565 1566 public void onDropCompleted(View target, boolean success) { 1567 if (success) { 1568 if (target != this && mDragInfo != null) { 1569 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1570 cellLayout.removeView(mDragInfo.cell); 1571 if (mDragInfo.cell instanceof DropTarget) { 1572 mDragController.removeDropTarget((DropTarget)mDragInfo.cell); 1573 } 1574 // final Object tag = mDragInfo.cell.getTag(); 1575 } 1576 } else { 1577 if (mDragInfo != null) { 1578 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1579 cellLayout.onDropAborted(mDragInfo.cell); 1580 } 1581 } 1582 1583 mDragInfo = null; 1584 } 1585 1586 public void scrollLeft() { 1587 if (mScroller.isFinished()) { 1588 if (mCurrentScreen > 0) 1589 snapToScreen(mCurrentScreen - 1); 1590 } else { 1591 if (mNextScreen > 0) 1592 snapToScreen(mNextScreen - 1); 1593 } 1594 } 1595 1596 public void scrollRight() { 1597 if (mScroller.isFinished()) { 1598 if (mCurrentScreen < getChildCount() - 1) 1599 snapToScreen(mCurrentScreen + 1); 1600 } else { 1601 if (mNextScreen < getChildCount() - 1) 1602 snapToScreen(mNextScreen + 1); 1603 } 1604 } 1605 1606 public int getScreenForView(View v) { 1607 int result = -1; 1608 if (v != null) { 1609 ViewParent vp = v.getParent(); 1610 final int screenCount = getChildCount(); 1611 for (int i = 0; i < screenCount; i++) { 1612 if (vp == getChildAt(i)) { 1613 return i; 1614 } 1615 } 1616 } 1617 return result; 1618 } 1619 1620 public Folder getFolderForTag(Object tag) { 1621 final int screenCount = getChildCount(); 1622 for (int screen = 0; screen < screenCount; screen++) { 1623 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1624 int count = currentScreen.getChildCount(); 1625 for (int i = 0; i < count; i++) { 1626 View child = currentScreen.getChildAt(i); 1627 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 1628 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { 1629 Folder f = (Folder) child; 1630 if (f.getInfo() == tag && f.getInfo().opened) { 1631 return f; 1632 } 1633 } 1634 } 1635 } 1636 return null; 1637 } 1638 1639 public View getViewForTag(Object tag) { 1640 int screenCount = getChildCount(); 1641 for (int screen = 0; screen < screenCount; screen++) { 1642 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1643 int count = currentScreen.getChildCount(); 1644 for (int i = 0; i < count; i++) { 1645 View child = currentScreen.getChildAt(i); 1646 if (child.getTag() == tag) { 1647 return child; 1648 } 1649 } 1650 } 1651 return null; 1652 } 1653 1654 /** 1655 * @return True is long presses are still allowed for the current touch 1656 */ 1657 public boolean allowLongPress() { 1658 return mAllowLongPress; 1659 } 1660 1661 /** 1662 * Set true to allow long-press events to be triggered, usually checked by 1663 * {@link Launcher} to accept or block dpad-initiated long-presses. 1664 */ 1665 public void setAllowLongPress(boolean allowLongPress) { 1666 mAllowLongPress = allowLongPress; 1667 } 1668 1669 void removeItems(final ArrayList<ApplicationInfo> apps) { 1670 final int screenCount = getChildCount(); 1671 final PackageManager manager = getContext().getPackageManager(); 1672 final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); 1673 1674 final HashSet<String> packageNames = new HashSet<String>(); 1675 final int appCount = apps.size(); 1676 for (int i = 0; i < appCount; i++) { 1677 packageNames.add(apps.get(i).componentName.getPackageName()); 1678 } 1679 1680 for (int i = 0; i < screenCount; i++) { 1681 final CellLayout layout = (CellLayout) getChildAt(i); 1682 1683 // Avoid ANRs by treating each screen separately 1684 post(new Runnable() { 1685 public void run() { 1686 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 1687 childrenToRemove.clear(); 1688 1689 int childCount = layout.getChildCount(); 1690 for (int j = 0; j < childCount; j++) { 1691 final View view = layout.getChildAt(j); 1692 Object tag = view.getTag(); 1693 1694 if (tag instanceof ShortcutInfo) { 1695 final ShortcutInfo info = (ShortcutInfo) tag; 1696 final Intent intent = info.intent; 1697 final ComponentName name = intent.getComponent(); 1698 1699 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1700 for (String packageName: packageNames) { 1701 if (packageName.equals(name.getPackageName())) { 1702 // TODO: This should probably be done on a worker thread 1703 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1704 childrenToRemove.add(view); 1705 } 1706 } 1707 } 1708 } else if (tag instanceof UserFolderInfo) { 1709 final UserFolderInfo info = (UserFolderInfo) tag; 1710 final ArrayList<ShortcutInfo> contents = info.contents; 1711 final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1); 1712 final int contentsCount = contents.size(); 1713 boolean removedFromFolder = false; 1714 1715 for (int k = 0; k < contentsCount; k++) { 1716 final ShortcutInfo appInfo = contents.get(k); 1717 final Intent intent = appInfo.intent; 1718 final ComponentName name = intent.getComponent(); 1719 1720 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1721 for (String packageName: packageNames) { 1722 if (packageName.equals(name.getPackageName())) { 1723 toRemove.add(appInfo); 1724 // TODO: This should probably be done on a worker thread 1725 LauncherModel.deleteItemFromDatabase( 1726 mLauncher, appInfo); 1727 removedFromFolder = true; 1728 } 1729 } 1730 } 1731 } 1732 1733 contents.removeAll(toRemove); 1734 if (removedFromFolder) { 1735 final Folder folder = getOpenFolder(); 1736 if (folder != null) 1737 folder.notifyDataSetChanged(); 1738 } 1739 } else if (tag instanceof LiveFolderInfo) { 1740 final LiveFolderInfo info = (LiveFolderInfo) tag; 1741 final Uri uri = info.uri; 1742 final ProviderInfo providerInfo = manager.resolveContentProvider( 1743 uri.getAuthority(), 0); 1744 1745 if (providerInfo != null) { 1746 for (String packageName: packageNames) { 1747 if (packageName.equals(providerInfo.packageName)) { 1748 // TODO: This should probably be done on a worker thread 1749 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1750 childrenToRemove.add(view); 1751 } 1752 } 1753 } 1754 } else if (tag instanceof LauncherAppWidgetInfo) { 1755 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; 1756 final AppWidgetProviderInfo provider = 1757 widgets.getAppWidgetInfo(info.appWidgetId); 1758 if (provider != null) { 1759 for (String packageName: packageNames) { 1760 if (packageName.equals(provider.provider.getPackageName())) { 1761 // TODO: This should probably be done on a worker thread 1762 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1763 childrenToRemove.add(view); 1764 } 1765 } 1766 } 1767 } 1768 } 1769 1770 childCount = childrenToRemove.size(); 1771 for (int j = 0; j < childCount; j++) { 1772 View child = childrenToRemove.get(j); 1773 layout.removeViewInLayout(child); 1774 if (child instanceof DropTarget) { 1775 mDragController.removeDropTarget((DropTarget)child); 1776 } 1777 } 1778 1779 if (childCount > 0) { 1780 layout.requestLayout(); 1781 layout.invalidate(); 1782 } 1783 } 1784 }); 1785 } 1786 } 1787 1788 void updateShortcuts(ArrayList<ApplicationInfo> apps) { 1789 final PackageManager pm = mLauncher.getPackageManager(); 1790 1791 final int screenCount = getChildCount(); 1792 for (int i = 0; i < screenCount; i++) { 1793 final CellLayout layout = (CellLayout) getChildAt(i); 1794 int childCount = layout.getChildCount(); 1795 for (int j = 0; j < childCount; j++) { 1796 final View view = layout.getChildAt(j); 1797 Object tag = view.getTag(); 1798 if (tag instanceof ShortcutInfo) { 1799 ShortcutInfo info = (ShortcutInfo)tag; 1800 // We need to check for ACTION_MAIN otherwise getComponent() might 1801 // return null for some shortcuts (for instance, for shortcuts to 1802 // web pages.) 1803 final Intent intent = info.intent; 1804 final ComponentName name = intent.getComponent(); 1805 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 1806 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1807 final int appCount = apps.size(); 1808 for (int k = 0; k < appCount; k++) { 1809 ApplicationInfo app = apps.get(k); 1810 if (app.componentName.equals(name)) { 1811 info.setIcon(mIconCache.getIcon(info.intent)); 1812 ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null, 1813 new FastBitmapDrawable(info.getIcon(mIconCache)), 1814 null, null); 1815 } 1816 } 1817 } 1818 } 1819 } 1820 } 1821 } 1822 1823 void moveToDefaultScreen(boolean animate) { 1824 if (animate) { 1825 if (mIsSmall) { 1826 unshrink(mDefaultScreen); 1827 } else { 1828 snapToScreen(mDefaultScreen); 1829 } 1830 } else { 1831 setCurrentScreen(mDefaultScreen); 1832 } 1833 getChildAt(mDefaultScreen).requestFocus(); 1834 } 1835 1836 void setIndicators(Drawable previous, Drawable next) { 1837 mPreviousIndicator = previous; 1838 mNextIndicator = next; 1839 previous.setLevel(mCurrentScreen); 1840 next.setLevel(mCurrentScreen); 1841 } 1842 1843 public static class SavedState extends BaseSavedState { 1844 int currentScreen = -1; 1845 1846 SavedState(Parcelable superState) { 1847 super(superState); 1848 } 1849 1850 private SavedState(Parcel in) { 1851 super(in); 1852 currentScreen = in.readInt(); 1853 } 1854 1855 @Override 1856 public void writeToParcel(Parcel out, int flags) { 1857 super.writeToParcel(out, flags); 1858 out.writeInt(currentScreen); 1859 } 1860 1861 public static final Parcelable.Creator<SavedState> CREATOR = 1862 new Parcelable.Creator<SavedState>() { 1863 public SavedState createFromParcel(Parcel in) { 1864 return new SavedState(in); 1865 } 1866 1867 public SavedState[] newArray(int size) { 1868 return new SavedState[size]; 1869 } 1870 }; 1871 } 1872} 1873