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