Workspace.java revision cd68ff5b88de9b5136ff5a9ef698e4db2fc5db66
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 java.util.ArrayList; 20import java.util.HashSet; 21 22import android.animation.Animator; 23import android.animation.Animator.AnimatorListener; 24import android.animation.AnimatorListenerAdapter; 25import android.animation.AnimatorSet; 26import android.animation.ObjectAnimator; 27import android.animation.PropertyValuesHolder; 28import android.animation.TimeInterpolator; 29import android.animation.ValueAnimator; 30import android.animation.ValueAnimator.AnimatorUpdateListener; 31import android.app.WallpaperManager; 32import android.appwidget.AppWidgetManager; 33import android.appwidget.AppWidgetProviderInfo; 34import android.content.ClipData; 35import android.content.ClipDescription; 36import android.content.ComponentName; 37import android.content.Context; 38import android.content.Intent; 39import android.content.pm.PackageManager; 40import android.content.pm.ProviderInfo; 41import android.content.res.Resources; 42import android.content.res.TypedArray; 43import android.graphics.Bitmap; 44import android.graphics.Canvas; 45import android.graphics.Matrix; 46import android.graphics.Paint; 47import android.graphics.Rect; 48import android.graphics.RectF; 49import android.graphics.Region.Op; 50import android.graphics.drawable.Drawable; 51import android.net.Uri; 52import android.os.IBinder; 53import android.os.Parcelable; 54import android.util.AttributeSet; 55import android.util.Log; 56import android.view.DragEvent; 57import android.view.MotionEvent; 58import android.view.View; 59import android.view.animation.DecelerateInterpolator; 60import android.widget.TextView; 61import android.widget.Toast; 62 63import com.android.launcher.R; 64 65/** 66 * The workspace is a wide area with a wallpaper and a finite number of pages. 67 * Each page contains a number of icons, folders or widgets the user can 68 * interact with. A workspace is meant to be used with a fixed width only. 69 */ 70public class Workspace extends SmoothPagedView 71 implements DropTarget, DragSource, DragScroller, View.OnTouchListener { 72 @SuppressWarnings({"UnusedDeclaration"}) 73 private static final String TAG = "Launcher.Workspace"; 74 75 // This is how much the workspace shrinks when we enter all apps or 76 // customization mode 77 private static final float SHRINK_FACTOR = 0.16f; 78 79 // Y rotation to apply to the workspace screens 80 private static final float WORKSPACE_ROTATION = 12.5f; 81 82 // These are extra scale factors to apply to the mini home screens 83 // so as to achieve the desired transform 84 private static final float EXTRA_SCALE_FACTOR_0 = 0.972f; 85 private static final float EXTRA_SCALE_FACTOR_1 = 1.0f; 86 private static final float EXTRA_SCALE_FACTOR_2 = 1.10f; 87 88 private static final int BACKGROUND_FADE_OUT_DELAY = 300; 89 private static final int BACKGROUND_FADE_OUT_DURATION = 300; 90 private static final int BACKGROUND_FADE_IN_DURATION = 100; 91 92 // These animators are used to fade the background 93 private ObjectAnimator mBackgroundFadeInAnimation; 94 private ObjectAnimator mBackgroundFadeOutAnimation; 95 private float mBackgroundAlpha = 0; 96 97 private final WallpaperManager mWallpaperManager; 98 99 private int mDefaultPage; 100 101 private boolean mPageMoving = false; 102 103 /** 104 * CellInfo for the cell that is currently being dragged 105 */ 106 private CellLayout.CellInfo mDragInfo; 107 108 /** 109 * Target drop area calculated during last acceptDrop call. 110 */ 111 private int[] mTargetCell = null; 112 113 /** 114 * The CellLayout that is currently being dragged over 115 */ 116 private CellLayout mDragTargetLayout = null; 117 118 private Launcher mLauncher; 119 private IconCache mIconCache; 120 private DragController mDragController; 121 122 // These are temporary variables to prevent having to allocate a new object just to 123 // return an (x, y) value from helper functions. Do NOT use them to maintain other state. 124 private int[] mTempCell = new int[2]; 125 private int[] mTempEstimate = new int[2]; 126 private float[] mTempOriginXY = new float[2]; 127 private float[] mTempDragCoordinates = new float[2]; 128 private float[] mTempTouchCoordinates = new float[2]; 129 private float[] mTempCellLayoutCenterCoordinates = new float[2]; 130 private float[] mTempDragBottomRightCoordinates = new float[2]; 131 private Matrix mTempInverseMatrix = new Matrix(); 132 133 private static final int DEFAULT_CELL_COUNT_X = 4; 134 private static final int DEFAULT_CELL_COUNT_Y = 4; 135 136 private Drawable mPreviousIndicator; 137 private Drawable mNextIndicator; 138 139 // State variable that indicates whether the pages are small (ie when you're 140 // in all apps or customize mode) 141 private boolean mIsSmall = false; 142 private boolean mIsInUnshrinkAnimation = false; 143 private AnimatorListener mUnshrinkAnimationListener; 144 private enum ShrinkPosition { 145 SHRINK_TO_TOP, SHRINK_TO_MIDDLE, SHRINK_TO_BOTTOM_HIDDEN, SHRINK_TO_BOTTOM_VISIBLE }; 146 private ShrinkPosition mShrunkenState; 147 private boolean mWaitingToShrink = false; 148 private ShrinkPosition mWaitingToShrinkPosition; 149 150 private boolean mInScrollArea = false; 151 private boolean mInDragMode = false; 152 153 private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper(); 154 private Bitmap mDragOutline = null; 155 private final Rect mTempRect = new Rect(); 156 private final int[] mTempXY = new int[2]; 157 158 private ValueAnimator mDropAnim = null; 159 private TimeInterpolator mQuintEaseOutInterpolator = new DecelerateInterpolator(2.5f); 160 private View mDropView = null; 161 private int[] mDropViewPos = new int[] { -1, -1 }; 162 163 // Paint used to draw external drop outline 164 private final Paint mExternalDragOutlinePaint = new Paint(); 165 166 /** 167 * Used to inflate the Workspace from XML. 168 * 169 * @param context The application's context. 170 * @param attrs The attributes set containing the Workspace's customization values. 171 */ 172 public Workspace(Context context, AttributeSet attrs) { 173 this(context, attrs, 0); 174 } 175 176 /** 177 * Used to inflate the Workspace from XML. 178 * 179 * @param context The application's context. 180 * @param attrs The attributes set containing the Workspace's customization values. 181 * @param defStyle Unused. 182 */ 183 public Workspace(Context context, AttributeSet attrs, int defStyle) { 184 super(context, attrs, defStyle); 185 mContentIsRefreshable = false; 186 187 if (!LauncherApplication.isScreenXLarge()) { 188 mFadeInAdjacentScreens = false; 189 } 190 191 mWallpaperManager = WallpaperManager.getInstance(context); 192 193 TypedArray a = context.obtainStyledAttributes(attrs, 194 R.styleable.Workspace, defStyle, 0); 195 int cellCountX = a.getInt(R.styleable.Workspace_cellCountX, DEFAULT_CELL_COUNT_X); 196 int cellCountY = a.getInt(R.styleable.Workspace_cellCountY, DEFAULT_CELL_COUNT_Y); 197 mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1); 198 a.recycle(); 199 200 LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY); 201 setHapticFeedbackEnabled(false); 202 203 initWorkspace(); 204 } 205 206 /** 207 * Initializes various states for this workspace. 208 */ 209 protected void initWorkspace() { 210 Context context = getContext(); 211 mCurrentPage = mDefaultPage; 212 Launcher.setScreen(mCurrentPage); 213 LauncherApplication app = (LauncherApplication)context.getApplicationContext(); 214 mIconCache = app.getIconCache(); 215 mExternalDragOutlinePaint.setAntiAlias(true); 216 setWillNotDraw(false); 217 218 mUnshrinkAnimationListener = new AnimatorListenerAdapter() { 219 public void onAnimationStart(Animator animation) { 220 mIsInUnshrinkAnimation = true; 221 } 222 public void onAnimationEnd(Animator animation) { 223 mIsInUnshrinkAnimation = false; 224 } 225 }; 226 mSnapVelocity = 600; 227 } 228 229 @Override 230 protected int getScrollMode() { 231 if (LauncherApplication.isScreenXLarge()) { 232 return SmoothPagedView.QUINTIC_MODE; 233 } else { 234 return SmoothPagedView.OVERSHOOT_MODE; 235 } 236 } 237 238 @Override 239 public void addView(View child, int index, LayoutParams params) { 240 if (!(child instanceof CellLayout)) { 241 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 242 } 243 ((CellLayout) child).setOnInterceptTouchListener(this); 244 super.addView(child, index, params); 245 } 246 247 @Override 248 public void addView(View child) { 249 if (!(child instanceof CellLayout)) { 250 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 251 } 252 ((CellLayout) child).setOnInterceptTouchListener(this); 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 ((CellLayout) child).setOnInterceptTouchListener(this); 262 super.addView(child, index); 263 } 264 265 @Override 266 public void addView(View child, int width, int height) { 267 if (!(child instanceof CellLayout)) { 268 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 269 } 270 ((CellLayout) child).setOnInterceptTouchListener(this); 271 super.addView(child, width, height); 272 } 273 274 @Override 275 public void addView(View child, LayoutParams params) { 276 if (!(child instanceof CellLayout)) { 277 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 278 } 279 ((CellLayout) child).setOnInterceptTouchListener(this); 280 super.addView(child, params); 281 } 282 283 /** 284 * @return The open folder on the current screen, or null if there is none 285 */ 286 Folder getOpenFolder() { 287 CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); 288 int count = currentPage.getChildCount(); 289 for (int i = 0; i < count; i++) { 290 View child = currentPage.getChildAt(i); 291 if (child instanceof Folder) { 292 Folder folder = (Folder) child; 293 if (folder.getInfo().opened) 294 return folder; 295 } 296 } 297 return null; 298 } 299 300 ArrayList<Folder> getOpenFolders() { 301 final int screenCount = getChildCount(); 302 ArrayList<Folder> folders = new ArrayList<Folder>(screenCount); 303 304 for (int screen = 0; screen < screenCount; screen++) { 305 CellLayout currentPage = (CellLayout) getChildAt(screen); 306 int count = currentPage.getChildCount(); 307 for (int i = 0; i < count; i++) { 308 View child = currentPage.getChildAt(i); 309 if (child instanceof Folder) { 310 Folder folder = (Folder) child; 311 if (folder.getInfo().opened) 312 folders.add(folder); 313 break; 314 } 315 } 316 } 317 return folders; 318 } 319 320 boolean isDefaultPageShowing() { 321 return mCurrentPage == mDefaultPage; 322 } 323 324 /** 325 * Sets the current screen. 326 * 327 * @param currentPage 328 */ 329 @Override 330 void setCurrentPage(int currentPage) { 331 super.setCurrentPage(currentPage); 332 updateWallpaperOffset(mScrollX); 333 } 334 335 /** 336 * Adds the specified child in the specified screen. The position and dimension of 337 * the child are defined by x, y, spanX and spanY. 338 * 339 * @param child The child to add in one of the workspace's screens. 340 * @param screen The screen in which to add the child. 341 * @param x The X position of the child in the screen's grid. 342 * @param y The Y position of the child in the screen's grid. 343 * @param spanX The number of cells spanned horizontally by the child. 344 * @param spanY The number of cells spanned vertically by the child. 345 */ 346 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) { 347 addInScreen(child, screen, x, y, spanX, spanY, false); 348 } 349 350 void addInFullScreen(View child, int screen) { 351 addInScreen(child, screen, 0, 0, -1, -1); 352 } 353 354 /** 355 * Adds the specified child in the specified screen. The position and dimension of 356 * the child are defined by x, y, spanX and spanY. 357 * 358 * @param child The child to add in one of the workspace's screens. 359 * @param screen The screen in which to add the child. 360 * @param x The X position of the child in the screen's grid. 361 * @param y The Y position of the child in the screen's grid. 362 * @param spanX The number of cells spanned horizontally by the child. 363 * @param spanY The number of cells spanned vertically by the child. 364 * @param insert When true, the child is inserted at the beginning of the children list. 365 */ 366 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) { 367 if (screen < 0 || screen >= getChildCount()) { 368 Log.e(TAG, "The screen must be >= 0 and < " + getChildCount() 369 + " (was " + screen + "); skipping child"); 370 return; 371 } 372 373 final CellLayout group = (CellLayout) getChildAt(screen); 374 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 375 if (lp == null) { 376 lp = new CellLayout.LayoutParams(x, y, spanX, spanY); 377 } else { 378 lp.cellX = x; 379 lp.cellY = y; 380 lp.cellHSpan = spanX; 381 lp.cellVSpan = spanY; 382 } 383 384 // Get the canonical child id to uniquely represent this view in this screen 385 int childId = LauncherModel.getCellLayoutChildId(-1, screen, x, y, spanX, spanY); 386 boolean markCellsAsOccupied = !(child instanceof Folder); 387 if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) { 388 // TODO: This branch occurs when the workspace is adding views 389 // outside of the defined grid 390 // maybe we should be deleting these items from the LauncherModel? 391 Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout"); 392 } 393 394 if (!(child instanceof Folder)) { 395 child.setHapticFeedbackEnabled(false); 396 child.setOnLongClickListener(mLongClickListener); 397 } 398 if (child instanceof DropTarget) { 399 mDragController.addDropTarget((DropTarget) child); 400 } 401 } 402 403 public boolean onTouch(View v, MotionEvent event) { 404 // this is an intercepted event being forwarded from a cell layout 405 if (mIsSmall || mIsInUnshrinkAnimation) { 406 mLauncher.onWorkspaceClick((CellLayout) v); 407 return true; 408 } else if (!mPageMoving) { 409 if (v == getChildAt(mCurrentPage - 1)) { 410 snapToPage(mCurrentPage - 1); 411 return true; 412 } else if (v == getChildAt(mCurrentPage + 1)) { 413 snapToPage(mCurrentPage + 1); 414 return true; 415 } 416 } 417 return false; 418 } 419 420 @Override 421 public boolean dispatchUnhandledMove(View focused, int direction) { 422 if (mIsSmall || mIsInUnshrinkAnimation) { 423 // when the home screens are shrunken, shouldn't allow side-scrolling 424 return false; 425 } 426 return super.dispatchUnhandledMove(focused, direction); 427 } 428 429 @Override 430 public boolean onInterceptTouchEvent(MotionEvent ev) { 431 if (mIsSmall || mIsInUnshrinkAnimation) { 432 // when the home screens are shrunken, shouldn't allow side-scrolling 433 return false; 434 } 435 return super.onInterceptTouchEvent(ev); 436 } 437 438 @Override 439 protected void determineScrollingStart(MotionEvent ev) { 440 if (!mIsSmall && !mIsInUnshrinkAnimation) super.determineScrollingStart(ev); 441 } 442 443 protected void onPageBeginMoving() { 444 if (mNextPage != INVALID_PAGE) { 445 // we're snapping to a particular screen 446 enableChildrenCache(mCurrentPage, mNextPage); 447 } else { 448 // this is when user is actively dragging a particular screen, they might 449 // swipe it either left or right (but we won't advance by more than one screen) 450 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1); 451 } 452 showOutlines(); 453 mPageMoving = true; 454 } 455 456 protected void onPageEndMoving() { 457 clearChildrenCache(); 458 // Hide the outlines, as long as we're not dragging 459 if (!mDragController.dragging()) { 460 hideOutlines(); 461 } 462 mPageMoving = false; 463 } 464 465 @Override 466 protected void notifyPageSwitchListener() { 467 super.notifyPageSwitchListener(); 468 469 if (mPreviousIndicator != null) { 470 // if we know the next page, we show the indication for it right away; it looks 471 // weird if the indicators are lagging 472 int page = mNextPage; 473 if (page == INVALID_PAGE) { 474 page = mCurrentPage; 475 } 476 mPreviousIndicator.setLevel(page); 477 mNextIndicator.setLevel(page); 478 } 479 Launcher.setScreen(mCurrentPage); 480 }; 481 482 private void updateWallpaperOffset() { 483 updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft)); 484 } 485 486 private void updateWallpaperOffset(int scrollRange) { 487 final boolean isStaticWallpaper = (mWallpaperManager != null) && 488 (mWallpaperManager.getWallpaperInfo() == null); 489 if (LauncherApplication.isScreenXLarge() && !isStaticWallpaper) { 490 IBinder token = getWindowToken(); 491 if (token != null) { 492 mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 ); 493 mWallpaperManager.setWallpaperOffsets(getWindowToken(), 494 Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0); 495 } 496 } 497 } 498 499 public void showOutlines() { 500 if (!mIsSmall && !mIsInUnshrinkAnimation) { 501 if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel(); 502 if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel(); 503 mBackgroundFadeInAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 1.0f); 504 mBackgroundFadeInAnimation.setDuration(BACKGROUND_FADE_IN_DURATION); 505 mBackgroundFadeInAnimation.start(); 506 } 507 } 508 509 public void hideOutlines() { 510 if (!mIsSmall && !mIsInUnshrinkAnimation) { 511 if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel(); 512 if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel(); 513 mBackgroundFadeOutAnimation = ObjectAnimator.ofFloat(this, "backgroundAlpha", 0.0f); 514 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION); 515 mBackgroundFadeOutAnimation.setStartDelay(BACKGROUND_FADE_OUT_DELAY); 516 mBackgroundFadeOutAnimation.start(); 517 } 518 } 519 520 public void setBackgroundAlpha(float alpha) { 521 mBackgroundAlpha = alpha; 522 for (int i = 0; i < getChildCount(); i++) { 523 CellLayout cl = (CellLayout) getChildAt(i); 524 cl.setBackgroundAlpha(alpha); 525 } 526 } 527 528 public float getBackgroundAlpha() { 529 return mBackgroundAlpha; 530 } 531 532 @Override 533 protected void screenScrolled(int screenCenter) { 534 final int halfScreenSize = getMeasuredWidth() / 2; 535 for (int i = 0; i < getChildCount(); i++) { 536 CellLayout cl = (CellLayout) getChildAt(i); 537 if (cl != null) { 538 int totalDistance = cl.getMeasuredWidth() + mPageSpacing; 539 int delta = screenCenter - (getChildOffset(i) - 540 getRelativeChildOffset(i) + halfScreenSize); 541 542 float scrollProgress = delta/(totalDistance*1.0f); 543 scrollProgress = Math.min(scrollProgress, 1.0f); 544 scrollProgress = Math.max(scrollProgress, -1.0f); 545 546 float mult = mInDragMode ? 1.0f : Math.abs(scrollProgress); 547 cl.setBackgroundAlphaMultiplier(mult); 548 549 float rotation = WORKSPACE_ROTATION * scrollProgress; 550 cl.setRotationY(rotation); 551 } 552 } 553 } 554 555 protected void onAttachedToWindow() { 556 super.onAttachedToWindow(); 557 computeScroll(); 558 mDragController.setWindowToken(getWindowToken()); 559 } 560 561 @Override 562 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 563 super.onLayout(changed, left, top, right, bottom); 564 565 // if shrinkToBottom() is called on initialization, it has to be deferred 566 // until after the first call to onLayout so that it has the correct width 567 if (mWaitingToShrink) { 568 shrink(mWaitingToShrinkPosition, false); 569 mWaitingToShrink = false; 570 } 571 572 if (LauncherApplication.isInPlaceRotationEnabled()) { 573 // When the device is rotated, the scroll position of the current screen 574 // needs to be refreshed 575 setCurrentPage(getCurrentPage()); 576 } 577 } 578 579 @Override 580 protected void dispatchDraw(Canvas canvas) { 581 if (mIsSmall || mIsInUnshrinkAnimation) { 582 // Draw all the workspaces if we're small 583 final int pageCount = getChildCount(); 584 final long drawingTime = getDrawingTime(); 585 for (int i = 0; i < pageCount; i++) { 586 final View page = (View) getChildAt(i); 587 588 drawChild(canvas, page, drawingTime); 589 } 590 } else { 591 super.dispatchDraw(canvas); 592 593 if (mDropView != null) { 594 canvas.save(Canvas.MATRIX_SAVE_FLAG); 595 final int xPos = mDropViewPos[0] - mDropView.getScrollX(); 596 final int yPos = mDropViewPos[1] - mDropView.getScrollY(); 597 canvas.translate(xPos, yPos); 598 mDropView.draw(canvas); 599 canvas.restore(); 600 } 601 } 602 } 603 604 @Override 605 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 606 if (!mLauncher.isAllAppsVisible()) { 607 final Folder openFolder = getOpenFolder(); 608 if (openFolder != null) { 609 return openFolder.requestFocus(direction, previouslyFocusedRect); 610 } else { 611 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 612 } 613 } 614 return false; 615 } 616 617 @Override 618 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 619 if (!mLauncher.isAllAppsVisible()) { 620 final Folder openFolder = getOpenFolder(); 621 if (openFolder != null) { 622 openFolder.addFocusables(views, direction); 623 } else { 624 super.addFocusables(views, direction, focusableMode); 625 } 626 } 627 } 628 629 @Override 630 public boolean dispatchTouchEvent(MotionEvent ev) { 631 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 632 // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps 633 // ie when you click on a mini-screen, it zooms back to that screen) 634 if (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible()) { 635 return false; 636 } 637 } 638 return super.dispatchTouchEvent(ev); 639 } 640 641 void enableChildrenCache(int fromPage, int toPage) { 642 if (fromPage > toPage) { 643 final int temp = fromPage; 644 fromPage = toPage; 645 toPage = temp; 646 } 647 648 final int screenCount = getChildCount(); 649 650 fromPage = Math.max(fromPage, 0); 651 toPage = Math.min(toPage, screenCount - 1); 652 653 for (int i = fromPage; i <= toPage; i++) { 654 final CellLayout layout = (CellLayout) getChildAt(i); 655 layout.setChildrenDrawnWithCacheEnabled(true); 656 layout.setChildrenDrawingCacheEnabled(true); 657 } 658 } 659 660 void clearChildrenCache() { 661 final int screenCount = getChildCount(); 662 for (int i = 0; i < screenCount; i++) { 663 final CellLayout layout = (CellLayout) getChildAt(i); 664 layout.setChildrenDrawnWithCacheEnabled(false); 665 } 666 } 667 668 @Override 669 public boolean onTouchEvent(MotionEvent ev) { 670 if (mLauncher.isAllAppsVisible()) { 671 // Cancel any scrolling that is in progress. 672 if (!mScroller.isFinished()) { 673 mScroller.abortAnimation(); 674 } 675 setCurrentPage(mCurrentPage); 676 return false; // We don't want the events. Let them fall through to the all apps view. 677 } 678 679 return super.onTouchEvent(ev); 680 } 681 682 public boolean isSmall() { 683 return mIsSmall; 684 } 685 686 void shrinkToTop(boolean animated) { 687 shrink(ShrinkPosition.SHRINK_TO_TOP, animated); 688 } 689 690 void shrinkToMiddle() { 691 shrink(ShrinkPosition.SHRINK_TO_MIDDLE, true); 692 } 693 694 void shrinkToBottom() { 695 shrinkToBottom(true); 696 } 697 698 void shrinkToBottom(boolean animated) { 699 shrink(ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN, animated); 700 } 701 702 private float getYScaleForScreen(int screen) { 703 int x = Math.abs(screen - 2); 704 705 // TODO: This should be generalized for use with arbitrary rotation angles. 706 switch(x) { 707 case 0: return EXTRA_SCALE_FACTOR_0; 708 case 1: return EXTRA_SCALE_FACTOR_1; 709 case 2: return EXTRA_SCALE_FACTOR_2; 710 } 711 return 1.0f; 712 } 713 714 // we use this to shrink the workspace for the all apps view and the customize view 715 private void shrink(ShrinkPosition shrinkPosition, boolean animated) { 716 if (mFirstLayout) { 717 // (mFirstLayout == "first layout has not happened yet") 718 // if we get a call to shrink() as part of our initialization (for example, if 719 // Launcher is started in All Apps mode) then we need to wait for a layout call 720 // to get our width so we can layout the mini-screen views correctly 721 mWaitingToShrink = true; 722 mWaitingToShrinkPosition = shrinkPosition; 723 return; 724 } 725 mIsSmall = true; 726 mShrunkenState = shrinkPosition; 727 728 // Stop any scrolling, move to the current page right away 729 setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage); 730 updateWhichPagesAcceptDrops(mShrunkenState); 731 732 // we intercept and reject all touch events when we're small, so be sure to reset the state 733 mTouchState = TOUCH_STATE_REST; 734 mActivePointerId = INVALID_POINTER; 735 736 CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); 737 currentPage.setBackgroundAlphaMultiplier(1.0f); 738 739 final Resources res = getResources(); 740 final int screenWidth = getWidth(); 741 final int screenHeight = getHeight(); 742 743 // Making the assumption that all pages have the same width as the 0th 744 final int pageWidth = getChildAt(0).getMeasuredWidth(); 745 final int pageHeight = getChildAt(0).getMeasuredHeight(); 746 747 final int scaledPageWidth = (int) (SHRINK_FACTOR * pageWidth); 748 final int scaledPageHeight = (int) (SHRINK_FACTOR * pageHeight); 749 final float extraScaledSpacing = res.getDimension(R.dimen.smallScreenExtraSpacing); 750 751 final int screenCount = getChildCount(); 752 float totalWidth = screenCount * scaledPageWidth + (screenCount - 1) * extraScaledSpacing; 753 754 boolean isPortrait = getMeasuredHeight() > getMeasuredWidth(); 755 float newY = (isPortrait ? 756 getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginPortrait) : 757 getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginLandscape)); 758 float finalAlpha = 1.0f; 759 float extraShrinkFactor = 1.0f; 760 if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE) { 761 newY = screenHeight - newY - scaledPageHeight; 762 } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) { 763 764 // We shrink and disappear to nothing in the case of all apps 765 // (which is when we shrink to the bottom) 766 newY = screenHeight - newY - scaledPageHeight; 767 finalAlpha = 0.0f; 768 extraShrinkFactor = 0.1f; 769 } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_MIDDLE) { 770 newY = screenHeight / 2 - scaledPageHeight / 2; 771 finalAlpha = 1.0f; 772 } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_TOP) { 773 newY = (isPortrait ? 774 getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginPortrait) : 775 getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginLandscape)); 776 } 777 778 // We animate all the screens to the centered position in workspace 779 // At the same time, the screens become greyed/dimmed 780 781 // newX is initialized to the left-most position of the centered screens 782 float newX = mScroller.getFinalX() + screenWidth / 2 - totalWidth / 2; 783 784 // We are going to scale about the center of the view, so we need to adjust the positions 785 // of the views accordingly 786 newX -= (pageWidth - scaledPageWidth) / 2.0f; 787 newY -= (pageHeight - scaledPageHeight) / 2.0f; 788 for (int i = 0; i < screenCount; i++) { 789 CellLayout cl = (CellLayout) getChildAt(i); 790 791 float rotation = (-i + 2) * WORKSPACE_ROTATION; 792 float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f)); 793 float rotationScaleY = getYScaleForScreen(i); 794 795 if (animated) { 796 final int duration = res.getInteger(R.integer.config_workspaceShrinkTime); 797 ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(cl, 798 PropertyValuesHolder.ofFloat("x", newX), 799 PropertyValuesHolder.ofFloat("y", newY), 800 PropertyValuesHolder.ofFloat("scaleX", 801 SHRINK_FACTOR * rotationScaleX * extraShrinkFactor), 802 PropertyValuesHolder.ofFloat("scaleY", 803 SHRINK_FACTOR * rotationScaleY * extraShrinkFactor), 804 PropertyValuesHolder.ofFloat("backgroundAlpha", finalAlpha), 805 PropertyValuesHolder.ofFloat("alpha", finalAlpha), 806 PropertyValuesHolder.ofFloat("rotationY", rotation)); 807 anim.setDuration(duration); 808 anim.start(); 809 } else { 810 cl.setX((int)newX); 811 cl.setY((int)newY); 812 cl.setScaleX(SHRINK_FACTOR * rotationScaleX * extraShrinkFactor); 813 cl.setScaleY(SHRINK_FACTOR * rotationScaleY * extraShrinkFactor); 814 cl.setBackgroundAlpha(finalAlpha); 815 cl.setAlpha(finalAlpha); 816 cl.setRotationY(rotation); 817 } 818 // increment newX for the next screen 819 newX += scaledPageWidth + extraScaledSpacing; 820 } 821 setChildrenDrawnWithCacheEnabled(true); 822 } 823 824 825 private void updateWhichPagesAcceptDrops(ShrinkPosition state) { 826 updateWhichPagesAcceptDropsHelper(state, false, 1, 1); 827 } 828 829 830 private void updateWhichPagesAcceptDropsDuringDrag(ShrinkPosition state, int spanX, int spanY) { 831 updateWhichPagesAcceptDropsHelper(state, true, spanX, spanY); 832 } 833 834 private void updateWhichPagesAcceptDropsHelper( 835 ShrinkPosition state, boolean isDragHappening, int spanX, int spanY) { 836 final int screenCount = getChildCount(); 837 for (int i = 0; i < screenCount; i++) { 838 CellLayout cl = (CellLayout) getChildAt(i); 839 840 switch (state) { 841 case SHRINK_TO_TOP: 842 if (!isDragHappening) { 843 boolean showDropHighlight = i == mCurrentPage; 844 cl.setAcceptsDrops(showDropHighlight); 845 break; 846 } 847 // otherwise, fall through below and mark non-full screens as accepting drops 848 case SHRINK_TO_BOTTOM_HIDDEN: 849 case SHRINK_TO_BOTTOM_VISIBLE: 850 if (!isDragHappening) { 851 // even if a drag isn't happening, we don't want to show a screen as 852 // accepting drops if it doesn't have at least one free cell 853 spanX = 1; 854 spanY = 1; 855 } 856 // the page accepts drops if we can find at least one empty spot 857 cl.setAcceptsDrops(cl.findCellForSpan(null, spanX, spanY)); 858 break; 859 default: 860 throw new RuntimeException( 861 "updateWhichPagesAcceptDropsHelper passed an unhandled ShrinkPosition"); 862 } 863 } 864 } 865 866 /* 867 * 868 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithItemMinSize) whenever we 869 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace 870 * 871 * These methods mark the appropriate pages as accepting drops (which alters their visual 872 * appearance) and, if the pages are hidden, makes them visible. 873 * 874 */ 875 public void onDragStartedWithItemSpans(int spanX, int spanY) { 876 updateWhichPagesAcceptDropsDuringDrag(mShrunkenState, spanX, spanY); 877 if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) { 878 shrink(ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE, true); 879 } 880 } 881 882 public void onDragStartedWithItemMinSize(int minWidth, int minHeight) { 883 int[] spanXY = CellLayout.rectToCell(getResources(), minWidth, minHeight, null); 884 onDragStartedWithItemSpans(spanXY[0], spanXY[1]); 885 } 886 887 // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was 888 // never dragged over 889 public void onDragStopped() { 890 updateWhichPagesAcceptDrops(mShrunkenState); 891 } 892 893 // We call this when we trigger an unshrink by clicking on the CellLayout cl 894 public void unshrink(CellLayout clThatWasClicked) { 895 int newCurrentPage = mCurrentPage; 896 final int screenCount = getChildCount(); 897 for (int i = 0; i < screenCount; i++) { 898 if (getChildAt(i) == clThatWasClicked) { 899 newCurrentPage = i; 900 } 901 } 902 unshrink(newCurrentPage); 903 } 904 905 @Override 906 protected boolean handlePagingClicks() { 907 return true; 908 } 909 910 private void unshrink(int newCurrentPage) { 911 if (mIsSmall) { 912 int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage); 913 int delta = newX - mScrollX; 914 915 final int screenCount = getChildCount(); 916 for (int i = 0; i < screenCount; i++) { 917 CellLayout cl = (CellLayout) getChildAt(i); 918 cl.setX(cl.getX() + delta); 919 } 920 setCurrentPage(newCurrentPage); 921 unshrink(); 922 } 923 } 924 925 void unshrink() { 926 unshrink(true); 927 } 928 929 void unshrink(boolean animated) { 930 if (mIsSmall) { 931 mIsSmall = false; 932 AnimatorSet s = new AnimatorSet(); 933 final int screenCount = getChildCount(); 934 935 final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime); 936 for (int i = 0; i < screenCount; i++) { 937 final CellLayout cl = (CellLayout)getChildAt(i); 938 float finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f; 939 float rotation = 0.0f; 940 941 if (i < mCurrentPage) { 942 rotation = WORKSPACE_ROTATION; 943 } else if (i > mCurrentPage) { 944 rotation = -WORKSPACE_ROTATION; 945 } 946 947 if (animated) { 948 949 s.playTogether( 950 ObjectAnimator.ofFloat(cl, "translationX", 0.0f).setDuration(duration), 951 ObjectAnimator.ofFloat(cl, "translationY", 0.0f).setDuration(duration), 952 ObjectAnimator.ofFloat(cl, "scaleX", 1.0f).setDuration(duration), 953 ObjectAnimator.ofFloat(cl, "scaleY", 1.0f).setDuration(duration), 954 ObjectAnimator.ofFloat(cl, "backgroundAlpha", 0.0f).setDuration(duration), 955 ObjectAnimator.ofFloat(cl, "alpha", finalAlphaValue).setDuration(duration), 956 ObjectAnimator.ofFloat(cl, "rotationY", rotation).setDuration(duration)); 957 } else { 958 cl.setTranslationX(0.0f); 959 cl.setTranslationY(0.0f); 960 cl.setScaleX(1.0f); 961 cl.setScaleY(1.0f); 962 cl.setBackgroundAlpha(0.0f); 963 cl.setAlpha(finalAlphaValue); 964 cl.setRotationY(rotation); 965 } 966 } 967 if (animated) { 968 // If we call this when we're not animated, onAnimationEnd is never called on 969 // the listener; make sure we only use the listener when we're actually animating 970 s.addListener(mUnshrinkAnimationListener); 971 s.start(); 972 } 973 } 974 } 975 976 /** 977 * Draw the View v into the given Canvas. 978 * 979 * @param v the view to draw 980 * @param destCanvas the canvas to draw on 981 * @param padding the horizontal and vertical padding to use when drawing 982 */ 983 private void drawDragView(View v, Canvas destCanvas, int padding) { 984 final Rect clipRect = mTempRect; 985 v.getDrawingRect(clipRect); 986 987 // For a TextView, adjust the clip rect so that we don't include the text label 988 if (v instanceof TextView) { 989 final int iconHeight = ((TextView)v).getCompoundPaddingTop() - v.getPaddingTop(); 990 clipRect.bottom = clipRect.top + iconHeight; 991 } 992 993 // Draw the View into the bitmap. 994 // The translate of scrollX and scrollY is necessary when drawing TextViews, because 995 // they set scrollX and scrollY to large values to achieve centered text 996 997 destCanvas.save(); 998 destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2); 999 destCanvas.clipRect(clipRect, Op.REPLACE); 1000 v.draw(destCanvas); 1001 destCanvas.restore(); 1002 } 1003 1004 /** 1005 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 1006 * Responsibility for the bitmap is transferred to the caller. 1007 */ 1008 private Bitmap createDragOutline(View v, Canvas canvas, int padding) { 1009 final int outlineColor = getResources().getColor(R.color.drag_outline_color); 1010 final Bitmap b = Bitmap.createBitmap( 1011 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); 1012 1013 canvas.setBitmap(b); 1014 drawDragView(v, canvas, padding); 1015 mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true); 1016 1017 return b; 1018 } 1019 1020 /** 1021 * Creates a drag outline to represent a drop (that we don't have the actual information for 1022 * yet). May be changed in the future to alter the drop outline slightly depending on the 1023 * clip description mime data. 1024 */ 1025 private Bitmap createExternalDragOutline(Canvas canvas, int padding) { 1026 Resources r = getResources(); 1027 final int outlineColor = r.getColor(R.color.drag_outline_color); 1028 final int iconWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width); 1029 final int iconHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height); 1030 final int rectRadius = r.getDimensionPixelSize(R.dimen.external_drop_icon_rect_radius); 1031 final int inset = (int) (Math.min(iconWidth, iconHeight) * 0.2f); 1032 final Bitmap b = Bitmap.createBitmap( 1033 iconWidth + padding, iconHeight + padding, Bitmap.Config.ARGB_8888); 1034 1035 canvas.setBitmap(b); 1036 canvas.drawRoundRect(new RectF(inset, inset, iconWidth - inset, iconHeight - inset), 1037 rectRadius, rectRadius, mExternalDragOutlinePaint); 1038 mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true); 1039 1040 return b; 1041 } 1042 1043 /** 1044 * Returns a new bitmap to show when the given View is being dragged around. 1045 * Responsibility for the bitmap is transferred to the caller. 1046 */ 1047 private Bitmap createDragBitmap(View v, Canvas canvas, int padding) { 1048 final int outlineColor = getResources().getColor(R.color.drag_outline_color); 1049 final Bitmap b = Bitmap.createBitmap( 1050 mDragOutline.getWidth(), mDragOutline.getHeight(), Bitmap.Config.ARGB_8888); 1051 1052 canvas.setBitmap(b); 1053 canvas.drawBitmap(mDragOutline, 0, 0, null); 1054 drawDragView(v, canvas, padding); 1055 mOutlineHelper.applyOuterBlur(b, canvas, outlineColor); 1056 1057 return b; 1058 } 1059 1060 void startDrag(CellLayout.CellInfo cellInfo) { 1061 View child = cellInfo.cell; 1062 1063 // Make sure the drag was started by a long press as opposed to a long click. 1064 if (!child.isInTouchMode()) { 1065 return; 1066 } 1067 1068 mDragInfo = cellInfo; 1069 mDragInfo.screen = mCurrentPage; 1070 1071 CellLayout current = getCurrentDropLayout(); 1072 1073 current.onDragChild(child); 1074 child.setVisibility(View.GONE); 1075 1076 child.clearFocus(); 1077 child.setPressed(false); 1078 1079 final Canvas canvas = new Canvas(); 1080 1081 // We need to add extra padding to the bitmap to make room for the glow effect 1082 final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS; 1083 1084 // The outline is used to visualize where the item will land if dropped 1085 mDragOutline = createDragOutline(child, canvas, bitmapPadding); 1086 1087 // The drag bitmap follows the touch point around on the screen 1088 final Bitmap b = createDragBitmap(child, canvas, bitmapPadding); 1089 1090 final int bmpWidth = b.getWidth(); 1091 final int bmpHeight = b.getHeight(); 1092 child.getLocationOnScreen(mTempXY); 1093 final int screenX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2; 1094 final int screenY = (int) mTempXY[1] + (child.getHeight() - bmpHeight) / 2; 1095 mDragController.startDrag(b, screenX, screenY, 0, 0, bmpWidth, bmpHeight, this, 1096 child.getTag(), DragController.DRAG_ACTION_MOVE, null); 1097 b.recycle(); 1098 } 1099 1100 void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY, 1101 boolean insertAtFirst, int intersectX, int intersectY) { 1102 final CellLayout cellLayout = (CellLayout) getChildAt(screen); 1103 View view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo) info); 1104 1105 final int[] cellXY = new int[2]; 1106 cellLayout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); 1107 addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst); 1108 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 1109 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 1110 cellXY[0], cellXY[1]); 1111 } 1112 1113 private void setPositionForDropAnimation( 1114 View dragView, int dragViewX, int dragViewY, View parent, View child) { 1115 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 1116 1117 // Based on the position of the drag view, find the top left of the original view 1118 int viewX = dragViewX + (dragView.getWidth() - child.getWidth()) / 2; 1119 int viewY = dragViewY + (dragView.getHeight() - child.getHeight()) / 2; 1120 viewX -= getResources().getInteger(R.integer.config_dragViewOffsetX); 1121 viewY -= getResources().getInteger(R.integer.config_dragViewOffsetY); 1122 1123 // Set its old pos (in the new parent's coordinates); it will be animated 1124 // in animateViewIntoPosition after the next layout pass 1125 lp.oldX = viewX - (parent.getLeft() - mScrollX); 1126 lp.oldY = viewY - (parent.getTop() - mScrollY); 1127 } 1128 1129 public void animateViewIntoPosition(final View view) { 1130 final CellLayout parent = (CellLayout) view.getParent(); 1131 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 1132 1133 mDropView = view; 1134 1135 // Convert the animation params to be relative to the Workspace, not the CellLayout 1136 final int fromX = lp.oldX + parent.getLeft(); 1137 final int fromY = lp.oldY + parent.getTop(); 1138 1139 final int dx = lp.x - lp.oldX; 1140 final int dy = lp.y - lp.oldY; 1141 1142 // Calculate the duration of the animation based on the object's distance 1143 final float dist = (float) Math.sqrt(dx*dx + dy*dy); 1144 final Resources res = getResources(); 1145 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); 1146 final int duration = (int) (res.getInteger(R.integer.config_dropAnimMaxDuration) 1147 * mQuintEaseOutInterpolator.getInterpolation(dist / maxDist)); 1148 1149 // Lazy initialize the animation 1150 if (mDropAnim == null) { 1151 mDropAnim = new ValueAnimator(); 1152 mDropAnim.setInterpolator(mQuintEaseOutInterpolator); 1153 1154 // Make the view invisible during the animation; we'll render it manually. 1155 mDropAnim.addListener(new AnimatorListenerAdapter() { 1156 public void onAnimationStart(Animator animation) { 1157 mDropView.setVisibility(View.INVISIBLE); 1158 } 1159 1160 public void onAnimationEnd(Animator animation) { 1161 if (mDropView != null) { 1162 mDropView.setVisibility(View.VISIBLE); 1163 mDropView = null; 1164 } 1165 } 1166 }); 1167 } else { 1168 mDropAnim.end(); // Make sure it's not already running 1169 } 1170 mDropAnim.setDuration(duration); 1171 mDropAnim.setFloatValues(0.0f, 1.0f); 1172 mDropAnim.removeAllUpdateListeners(); 1173 mDropAnim.addUpdateListener(new AnimatorUpdateListener() { 1174 public void onAnimationUpdate(ValueAnimator animation) { 1175 final float percent = (Float) animation.getAnimatedValue(); 1176 // Invalidate the old position 1177 invalidate(mDropViewPos[0], mDropViewPos[1], 1178 mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight()); 1179 1180 mDropViewPos[0] = fromX + (int) (percent * dx + 0.5f); 1181 mDropViewPos[1] = fromY + (int) (percent * dy + 0.5f); 1182 invalidate(mDropViewPos[0], mDropViewPos[1], 1183 mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight()); 1184 } 1185 }); 1186 mDropAnim.start(); 1187 } 1188 1189 /** 1190 * {@inheritDoc} 1191 */ 1192 public boolean acceptDrop(DragSource source, int x, int y, 1193 int xOffset, int yOffset, DragView dragView, Object dragInfo) { 1194 1195 // If it's an external drop (e.g. from All Apps), check if it should be accepted 1196 if (source != this) { 1197 // Don't accept the drop if we're not over a screen at time of drop 1198 if (mDragTargetLayout == null) { 1199 return false; 1200 } 1201 1202 final CellLayout.CellInfo dragCellInfo = mDragInfo; 1203 final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX; 1204 final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY; 1205 1206 final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell; 1207 1208 // Don't accept the drop if there's no room for the item 1209 if (!mDragTargetLayout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) { 1210 mLauncher.showOutOfSpaceMessage(); 1211 return false; 1212 } 1213 } 1214 return true; 1215 } 1216 1217 public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, 1218 DragView dragView, Object dragInfo) { 1219 1220 int originX = x - xOffset; 1221 int originY = y - yOffset; 1222 1223 if (mIsSmall || mIsInUnshrinkAnimation) { 1224 // get originX and originY in the local coordinate system of the screen 1225 mTempOriginXY[0] = originX; 1226 mTempOriginXY[1] = originY; 1227 mapPointFromSelfToChild(mDragTargetLayout, mTempOriginXY); 1228 originX = (int)mTempOriginXY[0]; 1229 originY = (int)mTempOriginXY[1]; 1230 } 1231 1232 if (source != this) { 1233 onDropExternal(originX, originY, dragInfo, mDragTargetLayout); 1234 } else if (mDragInfo != null) { 1235 final View cell = mDragInfo.cell; 1236 if (mDragTargetLayout != null) { 1237 // Move internally 1238 mTargetCell = findNearestVacantArea(originX, originY, 1239 mDragInfo.spanX, mDragInfo.spanY, cell, mDragTargetLayout, 1240 mTargetCell); 1241 1242 int screen = indexOfChild(mDragTargetLayout); 1243 if (screen != mDragInfo.screen) { 1244 final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1245 originalCellLayout.removeView(cell); 1246 addInScreen(cell, screen, mTargetCell[0], mTargetCell[1], 1247 mDragInfo.spanX, mDragInfo.spanY); 1248 } 1249 1250 // update the item's position after drop 1251 final ItemInfo info = (ItemInfo) cell.getTag(); 1252 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 1253 mDragTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]); 1254 lp.cellX = mTargetCell[0]; 1255 lp.cellY = mTargetCell[1]; 1256 cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen, 1257 mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); 1258 1259 LauncherModel.moveItemInDatabase(mLauncher, info, 1260 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 1261 lp.cellX, lp.cellY); 1262 } 1263 1264 final CellLayout parent = (CellLayout) cell.getParent(); 1265 1266 // Prepare it to be animated into its new position 1267 // This must be called after the view has been re-parented 1268 setPositionForDropAnimation(dragView, originX, originY, parent, cell); 1269 parent.onDropChild(cell); 1270 } 1271 } 1272 1273 public void onDragEnter(DragSource source, int x, int y, int xOffset, 1274 int yOffset, DragView dragView, Object dragInfo) { 1275 mDragTargetLayout = null; // Reset the drag state 1276 1277 if (!mIsSmall) { 1278 mDragTargetLayout = getCurrentDropLayout(); 1279 mDragTargetLayout.onDragEnter(); 1280 showOutlines(); 1281 mInDragMode = true; 1282 CellLayout cl = (CellLayout) getChildAt(mCurrentPage); 1283 cl.setBackgroundAlphaMultiplier(1.0f); 1284 } 1285 } 1286 1287 public DropTarget getDropTargetDelegate(DragSource source, int x, int y, 1288 int xOffset, int yOffset, DragView dragView, Object dragInfo) { 1289 1290 if (mIsSmall || mIsInUnshrinkAnimation) { 1291 // If we're shrunken, don't let anyone drag on folders/etc that are on the mini-screens 1292 return null; 1293 } 1294 // We may need to delegate the drag to a child view. If a 1x1 item 1295 // would land in a cell occupied by a DragTarget (e.g. a Folder), 1296 // then drag events should be handled by that child. 1297 1298 ItemInfo item = (ItemInfo)dragInfo; 1299 CellLayout currentLayout = getCurrentDropLayout(); 1300 1301 int dragPointX, dragPointY; 1302 if (item.spanX == 1 && item.spanY == 1) { 1303 // For a 1x1, calculate the drop cell exactly as in onDragOver 1304 dragPointX = x - xOffset; 1305 dragPointY = y - yOffset; 1306 } else { 1307 // Otherwise, use the exact drag coordinates 1308 dragPointX = x; 1309 dragPointY = y; 1310 } 1311 dragPointX += mScrollX - currentLayout.getLeft(); 1312 dragPointY += mScrollY - currentLayout.getTop(); 1313 1314 // If we are dragging over a cell that contains a DropTarget that will 1315 // accept the drop, delegate to that DropTarget. 1316 final int[] cellXY = mTempCell; 1317 currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY); 1318 View child = currentLayout.getChildAt(cellXY[0], cellXY[1]); 1319 if (child instanceof DropTarget) { 1320 DropTarget target = (DropTarget)child; 1321 if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) { 1322 return target; 1323 } 1324 } 1325 return null; 1326 } 1327 1328 /** 1329 * Global drag and drop handler 1330 */ 1331 @Override 1332 public boolean onDragEvent(DragEvent event) { 1333 final CellLayout layout = (CellLayout) getChildAt(mCurrentPage); 1334 final int[] pos = new int[2]; 1335 layout.getLocationOnScreen(pos); 1336 // We need to offset the drag coordinates to layout coordinate space 1337 final int x = (int) event.getX() - pos[0]; 1338 final int y = (int) event.getY() - pos[1]; 1339 1340 switch (event.getAction()) { 1341 case DragEvent.ACTION_DRAG_STARTED: 1342 // Check if we have enough space on this screen to add a new shortcut 1343 if (!layout.findCellForSpan(pos, 1, 1)) { 1344 Toast.makeText(mContext, mContext.getString(R.string.out_of_space), 1345 Toast.LENGTH_SHORT).show(); 1346 return false; 1347 } 1348 1349 ClipDescription desc = event.getClipDescription(); 1350 if (desc.filterMimeTypes(ClipDescription.MIMETYPE_TEXT_INTENT) != null) { 1351 // Create the drag outline 1352 // We need to add extra padding to the bitmap to make room for the glow effect 1353 final Canvas canvas = new Canvas(); 1354 final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS; 1355 mDragOutline = createExternalDragOutline(canvas, bitmapPadding); 1356 1357 // Show the current page outlines to indicate that we can accept this drop 1358 showOutlines(); 1359 layout.setHover(true); 1360 layout.onDragEnter(); 1361 layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1); 1362 1363 return true; 1364 } 1365 break; 1366 case DragEvent.ACTION_DRAG_LOCATION: 1367 // Visualize the drop location 1368 layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1); 1369 return true; 1370 case DragEvent.ACTION_DROP: 1371 // Check if we have enough space on this screen to add a new shortcut 1372 if (!layout.findCellForSpan(pos, 1, 1)) { 1373 Toast.makeText(mContext, mContext.getString(R.string.out_of_space), 1374 Toast.LENGTH_SHORT).show(); 1375 return false; 1376 } 1377 1378 // Try and add any shortcuts 1379 int newDropCount = 0; 1380 final LauncherModel model = mLauncher.getModel(); 1381 final ClipData data = event.getClipData(); 1382 final int itemCount = data.getItemCount(); 1383 for (int i = 0; i < itemCount; ++i) { 1384 final Intent intent = data.getItem(i).getIntent(); 1385 if (intent != null) { 1386 Object info = null; 1387 if (model.validateShortcutIntent(intent)) { 1388 info = model.infoFromShortcutIntent(mContext, intent, data.getIcon()); 1389 } else if (model.validateWidgetIntent(intent)) { 1390 final ComponentName component = ComponentName.unflattenFromString( 1391 intent.getStringExtra(InstallWidgetReceiver.EXTRA_APPWIDGET_COMPONENT)); 1392 final AppWidgetProviderInfo appInfo = 1393 model.findAppWidgetProviderInfoWithComponent(mContext, component); 1394 1395 PendingAddWidgetInfo createInfo = new PendingAddWidgetInfo(); 1396 createInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; 1397 createInfo.componentName = appInfo.provider; 1398 createInfo.minWidth = appInfo.minWidth; 1399 createInfo.minHeight = appInfo.minHeight; 1400 createInfo.configurationData = intent.getParcelableExtra( 1401 InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA); 1402 info = createInfo; 1403 } 1404 1405 if (info != null) { 1406 onDropExternal(x, y, info, layout); 1407 newDropCount++; 1408 } 1409 } 1410 } 1411 1412 // Show error message if we couldn't accept any of the items 1413 if (newDropCount <= 0) { 1414 Toast.makeText(mContext, "Only Shortcut Intents accepted.", 1415 Toast.LENGTH_SHORT).show(); 1416 } 1417 1418 return true; 1419 case DragEvent.ACTION_DRAG_ENDED: 1420 // Hide the page outlines after the drop 1421 layout.setHover(false); 1422 layout.onDragExit(); 1423 hideOutlines(); 1424 return true; 1425 } 1426 return super.onDragEvent(event); 1427 } 1428 1429 /* 1430 * 1431 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 1432 * coordinate space. The argument xy is modified with the return result. 1433 * 1434 */ 1435 void mapPointFromSelfToChild(View v, float[] xy) { 1436 mapPointFromSelfToChild(v, xy, null); 1437 } 1438 1439 /* 1440 * 1441 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 1442 * coordinate space. The argument xy is modified with the return result. 1443 * 1444 * if cachedInverseMatrix is not null, this method will just use that matrix instead of 1445 * computing it itself; we use this to avoid redundant matrix inversions in 1446 * findMatchingPageForDragOver 1447 * 1448 */ 1449 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { 1450 if (cachedInverseMatrix == null) { 1451 v.getMatrix().invert(mTempInverseMatrix); 1452 cachedInverseMatrix = mTempInverseMatrix; 1453 } 1454 xy[0] = xy[0] + mScrollX - v.getLeft(); 1455 xy[1] = xy[1] + mScrollY - v.getTop(); 1456 cachedInverseMatrix.mapPoints(xy); 1457 } 1458 1459 /* 1460 * 1461 * Convert the 2D coordinate xy from this CellLayout's coordinate space to 1462 * the parent View's coordinate space. The argument xy is modified with the return result. 1463 * 1464 */ 1465 void mapPointFromChildToSelf(View v, float[] xy) { 1466 v.getMatrix().mapPoints(xy); 1467 xy[0] -= (mScrollX - v.getLeft()); 1468 xy[1] -= (mScrollY - v.getTop()); 1469 } 1470 1471 static private float squaredDistance(float[] point1, float[] point2) { 1472 float distanceX = point1[0] - point2[0]; 1473 float distanceY = point2[1] - point2[1]; 1474 return distanceX * distanceX + distanceY * distanceY; 1475 } 1476 1477 /* 1478 * 1479 * Returns true if the passed CellLayout cl overlaps with dragView 1480 * 1481 */ 1482 boolean overlaps(CellLayout cl, DragView dragView, 1483 int dragViewX, int dragViewY, Matrix cachedInverseMatrix) { 1484 // Transform the coordinates of the item being dragged to the CellLayout's coordinates 1485 final float[] draggedItemTopLeft = mTempDragCoordinates; 1486 draggedItemTopLeft[0] = dragViewX + dragView.getScaledDragRegionXOffset(); 1487 draggedItemTopLeft[1] = dragViewY + dragView.getScaledDragRegionYOffset(); 1488 final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates; 1489 draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getScaledDragRegionWidth(); 1490 draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getScaledDragRegionHeight(); 1491 1492 // Transform the dragged item's top left coordinates 1493 // to the CellLayout's local coordinates 1494 mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix); 1495 float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]); 1496 float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]); 1497 1498 if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) { 1499 // Transform the dragged item's bottom right coordinates 1500 // to the CellLayout's local coordinates 1501 mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix); 1502 float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]); 1503 float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]); 1504 1505 if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) { 1506 float overlap = (overlapRegionRight - overlapRegionLeft) * 1507 (overlapRegionBottom - overlapRegionTop); 1508 if (overlap > 0) { 1509 return true; 1510 } 1511 } 1512 } 1513 return false; 1514 } 1515 1516 /* 1517 * 1518 * This method returns the CellLayout that is currently being dragged to. In order to drag 1519 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second 1520 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one 1521 * 1522 * Return null if no CellLayout is currently being dragged over 1523 * 1524 */ 1525 private CellLayout findMatchingPageForDragOver( 1526 DragView dragView, int originX, int originY, int offsetX, int offsetY) { 1527 // We loop through all the screens (ie CellLayouts) and see which ones overlap 1528 // with the item being dragged and then choose the one that's closest to the touch point 1529 final int screenCount = getChildCount(); 1530 CellLayout bestMatchingScreen = null; 1531 float smallestDistSoFar = Float.MAX_VALUE; 1532 1533 for (int i = 0; i < screenCount; i++) { 1534 CellLayout cl = (CellLayout)getChildAt(i); 1535 1536 final float[] touchXy = mTempTouchCoordinates; 1537 touchXy[0] = originX + offsetX; 1538 touchXy[1] = originY + offsetY; 1539 1540 // Transform the touch coordinates to the CellLayout's local coordinates 1541 // If the touch point is within the bounds of the cell layout, we can return immediately 1542 cl.getMatrix().invert(mTempInverseMatrix); 1543 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); 1544 1545 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && 1546 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { 1547 return cl; 1548 } 1549 1550 if (overlaps(cl, dragView, originX, originY, mTempInverseMatrix)) { 1551 // Get the center of the cell layout in screen coordinates 1552 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates; 1553 cellLayoutCenter[0] = cl.getWidth()/2; 1554 cellLayoutCenter[1] = cl.getHeight()/2; 1555 mapPointFromChildToSelf(cl, cellLayoutCenter); 1556 1557 touchXy[0] = originX + offsetX; 1558 touchXy[1] = originY + offsetY; 1559 1560 // Calculate the distance between the center of the CellLayout 1561 // and the touch point 1562 float dist = squaredDistance(touchXy, cellLayoutCenter); 1563 1564 if (dist < smallestDistSoFar) { 1565 smallestDistSoFar = dist; 1566 bestMatchingScreen = cl; 1567 } 1568 } 1569 } 1570 return bestMatchingScreen; 1571 } 1572 1573 public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, 1574 DragView dragView, Object dragInfo) { 1575 // When touch is inside the scroll area, skip dragOver actions for the current screen 1576 if (!mInScrollArea) { 1577 CellLayout layout; 1578 int originX = x - xOffset; 1579 int originY = y - yOffset; 1580 if (mIsSmall || mIsInUnshrinkAnimation) { 1581 layout = findMatchingPageForDragOver( 1582 dragView, originX, originY, xOffset, yOffset); 1583 1584 if (layout != mDragTargetLayout) { 1585 if (mDragTargetLayout != null) { 1586 mDragTargetLayout.setHover(false); 1587 } 1588 mDragTargetLayout = layout; 1589 if (mDragTargetLayout != null) { 1590 mDragTargetLayout.setHover(true); 1591 } 1592 } 1593 } else { 1594 layout = getCurrentDropLayout(); 1595 1596 final ItemInfo item = (ItemInfo)dragInfo; 1597 if (dragInfo instanceof LauncherAppWidgetInfo) { 1598 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo; 1599 1600 if (widgetInfo.spanX == -1) { 1601 // Calculate the grid spans needed to fit this widget 1602 int[] spans = layout.rectToCell( 1603 widgetInfo.minWidth, widgetInfo.minHeight, null); 1604 item.spanX = spans[0]; 1605 item.spanY = spans[1]; 1606 } 1607 } 1608 1609 if (source instanceof AllAppsPagedView) { 1610 // This is a hack to fix the point used to determine which cell an icon from 1611 // the all apps screen is over 1612 if (item != null && item.spanX == 1 && layout != null) { 1613 int dragRegionLeft = (dragView.getWidth() - layout.getCellWidth()) / 2; 1614 1615 originX += dragRegionLeft - dragView.getDragRegionLeft(); 1616 if (dragView.getDragRegionWidth() != layout.getCellWidth()) { 1617 dragView.setDragRegion(dragView.getDragRegionLeft(), 1618 dragView.getDragRegionTop(), 1619 layout.getCellWidth(), 1620 dragView.getDragRegionHeight()); 1621 } 1622 } 1623 } 1624 1625 if (layout != mDragTargetLayout) { 1626 if (mDragTargetLayout != null) { 1627 mDragTargetLayout.onDragExit(); 1628 } 1629 layout.onDragEnter(); 1630 mDragTargetLayout = layout; 1631 } 1632 1633 // only visualize the drop locations for moving icons within the home screen on 1634 // tablet on phone, we also visualize icons dragged in from All Apps 1635 if ((!LauncherApplication.isScreenXLarge() || source == this) 1636 && mDragTargetLayout != null) { 1637 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 1638 int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX); 1639 int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY); 1640 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, 1641 localOriginX, localOriginY, item.spanX, item.spanY); 1642 } 1643 } 1644 } 1645 } 1646 1647 public void onDragExit(DragSource source, int x, int y, int xOffset, 1648 int yOffset, DragView dragView, Object dragInfo) { 1649 if (mDragTargetLayout != null) { 1650 mDragTargetLayout.onDragExit(); 1651 } 1652 if (!mIsPageMoving) { 1653 hideOutlines(); 1654 mInDragMode = false; 1655 } 1656 clearAllHovers(); 1657 } 1658 1659 private void onDropExternal(int x, int y, Object dragInfo, 1660 CellLayout cellLayout) { 1661 onDropExternal(x, y, dragInfo, cellLayout, false); 1662 } 1663 1664 /** 1665 * Add the item specified by dragInfo to the given layout. 1666 * This is basically the equivalent of onDropExternal, except it's not initiated 1667 * by drag and drop. 1668 * @return true if successful 1669 */ 1670 public boolean addExternalItemToScreen(Object dragInfo, View layout) { 1671 CellLayout cl = (CellLayout) layout; 1672 ItemInfo info = (ItemInfo) dragInfo; 1673 1674 if (cl.findCellForSpan(mTempEstimate, info.spanX, info.spanY)) { 1675 onDropExternal(-1, -1, dragInfo, cl, false); 1676 return true; 1677 } 1678 mLauncher.showOutOfSpaceMessage(); 1679 return false; 1680 } 1681 1682 // Drag from somewhere else 1683 // NOTE: This can also be called when we are outside of a drag event, when we want 1684 // to add an item to one of the workspace screens. 1685 private void onDropExternal(int x, int y, Object dragInfo, 1686 CellLayout cellLayout, boolean insertAtFirst) { 1687 int screen = indexOfChild(cellLayout); 1688 if (dragInfo instanceof PendingAddItemInfo) { 1689 PendingAddItemInfo info = (PendingAddItemInfo) dragInfo; 1690 // When dragging and dropping from customization tray, we deal with creating 1691 // widgets/shortcuts/folders in a slightly different way 1692 int[] touchXY = new int[2]; 1693 touchXY[0] = x; 1694 touchXY[1] = y; 1695 switch (info.itemType) { 1696 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1697 mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) info, screen, touchXY); 1698 break; 1699 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: 1700 mLauncher.addLiveFolderFromDrop(info.componentName, screen, touchXY); 1701 break; 1702 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1703 mLauncher.processShortcutFromDrop(info.componentName, screen, touchXY); 1704 break; 1705 default: 1706 throw new IllegalStateException("Unknown item type: " + info.itemType); 1707 } 1708 cellLayout.onDragExit(); 1709 cellLayout.animateDrop(); 1710 return; 1711 } 1712 1713 // This is for other drag/drop cases, like dragging from All Apps 1714 ItemInfo info = (ItemInfo) dragInfo; 1715 1716 View view = null; 1717 1718 switch (info.itemType) { 1719 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1720 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1721 if (info.container == NO_ID && info instanceof ApplicationInfo) { 1722 // Came from all apps -- make a copy 1723 info = new ShortcutInfo((ApplicationInfo) info); 1724 } 1725 view = mLauncher.createShortcut(R.layout.application, cellLayout, 1726 (ShortcutInfo) info); 1727 break; 1728 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 1729 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, 1730 cellLayout, ((UserFolderInfo) info)); 1731 break; 1732 default: 1733 throw new IllegalStateException("Unknown item type: " + info.itemType); 1734 } 1735 1736 // If the view is null, it has already been added. 1737 if (view == null) { 1738 cellLayout.onDragExit(); 1739 } else { 1740 mTargetCell = findNearestVacantArea(x, y, 1, 1, null, cellLayout, mTargetCell); 1741 addInScreen(view, indexOfChild(cellLayout), mTargetCell[0], 1742 mTargetCell[1], info.spanX, info.spanY, insertAtFirst); 1743 cellLayout.onDropChild(view); 1744 cellLayout.animateDrop(); 1745 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 1746 1747 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 1748 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 1749 lp.cellX, lp.cellY); 1750 } 1751 } 1752 1753 /** 1754 * Return the current {@link CellLayout}, correctly picking the destination 1755 * screen while a scroll is in progress. 1756 */ 1757 private CellLayout getCurrentDropLayout() { 1758 // if we're currently small, use findMatchingPageForDragOver instead 1759 if (mIsSmall) return null; 1760 int index = mScroller.isFinished() ? mCurrentPage : mNextPage; 1761 return (CellLayout) getChildAt(index); 1762 } 1763 1764 /** 1765 * Return the current CellInfo describing our current drag; this method exists 1766 * so that Launcher can sync this object with the correct info when the activity is created/ 1767 * destroyed 1768 * 1769 */ 1770 public CellLayout.CellInfo getDragInfo() { 1771 return mDragInfo; 1772 } 1773 1774 /** 1775 * Calculate the nearest cell where the given object would be dropped. 1776 */ 1777 private int[] findNearestVacantArea(int pixelX, int pixelY, 1778 int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) { 1779 1780 int localPixelX = pixelX - (layout.getLeft() - mScrollX); 1781 int localPixelY = pixelY - (layout.getTop() - mScrollY); 1782 1783 // Find the best target drop location 1784 return layout.findNearestVacantArea( 1785 localPixelX, localPixelY, spanX, spanY, ignoreView, recycle); 1786 } 1787 1788 /** 1789 * Estimate the size that a child with the given dimensions will take in the current screen. 1790 */ 1791 void estimateChildSize(int minWidth, int minHeight, int[] result) { 1792 ((CellLayout)getChildAt(mCurrentPage)).estimateChildSize(minWidth, minHeight, result); 1793 } 1794 1795 void setLauncher(Launcher launcher) { 1796 mLauncher = launcher; 1797 } 1798 1799 public void setDragController(DragController dragController) { 1800 mDragController = dragController; 1801 } 1802 1803 public void onDropCompleted(View target, boolean success) { 1804 if (success) { 1805 if (target != this && mDragInfo != null) { 1806 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1807 cellLayout.removeView(mDragInfo.cell); 1808 if (mDragInfo.cell instanceof DropTarget) { 1809 mDragController.removeDropTarget((DropTarget)mDragInfo.cell); 1810 } 1811 // final Object tag = mDragInfo.cell.getTag(); 1812 } 1813 } else if (mDragInfo != null) { 1814 ((CellLayout) getChildAt(mDragInfo.screen)).onDropChild(mDragInfo.cell); 1815 } 1816 1817 mDragOutline = null; 1818 mDragInfo = null; 1819 } 1820 1821 public boolean isDropEnabled() { 1822 return true; 1823 } 1824 1825 @Override 1826 protected void onRestoreInstanceState(Parcelable state) { 1827 super.onRestoreInstanceState(state); 1828 Launcher.setScreen(mCurrentPage); 1829 } 1830 1831 @Override 1832 public void scrollLeft() { 1833 if (!mIsSmall && !mIsInUnshrinkAnimation) { 1834 super.scrollLeft(); 1835 } 1836 } 1837 1838 @Override 1839 public void scrollRight() { 1840 if (!mIsSmall && !mIsInUnshrinkAnimation) { 1841 super.scrollRight(); 1842 } 1843 } 1844 1845 @Override 1846 public void onEnterScrollArea(int direction) { 1847 if (!mIsSmall && !mIsInUnshrinkAnimation) { 1848 mInScrollArea = true; 1849 final int screen = getCurrentPage() + ((direction == DragController.SCROLL_LEFT) ? -1 : 1); 1850 if (0 <= screen && screen < getChildCount()) { 1851 ((CellLayout) getChildAt(screen)).setHover(true); 1852 } 1853 1854 if (mDragTargetLayout != null) { 1855 mDragTargetLayout.onDragExit(); 1856 mDragTargetLayout = null; 1857 } 1858 } 1859 } 1860 1861 private void clearAllHovers() { 1862 final int childCount = getChildCount(); 1863 for (int i = 0; i < childCount; i++) { 1864 ((CellLayout) getChildAt(i)).setHover(false); 1865 } 1866 } 1867 1868 @Override 1869 public void onExitScrollArea() { 1870 if (mInScrollArea) { 1871 mInScrollArea = false; 1872 clearAllHovers(); 1873 } 1874 } 1875 1876 public Folder getFolderForTag(Object tag) { 1877 final int screenCount = getChildCount(); 1878 for (int screen = 0; screen < screenCount; screen++) { 1879 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1880 int count = currentScreen.getChildCount(); 1881 for (int i = 0; i < count; i++) { 1882 View child = currentScreen.getChildAt(i); 1883 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 1884 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { 1885 Folder f = (Folder) child; 1886 if (f.getInfo() == tag && f.getInfo().opened) { 1887 return f; 1888 } 1889 } 1890 } 1891 } 1892 return null; 1893 } 1894 1895 public View getViewForTag(Object tag) { 1896 int screenCount = getChildCount(); 1897 for (int screen = 0; screen < screenCount; screen++) { 1898 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1899 int count = currentScreen.getChildCount(); 1900 for (int i = 0; i < count; i++) { 1901 View child = currentScreen.getChildAt(i); 1902 if (child.getTag() == tag) { 1903 return child; 1904 } 1905 } 1906 } 1907 return null; 1908 } 1909 1910 1911 void removeItems(final ArrayList<ApplicationInfo> apps) { 1912 final int screenCount = getChildCount(); 1913 final PackageManager manager = getContext().getPackageManager(); 1914 final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); 1915 1916 final HashSet<String> packageNames = new HashSet<String>(); 1917 final int appCount = apps.size(); 1918 for (int i = 0; i < appCount; i++) { 1919 packageNames.add(apps.get(i).componentName.getPackageName()); 1920 } 1921 1922 for (int i = 0; i < screenCount; i++) { 1923 final CellLayout layout = (CellLayout) getChildAt(i); 1924 1925 // Avoid ANRs by treating each screen separately 1926 post(new Runnable() { 1927 public void run() { 1928 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 1929 childrenToRemove.clear(); 1930 1931 int childCount = layout.getChildCount(); 1932 for (int j = 0; j < childCount; j++) { 1933 final View view = layout.getChildAt(j); 1934 Object tag = view.getTag(); 1935 1936 if (tag instanceof ShortcutInfo) { 1937 final ShortcutInfo info = (ShortcutInfo) tag; 1938 final Intent intent = info.intent; 1939 final ComponentName name = intent.getComponent(); 1940 1941 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1942 for (String packageName: packageNames) { 1943 if (packageName.equals(name.getPackageName())) { 1944 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1945 childrenToRemove.add(view); 1946 } 1947 } 1948 } 1949 } else if (tag instanceof UserFolderInfo) { 1950 final UserFolderInfo info = (UserFolderInfo) tag; 1951 final ArrayList<ShortcutInfo> contents = info.contents; 1952 final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1); 1953 final int contentsCount = contents.size(); 1954 boolean removedFromFolder = false; 1955 1956 for (int k = 0; k < contentsCount; k++) { 1957 final ShortcutInfo appInfo = contents.get(k); 1958 final Intent intent = appInfo.intent; 1959 final ComponentName name = intent.getComponent(); 1960 1961 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1962 for (String packageName: packageNames) { 1963 if (packageName.equals(name.getPackageName())) { 1964 toRemove.add(appInfo); 1965 LauncherModel.deleteItemFromDatabase(mLauncher, appInfo); 1966 removedFromFolder = true; 1967 } 1968 } 1969 } 1970 } 1971 1972 contents.removeAll(toRemove); 1973 if (removedFromFolder) { 1974 final Folder folder = getOpenFolder(); 1975 if (folder != null) 1976 folder.notifyDataSetChanged(); 1977 } 1978 } else if (tag instanceof LiveFolderInfo) { 1979 final LiveFolderInfo info = (LiveFolderInfo) tag; 1980 final Uri uri = info.uri; 1981 final ProviderInfo providerInfo = manager.resolveContentProvider( 1982 uri.getAuthority(), 0); 1983 1984 if (providerInfo != null) { 1985 for (String packageName: packageNames) { 1986 if (packageName.equals(providerInfo.packageName)) { 1987 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1988 childrenToRemove.add(view); 1989 } 1990 } 1991 } 1992 } else if (tag instanceof LauncherAppWidgetInfo) { 1993 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; 1994 final AppWidgetProviderInfo provider = 1995 widgets.getAppWidgetInfo(info.appWidgetId); 1996 if (provider != null) { 1997 for (String packageName: packageNames) { 1998 if (packageName.equals(provider.provider.getPackageName())) { 1999 LauncherModel.deleteItemFromDatabase(mLauncher, info); 2000 childrenToRemove.add(view); 2001 } 2002 } 2003 } 2004 } 2005 } 2006 2007 childCount = childrenToRemove.size(); 2008 for (int j = 0; j < childCount; j++) { 2009 View child = childrenToRemove.get(j); 2010 layout.removeViewInLayout(child); 2011 if (child instanceof DropTarget) { 2012 mDragController.removeDropTarget((DropTarget)child); 2013 } 2014 } 2015 2016 if (childCount > 0) { 2017 layout.requestLayout(); 2018 layout.invalidate(); 2019 } 2020 } 2021 }); 2022 } 2023 } 2024 2025 void updateShortcuts(ArrayList<ApplicationInfo> apps) { 2026 final int screenCount = getChildCount(); 2027 for (int i = 0; i < screenCount; i++) { 2028 final CellLayout layout = (CellLayout) getChildAt(i); 2029 int childCount = layout.getChildCount(); 2030 for (int j = 0; j < childCount; j++) { 2031 final View view = layout.getChildAt(j); 2032 Object tag = view.getTag(); 2033 if (tag instanceof ShortcutInfo) { 2034 ShortcutInfo info = (ShortcutInfo)tag; 2035 // We need to check for ACTION_MAIN otherwise getComponent() might 2036 // return null for some shortcuts (for instance, for shortcuts to 2037 // web pages.) 2038 final Intent intent = info.intent; 2039 final ComponentName name = intent.getComponent(); 2040 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 2041 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 2042 final int appCount = apps.size(); 2043 for (int k = 0; k < appCount; k++) { 2044 ApplicationInfo app = apps.get(k); 2045 if (app.componentName.equals(name)) { 2046 info.setIcon(mIconCache.getIcon(info.intent)); 2047 ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null, 2048 new FastBitmapDrawable(info.getIcon(mIconCache)), 2049 null, null); 2050 } 2051 } 2052 } 2053 } 2054 } 2055 } 2056 } 2057 2058 void moveToDefaultScreen(boolean animate) { 2059 if (mIsSmall || mIsInUnshrinkAnimation) { 2060 mLauncher.showWorkspace(animate, (CellLayout)getChildAt(mDefaultPage)); 2061 } else if (animate) { 2062 snapToPage(mDefaultPage); 2063 } else { 2064 setCurrentPage(mDefaultPage); 2065 } 2066 getChildAt(mDefaultPage).requestFocus(); 2067 } 2068 2069 void setIndicators(Drawable previous, Drawable next) { 2070 mPreviousIndicator = previous; 2071 mNextIndicator = next; 2072 previous.setLevel(mCurrentPage); 2073 next.setLevel(mCurrentPage); 2074 } 2075 2076 @Override 2077 public void syncPages() { 2078 } 2079 2080 @Override 2081 public void syncPageItems(int page) { 2082 } 2083 2084} 2085