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