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