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