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