Workspace.java revision 0207c522472d7252f146f4d39efa456ca5248c6b
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 final int width = getWidth(); 609 final int height = getHeight(); 610 611 // In portrait orientation, draw the glowing edge when dragging to adjacent screens 612 if (mInScrollArea && (height > width)) { 613 final int pageHeight = getChildAt(0).getHeight(); 614 615 // This determines the height of the glowing edge: 90% of the page height 616 final int padding = (int) ((height - pageHeight) * 0.5f + pageHeight * 0.1f); 617 618 final CellLayout leftPage = (CellLayout) getChildAt(mCurrentPage - 1); 619 final CellLayout rightPage = (CellLayout) getChildAt(mCurrentPage + 1); 620 621 if (leftPage != null && leftPage.getHover()) { 622 final Drawable d = getResources().getDrawable(R.drawable.page_hover_left); 623 d.setBounds(mScrollX, padding, mScrollX + d.getIntrinsicWidth(), height - padding); 624 d.draw(canvas); 625 } else if (rightPage != null && rightPage.getHover()) { 626 final Drawable d = getResources().getDrawable(R.drawable.page_hover_right); 627 d.setBounds(mScrollX + width - d.getIntrinsicWidth(), padding, mScrollX + width, height - padding); 628 d.draw(canvas); 629 } 630 } 631 632 if (mDropView != null) { 633 // We are animating an item that was just dropped on the home screen. 634 // Render its View in the current animation position. 635 canvas.save(Canvas.MATRIX_SAVE_FLAG); 636 final int xPos = mDropViewPos[0] - mDropView.getScrollX(); 637 final int yPos = mDropViewPos[1] - mDropView.getScrollY(); 638 canvas.translate(xPos, yPos); 639 mDropView.draw(canvas); 640 canvas.restore(); 641 } 642 } 643 } 644 645 @Override 646 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 647 if (!mLauncher.isAllAppsVisible()) { 648 final Folder openFolder = getOpenFolder(); 649 if (openFolder != null) { 650 return openFolder.requestFocus(direction, previouslyFocusedRect); 651 } else { 652 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 653 } 654 } 655 return false; 656 } 657 658 @Override 659 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 660 if (!mLauncher.isAllAppsVisible()) { 661 final Folder openFolder = getOpenFolder(); 662 if (openFolder != null) { 663 openFolder.addFocusables(views, direction); 664 } else { 665 super.addFocusables(views, direction, focusableMode); 666 } 667 } 668 } 669 670 @Override 671 public boolean dispatchTouchEvent(MotionEvent ev) { 672 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 673 // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps 674 // ie when you click on a mini-screen, it zooms back to that screen) 675 if (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible()) { 676 return false; 677 } 678 } 679 return super.dispatchTouchEvent(ev); 680 } 681 682 void enableChildrenCache(int fromPage, int toPage) { 683 if (fromPage > toPage) { 684 final int temp = fromPage; 685 fromPage = toPage; 686 toPage = temp; 687 } 688 689 final int screenCount = getChildCount(); 690 691 fromPage = Math.max(fromPage, 0); 692 toPage = Math.min(toPage, screenCount - 1); 693 694 for (int i = fromPage; i <= toPage; i++) { 695 final CellLayout layout = (CellLayout) getChildAt(i); 696 layout.setChildrenDrawnWithCacheEnabled(true); 697 layout.setChildrenDrawingCacheEnabled(true); 698 } 699 } 700 701 void clearChildrenCache() { 702 final int screenCount = getChildCount(); 703 for (int i = 0; i < screenCount; i++) { 704 final CellLayout layout = (CellLayout) getChildAt(i); 705 layout.setChildrenDrawnWithCacheEnabled(false); 706 } 707 } 708 709 @Override 710 public boolean onTouchEvent(MotionEvent ev) { 711 if (mLauncher.isAllAppsVisible()) { 712 // Cancel any scrolling that is in progress. 713 if (!mScroller.isFinished()) { 714 mScroller.abortAnimation(); 715 } 716 setCurrentPage(mCurrentPage); 717 return false; // We don't want the events. Let them fall through to the all apps view. 718 } 719 720 return super.onTouchEvent(ev); 721 } 722 723 public boolean isSmall() { 724 return mIsSmall; 725 } 726 727 void shrinkToTop(boolean animated) { 728 shrink(ShrinkPosition.SHRINK_TO_TOP, animated); 729 } 730 731 void shrinkToMiddle() { 732 shrink(ShrinkPosition.SHRINK_TO_MIDDLE, true); 733 } 734 735 void shrinkToBottom() { 736 shrinkToBottom(true); 737 } 738 739 void shrinkToBottom(boolean animated) { 740 shrink(ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN, animated); 741 } 742 743 private float getYScaleForScreen(int screen) { 744 int x = Math.abs(screen - 2); 745 746 // TODO: This should be generalized for use with arbitrary rotation angles. 747 switch(x) { 748 case 0: return EXTRA_SCALE_FACTOR_0; 749 case 1: return EXTRA_SCALE_FACTOR_1; 750 case 2: return EXTRA_SCALE_FACTOR_2; 751 } 752 return 1.0f; 753 } 754 755 // we use this to shrink the workspace for the all apps view and the customize view 756 private void shrink(ShrinkPosition shrinkPosition, boolean animated) { 757 if (mFirstLayout) { 758 // (mFirstLayout == "first layout has not happened yet") 759 // if we get a call to shrink() as part of our initialization (for example, if 760 // Launcher is started in All Apps mode) then we need to wait for a layout call 761 // to get our width so we can layout the mini-screen views correctly 762 mWaitingToShrink = true; 763 mWaitingToShrinkPosition = shrinkPosition; 764 return; 765 } 766 mIsSmall = true; 767 mShrunkenState = shrinkPosition; 768 769 // Stop any scrolling, move to the current page right away 770 setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage); 771 updateWhichPagesAcceptDrops(mShrunkenState); 772 773 // we intercept and reject all touch events when we're small, so be sure to reset the state 774 mTouchState = TOUCH_STATE_REST; 775 mActivePointerId = INVALID_POINTER; 776 777 CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); 778 currentPage.setBackgroundAlphaMultiplier(1.0f); 779 780 final Resources res = getResources(); 781 final int screenWidth = getWidth(); 782 final int screenHeight = getHeight(); 783 784 // Making the assumption that all pages have the same width as the 0th 785 final int pageWidth = getChildAt(0).getMeasuredWidth(); 786 final int pageHeight = getChildAt(0).getMeasuredHeight(); 787 788 final int scaledPageWidth = (int) (SHRINK_FACTOR * pageWidth); 789 final int scaledPageHeight = (int) (SHRINK_FACTOR * pageHeight); 790 final float extraScaledSpacing = res.getDimension(R.dimen.smallScreenExtraSpacing); 791 792 final int screenCount = getChildCount(); 793 float totalWidth = screenCount * scaledPageWidth + (screenCount - 1) * extraScaledSpacing; 794 795 boolean isPortrait = getMeasuredHeight() > getMeasuredWidth(); 796 float newY = (isPortrait ? 797 getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginPortrait) : 798 getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginLandscape)); 799 float finalAlpha = 1.0f; 800 float extraShrinkFactor = 1.0f; 801 if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE) { 802 newY = screenHeight - newY - scaledPageHeight; 803 } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) { 804 805 // We shrink and disappear to nothing in the case of all apps 806 // (which is when we shrink to the bottom) 807 newY = screenHeight - newY - scaledPageHeight; 808 finalAlpha = 0.25f; 809 } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_MIDDLE) { 810 newY = screenHeight / 2 - scaledPageHeight / 2; 811 finalAlpha = 1.0f; 812 } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_TOP) { 813 newY = (isPortrait ? 814 getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginPortrait) : 815 getResources().getDimension(R.dimen.customizeSmallScreenVerticalMarginLandscape)); 816 } 817 818 // We animate all the screens to the centered position in workspace 819 // At the same time, the screens become greyed/dimmed 820 821 // newX is initialized to the left-most position of the centered screens 822 float newX = mScroller.getFinalX() + screenWidth / 2 - totalWidth / 2; 823 824 // We are going to scale about the center of the view, so we need to adjust the positions 825 // of the views accordingly 826 newX -= (pageWidth - scaledPageWidth) / 2.0f; 827 newY -= (pageHeight - scaledPageHeight) / 2.0f; 828 829 if (mAnimator != null) { 830 mAnimator.cancel(); 831 } 832 mAnimator = new AnimatorSet(); 833 for (int i = 0; i < screenCount; i++) { 834 CellLayout cl = (CellLayout) getChildAt(i); 835 836 float rotation = (-i + 2) * WORKSPACE_ROTATION; 837 float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f)); 838 float rotationScaleY = getYScaleForScreen(i); 839 840 if (animated) { 841 final int duration = res.getInteger(R.integer.config_workspaceShrinkTime); 842 ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(cl, 843 PropertyValuesHolder.ofFloat("x", newX), 844 PropertyValuesHolder.ofFloat("y", newY), 845 PropertyValuesHolder.ofFloat("scaleX", 846 SHRINK_FACTOR * rotationScaleX * extraShrinkFactor), 847 PropertyValuesHolder.ofFloat("scaleY", 848 SHRINK_FACTOR * rotationScaleY * extraShrinkFactor), 849 PropertyValuesHolder.ofFloat("backgroundAlpha", finalAlpha), 850 PropertyValuesHolder.ofFloat("alpha", finalAlpha), 851 PropertyValuesHolder.ofFloat("rotationY", rotation)); 852 anim.setDuration(duration); 853 mAnimator.playTogether(anim); 854 } else { 855 cl.setX((int)newX); 856 cl.setY((int)newY); 857 cl.setScaleX(SHRINK_FACTOR * rotationScaleX * extraShrinkFactor); 858 cl.setScaleY(SHRINK_FACTOR * rotationScaleY * extraShrinkFactor); 859 cl.setBackgroundAlpha(finalAlpha); 860 cl.setAlpha(finalAlpha); 861 cl.setRotationY(rotation); 862 } 863 // increment newX for the next screen 864 newX += scaledPageWidth + extraScaledSpacing; 865 } 866 if (animated) { 867 mAnimator.start(); 868 } 869 setChildrenDrawnWithCacheEnabled(true); 870 } 871 872 873 private void updateWhichPagesAcceptDrops(ShrinkPosition state) { 874 updateWhichPagesAcceptDropsHelper(state, false, 1, 1); 875 } 876 877 878 private void updateWhichPagesAcceptDropsDuringDrag(ShrinkPosition state, int spanX, int spanY) { 879 updateWhichPagesAcceptDropsHelper(state, true, spanX, spanY); 880 } 881 882 private void updateWhichPagesAcceptDropsHelper( 883 ShrinkPosition state, boolean isDragHappening, int spanX, int spanY) { 884 final int screenCount = getChildCount(); 885 for (int i = 0; i < screenCount; i++) { 886 CellLayout cl = (CellLayout) getChildAt(i); 887 888 switch (state) { 889 case SHRINK_TO_TOP: 890 if (!isDragHappening) { 891 boolean showDropHighlight = i == mCurrentPage; 892 cl.setAcceptsDrops(showDropHighlight); 893 break; 894 } 895 // otherwise, fall through below and mark non-full screens as accepting drops 896 case SHRINK_TO_BOTTOM_HIDDEN: 897 case SHRINK_TO_BOTTOM_VISIBLE: 898 if (!isDragHappening) { 899 // even if a drag isn't happening, we don't want to show a screen as 900 // accepting drops if it doesn't have at least one free cell 901 spanX = 1; 902 spanY = 1; 903 } 904 // the page accepts drops if we can find at least one empty spot 905 cl.setAcceptsDrops(cl.findCellForSpan(null, spanX, spanY)); 906 break; 907 default: 908 throw new RuntimeException( 909 "updateWhichPagesAcceptDropsHelper passed an unhandled ShrinkPosition"); 910 } 911 } 912 } 913 914 /* 915 * 916 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithItemMinSize) whenever we 917 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace 918 * 919 * These methods mark the appropriate pages as accepting drops (which alters their visual 920 * appearance) and, if the pages are hidden, makes them visible. 921 * 922 */ 923 public void onDragStartedWithItemSpans(int spanX, int spanY) { 924 updateWhichPagesAcceptDropsDuringDrag(mShrunkenState, spanX, spanY); 925 if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) { 926 shrink(ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE, true); 927 } 928 } 929 930 public void onDragStartedWithItemMinSize(int minWidth, int minHeight) { 931 int[] spanXY = CellLayout.rectToCell(getResources(), minWidth, minHeight, null); 932 onDragStartedWithItemSpans(spanXY[0], spanXY[1]); 933 } 934 935 // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was 936 // never dragged over 937 public void onDragStopped() { 938 updateWhichPagesAcceptDrops(mShrunkenState); 939 if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE) { 940 shrink(ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN, true); 941 } 942 } 943 944 // We call this when we trigger an unshrink by clicking on the CellLayout cl 945 public void unshrink(CellLayout clThatWasClicked) { 946 int newCurrentPage = mCurrentPage; 947 final int screenCount = getChildCount(); 948 for (int i = 0; i < screenCount; i++) { 949 if (getChildAt(i) == clThatWasClicked) { 950 newCurrentPage = i; 951 } 952 } 953 unshrink(newCurrentPage); 954 } 955 956 @Override 957 protected boolean handlePagingClicks() { 958 return true; 959 } 960 961 private void unshrink(int newCurrentPage) { 962 if (mIsSmall) { 963 int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage); 964 int delta = newX - mScrollX; 965 966 final int screenCount = getChildCount(); 967 for (int i = 0; i < screenCount; i++) { 968 CellLayout cl = (CellLayout) getChildAt(i); 969 cl.setX(cl.getX() + delta); 970 } 971 setCurrentPage(newCurrentPage); 972 unshrink(); 973 } 974 } 975 976 void unshrink() { 977 unshrink(true); 978 } 979 980 void unshrink(boolean animated) { 981 if (mIsSmall) { 982 mIsSmall = false; 983 if (mAnimator != null) { 984 mAnimator.cancel(); 985 } 986 mAnimator = new AnimatorSet(); 987 final int screenCount = getChildCount(); 988 989 final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime); 990 for (int i = 0; i < screenCount; i++) { 991 final CellLayout cl = (CellLayout)getChildAt(i); 992 float finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f; 993 float rotation = 0.0f; 994 995 if (i < mCurrentPage) { 996 rotation = WORKSPACE_ROTATION; 997 } else if (i > mCurrentPage) { 998 rotation = -WORKSPACE_ROTATION; 999 } 1000 1001 if (animated) { 1002 mAnimator.playTogether( 1003 ObjectAnimator.ofFloat(cl, "translationX", 0.0f).setDuration(duration), 1004 ObjectAnimator.ofFloat(cl, "translationY", 0.0f).setDuration(duration), 1005 ObjectAnimator.ofFloat(cl, "scaleX", 1.0f).setDuration(duration), 1006 ObjectAnimator.ofFloat(cl, "scaleY", 1.0f).setDuration(duration), 1007 ObjectAnimator.ofFloat(cl, "backgroundAlpha", 0.0f).setDuration(duration), 1008 ObjectAnimator.ofFloat(cl, "alpha", finalAlphaValue).setDuration(duration), 1009 ObjectAnimator.ofFloat(cl, "rotationY", rotation).setDuration(duration)); 1010 } else { 1011 cl.setTranslationX(0.0f); 1012 cl.setTranslationY(0.0f); 1013 cl.setScaleX(1.0f); 1014 cl.setScaleY(1.0f); 1015 cl.setBackgroundAlpha(0.0f); 1016 cl.setAlpha(finalAlphaValue); 1017 cl.setRotationY(rotation); 1018 } 1019 } 1020 if (animated) { 1021 // If we call this when we're not animated, onAnimationEnd is never called on 1022 // the listener; make sure we only use the listener when we're actually animating 1023 mAnimator.addListener(mUnshrinkAnimationListener); 1024 mAnimator.start(); 1025 } 1026 } 1027 } 1028 1029 /** 1030 * Draw the View v into the given Canvas. 1031 * 1032 * @param v the view to draw 1033 * @param destCanvas the canvas to draw on 1034 * @param padding the horizontal and vertical padding to use when drawing 1035 */ 1036 private void drawDragView(View v, Canvas destCanvas, int padding) { 1037 final Rect clipRect = mTempRect; 1038 v.getDrawingRect(clipRect); 1039 1040 // For a TextView, adjust the clip rect so that we don't include the text label 1041 if (v instanceof TextView) { 1042 final int iconHeight = ((TextView)v).getCompoundPaddingTop() - v.getPaddingTop(); 1043 clipRect.bottom = clipRect.top + iconHeight; 1044 } 1045 1046 // Draw the View into the bitmap. 1047 // The translate of scrollX and scrollY is necessary when drawing TextViews, because 1048 // they set scrollX and scrollY to large values to achieve centered text 1049 1050 destCanvas.save(); 1051 destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2); 1052 destCanvas.clipRect(clipRect, Op.REPLACE); 1053 v.draw(destCanvas); 1054 destCanvas.restore(); 1055 } 1056 1057 /** 1058 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 1059 * Responsibility for the bitmap is transferred to the caller. 1060 */ 1061 private Bitmap createDragOutline(View v, Canvas canvas, int padding) { 1062 final int outlineColor = getResources().getColor(R.color.drag_outline_color); 1063 final Bitmap b = Bitmap.createBitmap( 1064 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); 1065 1066 canvas.setBitmap(b); 1067 drawDragView(v, canvas, padding); 1068 mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true); 1069 1070 return b; 1071 } 1072 1073 /** 1074 * Creates a drag outline to represent a drop (that we don't have the actual information for 1075 * yet). May be changed in the future to alter the drop outline slightly depending on the 1076 * clip description mime data. 1077 */ 1078 private Bitmap createExternalDragOutline(Canvas canvas, int padding) { 1079 Resources r = getResources(); 1080 final int outlineColor = r.getColor(R.color.drag_outline_color); 1081 final int iconWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width); 1082 final int iconHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height); 1083 final int rectRadius = r.getDimensionPixelSize(R.dimen.external_drop_icon_rect_radius); 1084 final int inset = (int) (Math.min(iconWidth, iconHeight) * 0.2f); 1085 final Bitmap b = Bitmap.createBitmap( 1086 iconWidth + padding, iconHeight + padding, Bitmap.Config.ARGB_8888); 1087 1088 canvas.setBitmap(b); 1089 canvas.drawRoundRect(new RectF(inset, inset, iconWidth - inset, iconHeight - inset), 1090 rectRadius, rectRadius, mExternalDragOutlinePaint); 1091 mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true); 1092 1093 return b; 1094 } 1095 1096 /** 1097 * Returns a new bitmap to show when the given View is being dragged around. 1098 * Responsibility for the bitmap is transferred to the caller. 1099 */ 1100 private Bitmap createDragBitmap(View v, Canvas canvas, int padding) { 1101 final int outlineColor = getResources().getColor(R.color.drag_outline_color); 1102 final Bitmap b = Bitmap.createBitmap( 1103 mDragOutline.getWidth(), mDragOutline.getHeight(), Bitmap.Config.ARGB_8888); 1104 1105 canvas.setBitmap(b); 1106 canvas.drawBitmap(mDragOutline, 0, 0, null); 1107 drawDragView(v, canvas, padding); 1108 mOutlineHelper.applyOuterBlur(b, canvas, outlineColor); 1109 1110 return b; 1111 } 1112 1113 void startDrag(CellLayout.CellInfo cellInfo) { 1114 View child = cellInfo.cell; 1115 1116 // Make sure the drag was started by a long press as opposed to a long click. 1117 if (!child.isInTouchMode()) { 1118 return; 1119 } 1120 1121 mDragInfo = cellInfo; 1122 mDragInfo.screen = mCurrentPage; 1123 1124 CellLayout current = getCurrentDropLayout(); 1125 1126 current.onDragChild(child); 1127 child.setVisibility(View.GONE); 1128 1129 child.clearFocus(); 1130 child.setPressed(false); 1131 1132 final Canvas canvas = new Canvas(); 1133 1134 // We need to add extra padding to the bitmap to make room for the glow effect 1135 final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS; 1136 1137 // The outline is used to visualize where the item will land if dropped 1138 mDragOutline = createDragOutline(child, canvas, bitmapPadding); 1139 1140 // The drag bitmap follows the touch point around on the screen 1141 final Bitmap b = createDragBitmap(child, canvas, bitmapPadding); 1142 1143 final int bmpWidth = b.getWidth(); 1144 final int bmpHeight = b.getHeight(); 1145 child.getLocationOnScreen(mTempXY); 1146 final int screenX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2; 1147 final int screenY = (int) mTempXY[1] + (child.getHeight() - bmpHeight) / 2; 1148 mDragController.startDrag(b, screenX, screenY, 0, 0, bmpWidth, bmpHeight, this, 1149 child.getTag(), DragController.DRAG_ACTION_MOVE, null); 1150 b.recycle(); 1151 } 1152 1153 void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY, 1154 boolean insertAtFirst, int intersectX, int intersectY) { 1155 final CellLayout cellLayout = (CellLayout) getChildAt(screen); 1156 View view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo) info); 1157 1158 final int[] cellXY = new int[2]; 1159 cellLayout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); 1160 addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst); 1161 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 1162 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 1163 cellXY[0], cellXY[1]); 1164 } 1165 1166 private void setPositionForDropAnimation( 1167 View dragView, int dragViewX, int dragViewY, View parent, View child) { 1168 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 1169 1170 // Based on the position of the drag view, find the top left of the original view 1171 int viewX = dragViewX + (dragView.getWidth() - child.getWidth()) / 2; 1172 int viewY = dragViewY + (dragView.getHeight() - child.getHeight()) / 2; 1173 viewX -= getResources().getInteger(R.integer.config_dragViewOffsetX); 1174 viewY -= getResources().getInteger(R.integer.config_dragViewOffsetY); 1175 1176 // Set its old pos (in the new parent's coordinates); it will be animated 1177 // in animateViewIntoPosition after the next layout pass 1178 lp.oldX = viewX - (parent.getLeft() - mScrollX); 1179 lp.oldY = viewY - (parent.getTop() - mScrollY); 1180 } 1181 1182 public void animateViewIntoPosition(final View view) { 1183 final CellLayout parent = (CellLayout) view.getParent(); 1184 final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 1185 1186 // Convert the animation params to be relative to the Workspace, not the CellLayout 1187 final int fromX = lp.oldX + parent.getLeft(); 1188 final int fromY = lp.oldY + parent.getTop(); 1189 1190 final int dx = lp.x - lp.oldX; 1191 final int dy = lp.y - lp.oldY; 1192 1193 // Calculate the duration of the animation based on the object's distance 1194 final float dist = (float) Math.sqrt(dx*dx + dy*dy); 1195 final Resources res = getResources(); 1196 final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist); 1197 int duration = res.getInteger(R.integer.config_dropAnimMaxDuration); 1198 if (dist < maxDist) { 1199 duration *= mQuintEaseOutInterpolator.getInterpolation(dist / maxDist); 1200 } 1201 1202 if (mDropAnim != null) { 1203 // This should really be end(), but that will not be called synchronously, 1204 // so instead we use LauncherAnimatorListenerAdapter.onAnimationEndOrCancel() 1205 // and call cancel() here. 1206 mDropAnim.cancel(); 1207 } 1208 mDropAnim = new ValueAnimator(); 1209 mDropAnim.setInterpolator(mQuintEaseOutInterpolator); 1210 1211 // The view is invisible during the animation; we render it manually. 1212 mDropAnim.addListener(new LauncherAnimatorListenerAdapter() { 1213 public void onAnimationStart(Animator animation) { 1214 // Set this here so that we don't render it until the animation begins 1215 mDropView = view; 1216 } 1217 1218 public void onAnimationEndOrCancel(Animator animation) { 1219 if (mDropView != null) { 1220 mDropView.setVisibility(View.VISIBLE); 1221 mDropView = null; 1222 } 1223 } 1224 }); 1225 1226 mDropAnim.setDuration(duration); 1227 mDropAnim.setFloatValues(0.0f, 1.0f); 1228 mDropAnim.removeAllUpdateListeners(); 1229 mDropAnim.addUpdateListener(new AnimatorUpdateListener() { 1230 public void onAnimationUpdate(ValueAnimator animation) { 1231 final float percent = (Float) animation.getAnimatedValue(); 1232 // Invalidate the old position 1233 invalidate(mDropViewPos[0], mDropViewPos[1], 1234 mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight()); 1235 1236 mDropViewPos[0] = fromX + (int) (percent * dx + 0.5f); 1237 mDropViewPos[1] = fromY + (int) (percent * dy + 0.5f); 1238 invalidate(mDropViewPos[0], mDropViewPos[1], 1239 mDropViewPos[0] + view.getWidth(), mDropViewPos[1] + view.getHeight()); 1240 } 1241 }); 1242 1243 1244 view.setVisibility(View.INVISIBLE); 1245 1246 if (!mScroller.isFinished()) { 1247 mAnimOnPageEndMoving = mDropAnim; 1248 } else { 1249 mDropAnim.start(); 1250 } 1251 } 1252 1253 /** 1254 * {@inheritDoc} 1255 */ 1256 public boolean acceptDrop(DragSource source, int x, int y, 1257 int xOffset, int yOffset, DragView dragView, Object dragInfo) { 1258 1259 // If it's an external drop (e.g. from All Apps), check if it should be accepted 1260 if (source != this) { 1261 // Don't accept the drop if we're not over a screen at time of drop 1262 if (mDragTargetLayout == null) { 1263 return false; 1264 } 1265 1266 final CellLayout.CellInfo dragCellInfo = mDragInfo; 1267 final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX; 1268 final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY; 1269 1270 final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell; 1271 1272 // Don't accept the drop if there's no room for the item 1273 if (!mDragTargetLayout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) { 1274 mLauncher.showOutOfSpaceMessage(); 1275 return false; 1276 } 1277 } 1278 return true; 1279 } 1280 1281 public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, 1282 DragView dragView, Object dragInfo) { 1283 1284 int originX = x - xOffset; 1285 int originY = y - yOffset; 1286 1287 if (mIsSmall || mIsInUnshrinkAnimation) { 1288 // get originX and originY in the local coordinate system of the screen 1289 mTempOriginXY[0] = originX; 1290 mTempOriginXY[1] = originY; 1291 mapPointFromSelfToChild(mDragTargetLayout, mTempOriginXY); 1292 originX = (int)mTempOriginXY[0]; 1293 originY = (int)mTempOriginXY[1]; 1294 } 1295 1296 if (source != this) { 1297 onDropExternal(originX, originY, dragInfo, mDragTargetLayout); 1298 } else if (mDragInfo != null) { 1299 final View cell = mDragInfo.cell; 1300 if (mDragTargetLayout != null) { 1301 // Move internally 1302 mTargetCell = findNearestVacantArea(originX, originY, 1303 mDragInfo.spanX, mDragInfo.spanY, cell, mDragTargetLayout, 1304 mTargetCell); 1305 1306 if (mTargetCell == null) { 1307 snapToPage(mDragInfo.screen); 1308 } else { 1309 int screen = indexOfChild(mDragTargetLayout); 1310 if (screen != mDragInfo.screen) { 1311 // Reparent the view 1312 ((CellLayout) getChildAt(mDragInfo.screen)).removeView(cell); 1313 addInScreen(cell, screen, mTargetCell[0], mTargetCell[1], 1314 mDragInfo.spanX, mDragInfo.spanY); 1315 } 1316 1317 // update the item's position after drop 1318 final ItemInfo info = (ItemInfo) cell.getTag(); 1319 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 1320 mDragTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]); 1321 lp.cellX = mTargetCell[0]; 1322 lp.cellY = mTargetCell[1]; 1323 cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen, 1324 mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); 1325 1326 LauncherModel.moveItemInDatabase(mLauncher, info, 1327 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 1328 lp.cellX, lp.cellY); 1329 } 1330 } 1331 1332 final CellLayout parent = (CellLayout) cell.getParent(); 1333 1334 // Prepare it to be animated into its new position 1335 // This must be called after the view has been re-parented 1336 setPositionForDropAnimation(dragView, originX, originY, parent, cell); 1337 parent.onDropChild(cell); 1338 } 1339 } 1340 1341 public void onDragEnter(DragSource source, int x, int y, int xOffset, 1342 int yOffset, DragView dragView, Object dragInfo) { 1343 mDragTargetLayout = null; // Reset the drag state 1344 1345 if (!mIsSmall) { 1346 mDragTargetLayout = getCurrentDropLayout(); 1347 mDragTargetLayout.onDragEnter(); 1348 showOutlines(); 1349 mInDragMode = true; 1350 CellLayout cl = (CellLayout) getChildAt(mCurrentPage); 1351 cl.setBackgroundAlphaMultiplier(1.0f); 1352 } 1353 } 1354 1355 public DropTarget getDropTargetDelegate(DragSource source, int x, int y, 1356 int xOffset, int yOffset, DragView dragView, Object dragInfo) { 1357 1358 if (mIsSmall || mIsInUnshrinkAnimation) { 1359 // If we're shrunken, don't let anyone drag on folders/etc that are on the mini-screens 1360 return null; 1361 } 1362 // We may need to delegate the drag to a child view. If a 1x1 item 1363 // would land in a cell occupied by a DragTarget (e.g. a Folder), 1364 // then drag events should be handled by that child. 1365 1366 ItemInfo item = (ItemInfo)dragInfo; 1367 CellLayout currentLayout = getCurrentDropLayout(); 1368 1369 int dragPointX, dragPointY; 1370 if (item.spanX == 1 && item.spanY == 1) { 1371 // For a 1x1, calculate the drop cell exactly as in onDragOver 1372 dragPointX = x - xOffset; 1373 dragPointY = y - yOffset; 1374 } else { 1375 // Otherwise, use the exact drag coordinates 1376 dragPointX = x; 1377 dragPointY = y; 1378 } 1379 dragPointX += mScrollX - currentLayout.getLeft(); 1380 dragPointY += mScrollY - currentLayout.getTop(); 1381 1382 // If we are dragging over a cell that contains a DropTarget that will 1383 // accept the drop, delegate to that DropTarget. 1384 final int[] cellXY = mTempCell; 1385 currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY); 1386 View child = currentLayout.getChildAt(cellXY[0], cellXY[1]); 1387 if (child instanceof DropTarget) { 1388 DropTarget target = (DropTarget)child; 1389 if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) { 1390 return target; 1391 } 1392 } 1393 return null; 1394 } 1395 1396 /** 1397 * Global drag and drop handler 1398 */ 1399 @Override 1400 public boolean onDragEvent(DragEvent event) { 1401 final ClipDescription desc = event.getClipDescription(); 1402 final CellLayout layout = (CellLayout) getChildAt(mCurrentPage); 1403 final int[] pos = new int[2]; 1404 layout.getLocationOnScreen(pos); 1405 // We need to offset the drag coordinates to layout coordinate space 1406 final int x = (int) event.getX() - pos[0]; 1407 final int y = (int) event.getY() - pos[1]; 1408 1409 switch (event.getAction()) { 1410 case DragEvent.ACTION_DRAG_STARTED: 1411 // Check if we have enough space on this screen to add a new shortcut 1412 if (!layout.findCellForSpan(pos, 1, 1)) { 1413 Toast.makeText(mContext, mContext.getString(R.string.out_of_space), 1414 Toast.LENGTH_SHORT).show(); 1415 return false; 1416 } 1417 1418 // Create the drag outline 1419 // We need to add extra padding to the bitmap to make room for the glow effect 1420 final Canvas canvas = new Canvas(); 1421 final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS; 1422 mDragOutline = createExternalDragOutline(canvas, bitmapPadding); 1423 1424 // Show the current page outlines to indicate that we can accept this drop 1425 showOutlines(); 1426 layout.setHover(true); 1427 layout.onDragEnter(); 1428 layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1); 1429 1430 return true; 1431 case DragEvent.ACTION_DRAG_LOCATION: 1432 // Visualize the drop location 1433 layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1); 1434 return true; 1435 case DragEvent.ACTION_DROP: 1436 // Check if we have enough space on this screen to add a new shortcut 1437 if (!layout.findCellForSpan(pos, 1, 1)) { 1438 Toast.makeText(mContext, mContext.getString(R.string.out_of_space), 1439 Toast.LENGTH_SHORT).show(); 1440 return false; 1441 } 1442 1443 // Try and add any shortcuts 1444 int newDropCount = 0; 1445 final LauncherModel model = mLauncher.getModel(); 1446 final ClipData data = event.getClipData(); 1447 1448 // We assume that the mime types are ordered in descending importance of 1449 // representation. So we enumerate the list of mime types and alert the 1450 // user if any widgets can handle the drop. Only the most preferred 1451 // representation will be handled. 1452 pos[0] = x; 1453 pos[1] = y; 1454 final int mimeTypeCount = desc.getMimeTypeCount(); 1455 for (int j = 0; j < mimeTypeCount; ++j) { 1456 final String mimeType = desc.getMimeType(j); 1457 1458 if (mimeType.equals(InstallShortcutReceiver.SHORTCUT_MIMETYPE)) { 1459 final Intent intent = data.getItem(j).getIntent(); 1460 Object info = model.infoFromShortcutIntent(mContext, intent, data.getIcon()); 1461 onDropExternal(x, y, info, layout); 1462 } else { 1463 final List<WidgetMimeTypeHandlerData> widgets = 1464 model.resolveWidgetsForMimeType(mContext, mimeType); 1465 final int numWidgets = widgets.size(); 1466 1467 if (numWidgets == 0) { 1468 continue; 1469 } else if (numWidgets == 1) { 1470 // If there is only one item, then go ahead and add and configure 1471 // that widget 1472 final AppWidgetProviderInfo widgetInfo = widgets.get(0).widgetInfo; 1473 final PendingAddWidgetInfo createInfo = 1474 new PendingAddWidgetInfo(widgetInfo, mimeType, data); 1475 mLauncher.addAppWidgetFromDrop(createInfo, mCurrentPage, pos); 1476 } else if (numWidgets > 1) { 1477 // Show the widget picker dialog if there is more than one widget 1478 // that can handle this data type 1479 final InstallWidgetReceiver.WidgetListAdapter adapter = 1480 new InstallWidgetReceiver.WidgetListAdapter(mLauncher, mimeType, 1481 data, widgets, layout, mCurrentPage, pos); 1482 final AlertDialog.Builder builder = 1483 new AlertDialog.Builder(mContext); 1484 builder.setAdapter(adapter, adapter); 1485 builder.setCancelable(true); 1486 builder.setTitle(mContext.getString( 1487 R.string.external_drop_widget_pick_title)); 1488 builder.setIcon(R.drawable.ic_no_applications); 1489 builder.show(); 1490 } 1491 } 1492 newDropCount++; 1493 break; 1494 } 1495 1496 // Show error message if we couldn't accept any of the items 1497 if (newDropCount <= 0) { 1498 Toast.makeText(mContext, mContext.getString(R.string.external_drop_widget_error), 1499 Toast.LENGTH_SHORT).show(); 1500 } 1501 1502 return true; 1503 case DragEvent.ACTION_DRAG_ENDED: 1504 // Hide the page outlines after the drop 1505 layout.setHover(false); 1506 layout.onDragExit(); 1507 hideOutlines(); 1508 return true; 1509 } 1510 return super.onDragEvent(event); 1511 } 1512 1513 /* 1514 * 1515 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 1516 * coordinate space. The argument xy is modified with the return result. 1517 * 1518 */ 1519 void mapPointFromSelfToChild(View v, float[] xy) { 1520 mapPointFromSelfToChild(v, xy, null); 1521 } 1522 1523 /* 1524 * 1525 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 1526 * coordinate space. The argument xy is modified with the return result. 1527 * 1528 * if cachedInverseMatrix is not null, this method will just use that matrix instead of 1529 * computing it itself; we use this to avoid redundant matrix inversions in 1530 * findMatchingPageForDragOver 1531 * 1532 */ 1533 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { 1534 if (cachedInverseMatrix == null) { 1535 v.getMatrix().invert(mTempInverseMatrix); 1536 cachedInverseMatrix = mTempInverseMatrix; 1537 } 1538 xy[0] = xy[0] + mScrollX - v.getLeft(); 1539 xy[1] = xy[1] + mScrollY - v.getTop(); 1540 cachedInverseMatrix.mapPoints(xy); 1541 } 1542 1543 /* 1544 * 1545 * Convert the 2D coordinate xy from this CellLayout's coordinate space to 1546 * the parent View's coordinate space. The argument xy is modified with the return result. 1547 * 1548 */ 1549 void mapPointFromChildToSelf(View v, float[] xy) { 1550 v.getMatrix().mapPoints(xy); 1551 xy[0] -= (mScrollX - v.getLeft()); 1552 xy[1] -= (mScrollY - v.getTop()); 1553 } 1554 1555 static private float squaredDistance(float[] point1, float[] point2) { 1556 float distanceX = point1[0] - point2[0]; 1557 float distanceY = point2[1] - point2[1]; 1558 return distanceX * distanceX + distanceY * distanceY; 1559 } 1560 1561 /* 1562 * 1563 * Returns true if the passed CellLayout cl overlaps with dragView 1564 * 1565 */ 1566 boolean overlaps(CellLayout cl, DragView dragView, 1567 int dragViewX, int dragViewY, Matrix cachedInverseMatrix) { 1568 // Transform the coordinates of the item being dragged to the CellLayout's coordinates 1569 final float[] draggedItemTopLeft = mTempDragCoordinates; 1570 draggedItemTopLeft[0] = dragViewX + dragView.getScaledDragRegionXOffset(); 1571 draggedItemTopLeft[1] = dragViewY + dragView.getScaledDragRegionYOffset(); 1572 final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates; 1573 draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getScaledDragRegionWidth(); 1574 draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getScaledDragRegionHeight(); 1575 1576 // Transform the dragged item's top left coordinates 1577 // to the CellLayout's local coordinates 1578 mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix); 1579 float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]); 1580 float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]); 1581 1582 if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) { 1583 // Transform the dragged item's bottom right coordinates 1584 // to the CellLayout's local coordinates 1585 mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix); 1586 float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]); 1587 float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]); 1588 1589 if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) { 1590 float overlap = (overlapRegionRight - overlapRegionLeft) * 1591 (overlapRegionBottom - overlapRegionTop); 1592 if (overlap > 0) { 1593 return true; 1594 } 1595 } 1596 } 1597 return false; 1598 } 1599 1600 /* 1601 * 1602 * This method returns the CellLayout that is currently being dragged to. In order to drag 1603 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second 1604 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one 1605 * 1606 * Return null if no CellLayout is currently being dragged over 1607 * 1608 */ 1609 private CellLayout findMatchingPageForDragOver( 1610 DragView dragView, int originX, int originY, int offsetX, int offsetY) { 1611 // We loop through all the screens (ie CellLayouts) and see which ones overlap 1612 // with the item being dragged and then choose the one that's closest to the touch point 1613 final int screenCount = getChildCount(); 1614 CellLayout bestMatchingScreen = null; 1615 float smallestDistSoFar = Float.MAX_VALUE; 1616 1617 for (int i = 0; i < screenCount; i++) { 1618 CellLayout cl = (CellLayout)getChildAt(i); 1619 1620 final float[] touchXy = mTempTouchCoordinates; 1621 touchXy[0] = originX + offsetX; 1622 touchXy[1] = originY + offsetY; 1623 1624 // Transform the touch coordinates to the CellLayout's local coordinates 1625 // If the touch point is within the bounds of the cell layout, we can return immediately 1626 cl.getMatrix().invert(mTempInverseMatrix); 1627 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); 1628 1629 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && 1630 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { 1631 return cl; 1632 } 1633 1634 if (overlaps(cl, dragView, originX, originY, mTempInverseMatrix)) { 1635 // Get the center of the cell layout in screen coordinates 1636 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates; 1637 cellLayoutCenter[0] = cl.getWidth()/2; 1638 cellLayoutCenter[1] = cl.getHeight()/2; 1639 mapPointFromChildToSelf(cl, cellLayoutCenter); 1640 1641 touchXy[0] = originX + offsetX; 1642 touchXy[1] = originY + offsetY; 1643 1644 // Calculate the distance between the center of the CellLayout 1645 // and the touch point 1646 float dist = squaredDistance(touchXy, cellLayoutCenter); 1647 1648 if (dist < smallestDistSoFar) { 1649 smallestDistSoFar = dist; 1650 bestMatchingScreen = cl; 1651 } 1652 } 1653 } 1654 return bestMatchingScreen; 1655 } 1656 1657 public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, 1658 DragView dragView, Object dragInfo) { 1659 // When touch is inside the scroll area, skip dragOver actions for the current screen 1660 if (!mInScrollArea) { 1661 CellLayout layout; 1662 int originX = x - xOffset; 1663 int originY = y - yOffset; 1664 if (mIsSmall || mIsInUnshrinkAnimation) { 1665 layout = findMatchingPageForDragOver( 1666 dragView, originX, originY, xOffset, yOffset); 1667 1668 if (layout != mDragTargetLayout) { 1669 if (mDragTargetLayout != null) { 1670 mDragTargetLayout.setHover(false); 1671 } 1672 mDragTargetLayout = layout; 1673 if (mDragTargetLayout != null) { 1674 mDragTargetLayout.setHover(true); 1675 } 1676 } 1677 } else { 1678 layout = getCurrentDropLayout(); 1679 1680 final ItemInfo item = (ItemInfo)dragInfo; 1681 if (dragInfo instanceof LauncherAppWidgetInfo) { 1682 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo; 1683 1684 if (widgetInfo.spanX == -1) { 1685 // Calculate the grid spans needed to fit this widget 1686 int[] spans = layout.rectToCell( 1687 widgetInfo.minWidth, widgetInfo.minHeight, null); 1688 item.spanX = spans[0]; 1689 item.spanY = spans[1]; 1690 } 1691 } 1692 1693 if (source instanceof AllAppsPagedView) { 1694 // This is a hack to fix the point used to determine which cell an icon from 1695 // the all apps screen is over 1696 if (item != null && item.spanX == 1 && layout != null) { 1697 int dragRegionLeft = (dragView.getWidth() - layout.getCellWidth()) / 2; 1698 1699 originX += dragRegionLeft - dragView.getDragRegionLeft(); 1700 if (dragView.getDragRegionWidth() != layout.getCellWidth()) { 1701 dragView.setDragRegion(dragView.getDragRegionLeft(), 1702 dragView.getDragRegionTop(), 1703 layout.getCellWidth(), 1704 dragView.getDragRegionHeight()); 1705 } 1706 } 1707 } 1708 1709 if (layout != mDragTargetLayout) { 1710 if (mDragTargetLayout != null) { 1711 mDragTargetLayout.onDragExit(); 1712 } 1713 layout.onDragEnter(); 1714 mDragTargetLayout = layout; 1715 } 1716 1717 // only visualize the drop locations for moving icons within the home screen on 1718 // tablet on phone, we also visualize icons dragged in from All Apps 1719 if ((!LauncherApplication.isScreenXLarge() || source == this) 1720 && mDragTargetLayout != null) { 1721 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 1722 int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX); 1723 int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY); 1724 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, 1725 localOriginX, localOriginY, item.spanX, item.spanY); 1726 } 1727 } 1728 } 1729 } 1730 1731 public void onDragExit(DragSource source, int x, int y, int xOffset, 1732 int yOffset, DragView dragView, Object dragInfo) { 1733 if (mDragTargetLayout != null) { 1734 mDragTargetLayout.onDragExit(); 1735 } 1736 if (!mIsPageMoving) { 1737 hideOutlines(); 1738 mInDragMode = false; 1739 } 1740 clearAllHovers(); 1741 } 1742 1743 private void onDropExternal(int x, int y, Object dragInfo, 1744 CellLayout cellLayout) { 1745 onDropExternal(x, y, dragInfo, cellLayout, false); 1746 } 1747 1748 /** 1749 * Add the item specified by dragInfo to the given layout. 1750 * This is basically the equivalent of onDropExternal, except it's not initiated 1751 * by drag and drop. 1752 * @return true if successful 1753 */ 1754 public boolean addExternalItemToScreen(Object dragInfo, View layout) { 1755 CellLayout cl = (CellLayout) layout; 1756 ItemInfo info = (ItemInfo) dragInfo; 1757 1758 if (cl.findCellForSpan(mTempEstimate, info.spanX, info.spanY)) { 1759 onDropExternal(-1, -1, dragInfo, cl, false); 1760 return true; 1761 } 1762 mLauncher.showOutOfSpaceMessage(); 1763 return false; 1764 } 1765 1766 // Drag from somewhere else 1767 // NOTE: This can also be called when we are outside of a drag event, when we want 1768 // to add an item to one of the workspace screens. 1769 private void onDropExternal(int x, int y, Object dragInfo, 1770 CellLayout cellLayout, boolean insertAtFirst) { 1771 int screen = indexOfChild(cellLayout); 1772 if (dragInfo instanceof PendingAddItemInfo) { 1773 PendingAddItemInfo info = (PendingAddItemInfo) dragInfo; 1774 // When dragging and dropping from customization tray, we deal with creating 1775 // widgets/shortcuts/folders in a slightly different way 1776 int[] touchXY = new int[2]; 1777 touchXY[0] = x; 1778 touchXY[1] = y; 1779 switch (info.itemType) { 1780 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1781 mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) info, screen, touchXY); 1782 break; 1783 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: 1784 mLauncher.addLiveFolderFromDrop(info.componentName, screen, touchXY); 1785 break; 1786 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1787 mLauncher.processShortcutFromDrop(info.componentName, screen, touchXY); 1788 break; 1789 default: 1790 throw new IllegalStateException("Unknown item type: " + info.itemType); 1791 } 1792 cellLayout.onDragExit(); 1793 cellLayout.animateDrop(); 1794 return; 1795 } 1796 1797 // This is for other drag/drop cases, like dragging from All Apps 1798 ItemInfo info = (ItemInfo) dragInfo; 1799 1800 View view = null; 1801 1802 switch (info.itemType) { 1803 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1804 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1805 if (info.container == NO_ID && info instanceof ApplicationInfo) { 1806 // Came from all apps -- make a copy 1807 info = new ShortcutInfo((ApplicationInfo) info); 1808 } 1809 view = mLauncher.createShortcut(R.layout.application, cellLayout, 1810 (ShortcutInfo) info); 1811 break; 1812 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 1813 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, 1814 cellLayout, (UserFolderInfo) info, mIconCache); 1815 break; 1816 default: 1817 throw new IllegalStateException("Unknown item type: " + info.itemType); 1818 } 1819 1820 // If the view is null, it has already been added. 1821 if (view == null) { 1822 cellLayout.onDragExit(); 1823 } else { 1824 mTargetCell = findNearestVacantArea(x, y, 1, 1, null, cellLayout, mTargetCell); 1825 addInScreen(view, indexOfChild(cellLayout), mTargetCell[0], 1826 mTargetCell[1], info.spanX, info.spanY, insertAtFirst); 1827 cellLayout.onDropChild(view); 1828 cellLayout.animateDrop(); 1829 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 1830 1831 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 1832 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 1833 lp.cellX, lp.cellY); 1834 } 1835 } 1836 1837 /** 1838 * Return the current {@link CellLayout}, correctly picking the destination 1839 * screen while a scroll is in progress. 1840 */ 1841 private CellLayout getCurrentDropLayout() { 1842 // if we're currently small, use findMatchingPageForDragOver instead 1843 if (mIsSmall) return null; 1844 int index = mScroller.isFinished() ? mCurrentPage : mNextPage; 1845 return (CellLayout) getChildAt(index); 1846 } 1847 1848 /** 1849 * Return the current CellInfo describing our current drag; this method exists 1850 * so that Launcher can sync this object with the correct info when the activity is created/ 1851 * destroyed 1852 * 1853 */ 1854 public CellLayout.CellInfo getDragInfo() { 1855 return mDragInfo; 1856 } 1857 1858 /** 1859 * Calculate the nearest cell where the given object would be dropped. 1860 */ 1861 private int[] findNearestVacantArea(int pixelX, int pixelY, 1862 int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) { 1863 1864 int localPixelX = pixelX - (layout.getLeft() - mScrollX); 1865 int localPixelY = pixelY - (layout.getTop() - mScrollY); 1866 1867 // Find the best target drop location 1868 return layout.findNearestVacantArea( 1869 localPixelX, localPixelY, spanX, spanY, ignoreView, recycle); 1870 } 1871 1872 /** 1873 * Estimate the size that a child with the given dimensions will take in the current screen. 1874 */ 1875 void estimateChildSize(int minWidth, int minHeight, int[] result) { 1876 ((CellLayout)getChildAt(mCurrentPage)).estimateChildSize(minWidth, minHeight, result); 1877 } 1878 1879 void setLauncher(Launcher launcher) { 1880 mLauncher = launcher; 1881 } 1882 1883 public void setDragController(DragController dragController) { 1884 mDragController = dragController; 1885 } 1886 1887 public void onDropCompleted(View target, boolean success) { 1888 if (success) { 1889 if (target != this && mDragInfo != null) { 1890 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1891 cellLayout.removeView(mDragInfo.cell); 1892 if (mDragInfo.cell instanceof DropTarget) { 1893 mDragController.removeDropTarget((DropTarget)mDragInfo.cell); 1894 } 1895 // final Object tag = mDragInfo.cell.getTag(); 1896 } 1897 } else if (mDragInfo != null) { 1898 ((CellLayout) getChildAt(mDragInfo.screen)).onDropChild(mDragInfo.cell); 1899 } 1900 1901 mDragOutline = null; 1902 mDragInfo = null; 1903 } 1904 1905 public boolean isDropEnabled() { 1906 return true; 1907 } 1908 1909 @Override 1910 protected void onRestoreInstanceState(Parcelable state) { 1911 super.onRestoreInstanceState(state); 1912 Launcher.setScreen(mCurrentPage); 1913 } 1914 1915 @Override 1916 public void scrollLeft() { 1917 if (!mIsSmall && !mIsInUnshrinkAnimation) { 1918 super.scrollLeft(); 1919 } 1920 } 1921 1922 @Override 1923 public void scrollRight() { 1924 if (!mIsSmall && !mIsInUnshrinkAnimation) { 1925 super.scrollRight(); 1926 } 1927 } 1928 1929 @Override 1930 public void onEnterScrollArea(int direction) { 1931 if (!mIsSmall && !mIsInUnshrinkAnimation) { 1932 mInScrollArea = true; 1933 final int screen = getCurrentPage() + ((direction == DragController.SCROLL_LEFT) ? -1 : 1); 1934 if (0 <= screen && screen < getChildCount()) { 1935 ((CellLayout) getChildAt(screen)).setHover(true); 1936 1937 if (mDragTargetLayout != null) { 1938 mDragTargetLayout.onDragExit(); 1939 mDragTargetLayout = null; 1940 } 1941 } 1942 } 1943 } 1944 1945 private void clearAllHovers() { 1946 final int childCount = getChildCount(); 1947 for (int i = 0; i < childCount; i++) { 1948 ((CellLayout) getChildAt(i)).setHover(false); 1949 } 1950 } 1951 1952 @Override 1953 public void onExitScrollArea() { 1954 if (mInScrollArea) { 1955 mInScrollArea = false; 1956 clearAllHovers(); 1957 } 1958 } 1959 1960 public Folder getFolderForTag(Object tag) { 1961 final int screenCount = getChildCount(); 1962 for (int screen = 0; screen < screenCount; screen++) { 1963 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1964 int count = currentScreen.getChildCount(); 1965 for (int i = 0; i < count; i++) { 1966 View child = currentScreen.getChildAt(i); 1967 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 1968 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { 1969 Folder f = (Folder) child; 1970 if (f.getInfo() == tag && f.getInfo().opened) { 1971 return f; 1972 } 1973 } 1974 } 1975 } 1976 return null; 1977 } 1978 1979 public View getViewForTag(Object tag) { 1980 int screenCount = getChildCount(); 1981 for (int screen = 0; screen < screenCount; screen++) { 1982 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1983 int count = currentScreen.getChildCount(); 1984 for (int i = 0; i < count; i++) { 1985 View child = currentScreen.getChildAt(i); 1986 if (child.getTag() == tag) { 1987 return child; 1988 } 1989 } 1990 } 1991 return null; 1992 } 1993 1994 1995 void removeItems(final ArrayList<ApplicationInfo> apps) { 1996 final int screenCount = getChildCount(); 1997 final PackageManager manager = getContext().getPackageManager(); 1998 final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); 1999 2000 final HashSet<String> packageNames = new HashSet<String>(); 2001 final int appCount = apps.size(); 2002 for (int i = 0; i < appCount; i++) { 2003 packageNames.add(apps.get(i).componentName.getPackageName()); 2004 } 2005 2006 for (int i = 0; i < screenCount; i++) { 2007 final CellLayout layout = (CellLayout) getChildAt(i); 2008 2009 // Avoid ANRs by treating each screen separately 2010 post(new Runnable() { 2011 public void run() { 2012 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 2013 childrenToRemove.clear(); 2014 2015 int childCount = layout.getChildCount(); 2016 for (int j = 0; j < childCount; j++) { 2017 final View view = layout.getChildAt(j); 2018 Object tag = view.getTag(); 2019 2020 if (tag instanceof ShortcutInfo) { 2021 final ShortcutInfo info = (ShortcutInfo) tag; 2022 final Intent intent = info.intent; 2023 final ComponentName name = intent.getComponent(); 2024 2025 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 2026 for (String packageName: packageNames) { 2027 if (packageName.equals(name.getPackageName())) { 2028 LauncherModel.deleteItemFromDatabase(mLauncher, info); 2029 childrenToRemove.add(view); 2030 } 2031 } 2032 } 2033 } else if (tag instanceof UserFolderInfo) { 2034 final UserFolderInfo info = (UserFolderInfo) tag; 2035 final ArrayList<ShortcutInfo> contents = info.contents; 2036 final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1); 2037 final int contentsCount = contents.size(); 2038 boolean removedFromFolder = false; 2039 2040 for (int k = 0; k < contentsCount; k++) { 2041 final ShortcutInfo appInfo = contents.get(k); 2042 final Intent intent = appInfo.intent; 2043 final ComponentName name = intent.getComponent(); 2044 2045 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 2046 for (String packageName: packageNames) { 2047 if (packageName.equals(name.getPackageName())) { 2048 toRemove.add(appInfo); 2049 LauncherModel.deleteItemFromDatabase(mLauncher, appInfo); 2050 removedFromFolder = true; 2051 } 2052 } 2053 } 2054 } 2055 2056 contents.removeAll(toRemove); 2057 if (removedFromFolder) { 2058 final Folder folder = getOpenFolder(); 2059 if (folder != null) 2060 folder.notifyDataSetChanged(); 2061 } 2062 } else if (tag instanceof LiveFolderInfo) { 2063 final LiveFolderInfo info = (LiveFolderInfo) tag; 2064 final Uri uri = info.uri; 2065 final ProviderInfo providerInfo = manager.resolveContentProvider( 2066 uri.getAuthority(), 0); 2067 2068 if (providerInfo != null) { 2069 for (String packageName: packageNames) { 2070 if (packageName.equals(providerInfo.packageName)) { 2071 LauncherModel.deleteItemFromDatabase(mLauncher, info); 2072 childrenToRemove.add(view); 2073 } 2074 } 2075 } 2076 } else if (tag instanceof LauncherAppWidgetInfo) { 2077 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; 2078 final AppWidgetProviderInfo provider = 2079 widgets.getAppWidgetInfo(info.appWidgetId); 2080 if (provider != null) { 2081 for (String packageName: packageNames) { 2082 if (packageName.equals(provider.provider.getPackageName())) { 2083 LauncherModel.deleteItemFromDatabase(mLauncher, info); 2084 childrenToRemove.add(view); 2085 } 2086 } 2087 } 2088 } 2089 } 2090 2091 childCount = childrenToRemove.size(); 2092 for (int j = 0; j < childCount; j++) { 2093 View child = childrenToRemove.get(j); 2094 layout.removeViewInLayout(child); 2095 if (child instanceof DropTarget) { 2096 mDragController.removeDropTarget((DropTarget)child); 2097 } 2098 } 2099 2100 if (childCount > 0) { 2101 layout.requestLayout(); 2102 layout.invalidate(); 2103 } 2104 } 2105 }); 2106 } 2107 } 2108 2109 void updateShortcuts(ArrayList<ApplicationInfo> apps) { 2110 final int screenCount = getChildCount(); 2111 for (int i = 0; i < screenCount; i++) { 2112 final CellLayout layout = (CellLayout) getChildAt(i); 2113 int childCount = layout.getChildCount(); 2114 for (int j = 0; j < childCount; j++) { 2115 final View view = layout.getChildAt(j); 2116 Object tag = view.getTag(); 2117 if (tag instanceof ShortcutInfo) { 2118 ShortcutInfo info = (ShortcutInfo)tag; 2119 // We need to check for ACTION_MAIN otherwise getComponent() might 2120 // return null for some shortcuts (for instance, for shortcuts to 2121 // web pages.) 2122 final Intent intent = info.intent; 2123 final ComponentName name = intent.getComponent(); 2124 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 2125 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 2126 final int appCount = apps.size(); 2127 for (int k = 0; k < appCount; k++) { 2128 ApplicationInfo app = apps.get(k); 2129 if (app.componentName.equals(name)) { 2130 info.setIcon(mIconCache.getIcon(info.intent)); 2131 ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null, 2132 new FastBitmapDrawable(info.getIcon(mIconCache)), 2133 null, null); 2134 } 2135 } 2136 } 2137 } 2138 } 2139 } 2140 } 2141 2142 void moveToDefaultScreen(boolean animate) { 2143 if (mIsSmall || mIsInUnshrinkAnimation) { 2144 mLauncher.showWorkspace(animate, (CellLayout)getChildAt(mDefaultPage)); 2145 } else if (animate) { 2146 snapToPage(mDefaultPage); 2147 } else { 2148 setCurrentPage(mDefaultPage); 2149 } 2150 getChildAt(mDefaultPage).requestFocus(); 2151 } 2152 2153 void setIndicators(Drawable previous, Drawable next) { 2154 mPreviousIndicator = previous; 2155 mNextIndicator = next; 2156 previous.setLevel(mCurrentPage); 2157 next.setLevel(mCurrentPage); 2158 } 2159 2160 @Override 2161 public void syncPages() { 2162 } 2163 2164 @Override 2165 public void syncPageItems(int page) { 2166 } 2167 2168} 2169