Workspace.java revision 40e882bf19a837ee62ec328c5748b75be7960d39
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 android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.animation.TimeInterpolator; 24import android.animation.ValueAnimator; 25import android.animation.Animator.AnimatorListener; 26import android.animation.ValueAnimator.AnimatorUpdateListener; 27import android.app.AlertDialog; 28import android.app.WallpaperManager; 29import android.appwidget.AppWidgetManager; 30import android.appwidget.AppWidgetProviderInfo; 31import android.content.ClipData; 32import android.content.ClipDescription; 33import android.content.ComponentName; 34import android.content.Context; 35import android.content.Intent; 36import android.content.pm.PackageManager; 37import android.content.res.Resources; 38import android.content.res.TypedArray; 39import android.graphics.Bitmap; 40import android.graphics.Camera; 41import android.graphics.Canvas; 42import android.graphics.Color; 43import android.graphics.Matrix; 44import android.graphics.Paint; 45import android.graphics.Rect; 46import android.graphics.RectF; 47import android.graphics.Region.Op; 48import android.graphics.drawable.Drawable; 49import android.os.IBinder; 50import android.os.Parcelable; 51import android.util.AttributeSet; 52import android.util.DisplayMetrics; 53import android.util.Log; 54import android.util.Pair; 55import android.view.Display; 56import android.view.DragEvent; 57import android.view.MotionEvent; 58import android.view.View; 59import android.view.ViewGroup; 60import android.view.animation.DecelerateInterpolator; 61import android.widget.TextView; 62import android.widget.Toast; 63 64import com.android.launcher.R; 65import com.android.launcher2.FolderIcon.FolderRingAnimator; 66import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData; 67 68import java.util.ArrayList; 69import java.util.HashSet; 70import java.util.List; 71 72/** 73 * The workspace is a wide area with a wallpaper and a finite number of pages. 74 * Each page contains a number of icons, folders or widgets the user can 75 * interact with. A workspace is meant to be used with a fixed width only. 76 */ 77public class Workspace extends SmoothPagedView 78 implements DropTarget, DragSource, DragScroller, View.OnTouchListener, 79 DragController.DragListener { 80 @SuppressWarnings({"UnusedDeclaration"}) 81 private static final String TAG = "Launcher.Workspace"; 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 CHILDREN_OUTLINE_FADE_OUT_DELAY = 0; 93 private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375; 94 private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100; 95 96 private static final int BACKGROUND_FADE_OUT_DURATION = 350; 97 private static final int BACKGROUND_FADE_IN_DURATION = 350; 98 99 // These animators are used to fade the children's outlines 100 private ObjectAnimator mChildrenOutlineFadeInAnimation; 101 private ObjectAnimator mChildrenOutlineFadeOutAnimation; 102 private float mChildrenOutlineAlpha = 0; 103 104 // These properties refer to the background protection gradient used for AllApps and Customize 105 private ValueAnimator mBackgroundFadeInAnimation; 106 private ValueAnimator mBackgroundFadeOutAnimation; 107 private Drawable mBackground; 108 boolean mDrawBackground = true; 109 private float mBackgroundAlpha = 0; 110 private float mOverScrollMaxBackgroundAlpha = 0.0f; 111 private int mOverScrollPageIndex = -1; 112 113 private final WallpaperManager mWallpaperManager; 114 private IBinder mWindowToken; 115 116 private int mDefaultPage; 117 118 /** 119 * CellInfo for the cell that is currently being dragged 120 */ 121 private CellLayout.CellInfo mDragInfo; 122 123 /** 124 * Target drop area calculated during last acceptDrop call. 125 */ 126 private int[] mTargetCell = new int[2]; 127 128 /** 129 * The CellLayout that is currently being dragged over 130 */ 131 private CellLayout mDragTargetLayout = null; 132 133 private Launcher mLauncher; 134 private IconCache mIconCache; 135 private DragController mDragController; 136 137 // These are temporary variables to prevent having to allocate a new object just to 138 // return an (x, y) value from helper functions. Do NOT use them to maintain other state. 139 private int[] mTempCell = new int[2]; 140 private int[] mTempEstimate = new int[2]; 141 private float[] mDragViewVisualCenter = new float[2]; 142 private float[] mTempDragCoordinates = new float[2]; 143 private float[] mTempTouchCoordinates = new float[2]; 144 private float[] mTempCellLayoutCenterCoordinates = new float[2]; 145 private float[] mTempDragBottomRightCoordinates = new float[2]; 146 private Matrix mTempInverseMatrix = new Matrix(); 147 148 private SpringLoadedDragController mSpringLoadedDragController; 149 private float mSpringLoadedShrinkFactor; 150 151 private static final int DEFAULT_CELL_COUNT_X = 4; 152 private static final int DEFAULT_CELL_COUNT_Y = 4; 153 154 // State variable that indicates whether the pages are small (ie when you're 155 // in all apps or customize mode) 156 157 enum State { NORMAL, SPRING_LOADED, SMALL }; 158 private State mState; 159 private boolean mIsSwitchingState = false; 160 161 private boolean mSwitchStateAfterFirstLayout = false; 162 private State mStateAfterFirstLayout; 163 164 private AnimatorSet mAnimator; 165 private AnimatorListener mShrinkAnimationListener; 166 private AnimatorListener mUnshrinkAnimationListener; 167 168 boolean mAnimatingViewIntoPlace = false; 169 boolean mIsDragOccuring = false; 170 boolean mChildrenLayersEnabled = true; 171 172 /** Is the user is dragging an item near the edge of a page? */ 173 private boolean mInScrollArea = false; 174 175 private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper(); 176 private Bitmap mDragOutline = null; 177 private final Rect mTempRect = new Rect(); 178 private final int[] mTempXY = new int[2]; 179 180 // Paint used to draw external drop outline 181 private final Paint mExternalDragOutlinePaint = new Paint(); 182 183 // Camera and Matrix used to determine the final position of a neighboring CellLayout 184 private final Matrix mMatrix = new Matrix(); 185 private final Camera mCamera = new Camera(); 186 private final float mTempFloat2[] = new float[2]; 187 188 enum WallpaperVerticalOffset { TOP, MIDDLE, BOTTOM }; 189 int mWallpaperWidth; 190 int mWallpaperHeight; 191 WallpaperOffsetInterpolator mWallpaperOffset; 192 boolean mUpdateWallpaperOffsetImmediately = false; 193 boolean mSyncWallpaperOffsetWithScroll = true; 194 private Runnable mDelayedResizeRunnable; 195 196 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts 197 private static final int FOLDER_CREATION_TIMEOUT = 250; 198 private final Alarm mFolderCreationAlarm = new Alarm(); 199 private FolderRingAnimator mDragFolderRingAnimator = null; 200 private View mLastDragOverView = null; 201 private boolean mCreateUserFolderOnDrop = false; 202 203 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget) 204 private float mXDown; 205 private float mYDown; 206 final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6; 207 final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3; 208 final static float TOUCH_SLOP_DAMPING_FACTOR = 4; 209 210 // These variables are used for storing the initial and final values during workspace animations 211 private float mCurrentScaleX; 212 private float mCurrentScaleY; 213 private float mCurrentRotationY; 214 private float mCurrentTranslationX; 215 private float mCurrentTranslationY; 216 private float[] mOldTranslationXs; 217 private float[] mOldTranslationYs; 218 private float[] mOldScaleXs; 219 private float[] mOldScaleYs; 220 private float[] mOldBackgroundAlphas; 221 private float[] mOldBackgroundAlphaMultipliers; 222 private float[] mOldAlphas; 223 private float[] mOldRotationYs; 224 private float[] mNewTranslationXs; 225 private float[] mNewTranslationYs; 226 private float[] mNewScaleXs; 227 private float[] mNewScaleYs; 228 private float[] mNewBackgroundAlphas; 229 private float[] mNewBackgroundAlphaMultipliers; 230 private float[] mNewAlphas; 231 private float[] mNewRotationYs; 232 private float mTransitionProgress; 233 234 /** 235 * Used to inflate the Workspace from XML. 236 * 237 * @param context The application's context. 238 * @param attrs The attributes set containing the Workspace's customization values. 239 */ 240 public Workspace(Context context, AttributeSet attrs) { 241 this(context, attrs, 0); 242 } 243 244 /** 245 * Used to inflate the Workspace from XML. 246 * 247 * @param context The application's context. 248 * @param attrs The attributes set containing the Workspace's customization values. 249 * @param defStyle Unused. 250 */ 251 public Workspace(Context context, AttributeSet attrs, int defStyle) { 252 super(context, attrs, defStyle); 253 mContentIsRefreshable = false; 254 255 // With workspace, data is available straight from the get-go 256 setDataIsReady(); 257 258 if (!LauncherApplication.isScreenLarge()) { 259 mFadeInAdjacentScreens = false; 260 } 261 262 mWallpaperManager = WallpaperManager.getInstance(context); 263 264 int cellCountX = DEFAULT_CELL_COUNT_X; 265 int cellCountY = DEFAULT_CELL_COUNT_Y; 266 267 TypedArray a = context.obtainStyledAttributes(attrs, 268 R.styleable.Workspace, defStyle, 0); 269 270 final Resources res = context.getResources(); 271 if (LauncherApplication.isScreenLarge()) { 272 // Determine number of rows/columns dynamically 273 // TODO: This code currently fails on tablets with an aspect ratio < 1.3. 274 // Around that ratio we should make cells the same size in portrait and 275 // landscape 276 TypedArray actionBarSizeTypedArray = 277 context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize }); 278 final float actionBarHeight = actionBarSizeTypedArray.getDimension(0, 0f); 279 final float systemBarHeight = res.getDimension(R.dimen.status_bar_height); 280 final float smallestScreenDim = res.getConfiguration().smallestScreenWidthDp; 281 282 cellCountX = 1; 283 while (CellLayout.widthInPortrait(res, cellCountX + 1) <= smallestScreenDim) { 284 cellCountX++; 285 } 286 287 cellCountY = 1; 288 while (actionBarHeight + CellLayout.heightInLandscape(res, cellCountY + 1) 289 <= smallestScreenDim - systemBarHeight) { 290 cellCountY++; 291 } 292 } 293 294 mSpringLoadedShrinkFactor = 295 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; 296 297 // if the value is manually specified, use that instead 298 cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX); 299 cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY); 300 mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1); 301 a.recycle(); 302 303 LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY); 304 setHapticFeedbackEnabled(false); 305 306 initWorkspace(); 307 308 // Disable multitouch across the workspace/all apps/customize tray 309 setMotionEventSplittingEnabled(true); 310 } 311 312 public void onDragStart(DragSource source, Object info, int dragAction) { 313 mIsDragOccuring = true; 314 updateChildrenLayersEnabled(); 315 mLauncher.lockScreenOrientation(); 316 } 317 318 public void onDragEnd() { 319 mIsDragOccuring = false; 320 updateChildrenLayersEnabled(); 321 mLauncher.unlockScreenOrientation(); 322 } 323 324 /** 325 * Initializes various states for this workspace. 326 */ 327 protected void initWorkspace() { 328 Context context = getContext(); 329 mCurrentPage = mDefaultPage; 330 Launcher.setScreen(mCurrentPage); 331 LauncherApplication app = (LauncherApplication)context.getApplicationContext(); 332 mIconCache = app.getIconCache(); 333 mExternalDragOutlinePaint.setAntiAlias(true); 334 setWillNotDraw(false); 335 336 try { 337 final Resources res = getResources(); 338 mBackground = res.getDrawable(R.drawable.apps_customize_bg); 339 } catch (Resources.NotFoundException e) { 340 // In this case, we will skip drawing background protection 341 } 342 343 mUnshrinkAnimationListener = new AnimatorListenerAdapter() { 344 @Override 345 public void onAnimationStart(Animator animation) { 346 mIsSwitchingState = true; 347 } 348 349 @Override 350 public void onAnimationEnd(Animator animation) { 351 mIsSwitchingState = false; 352 mSyncWallpaperOffsetWithScroll = true; 353 mWallpaperOffset.setOverrideHorizontalCatchupConstant(false); 354 mAnimator = null; 355 updateChildrenLayersEnabled(); 356 } 357 }; 358 mShrinkAnimationListener = new AnimatorListenerAdapter() { 359 @Override 360 public void onAnimationStart(Animator animation) { 361 mIsSwitchingState = true; 362 } 363 @Override 364 public void onAnimationEnd(Animator animation) { 365 mIsSwitchingState = false; 366 mWallpaperOffset.setOverrideHorizontalCatchupConstant(false); 367 mAnimator = null; 368 } 369 }; 370 mSnapVelocity = 600; 371 mWallpaperOffset = new WallpaperOffsetInterpolator(); 372 } 373 374 @Override 375 protected int getScrollMode() { 376 return SmoothPagedView.X_LARGE_MODE; 377 } 378 379 private void onAddView(View child) { 380 if (!(child instanceof CellLayout)) { 381 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 382 } 383 CellLayout cl = ((CellLayout) child); 384 cl.setOnInterceptTouchListener(this); 385 cl.setClickable(true); 386 cl.enableHardwareLayers(); 387 } 388 389 @Override 390 public void addView(View child, int index, LayoutParams params) { 391 onAddView(child); 392 super.addView(child, index, params); 393 } 394 395 @Override 396 public void addView(View child) { 397 onAddView(child); 398 super.addView(child); 399 } 400 401 @Override 402 public void addView(View child, int index) { 403 onAddView(child); 404 super.addView(child, index); 405 } 406 407 @Override 408 public void addView(View child, int width, int height) { 409 onAddView(child); 410 super.addView(child, width, height); 411 } 412 413 @Override 414 public void addView(View child, LayoutParams params) { 415 onAddView(child); 416 super.addView(child, params); 417 } 418 419 /** 420 * @return The open folder on the current screen, or null if there is none 421 */ 422 Folder getOpenFolder() { 423 DragLayer dragLayer = mLauncher.getDragLayer(); 424 int count = dragLayer.getChildCount(); 425 for (int i = 0; i < count; i++) { 426 View child = dragLayer.getChildAt(i); 427 if (child instanceof Folder) { 428 Folder folder = (Folder) child; 429 if (folder.getInfo().opened) 430 return folder; 431 } 432 } 433 return null; 434 } 435 436 boolean isTouchActive() { 437 return mTouchState != TOUCH_STATE_REST; 438 } 439 440 /** 441 * Adds the specified child in the specified screen. The position and dimension of 442 * the child are defined by x, y, spanX and spanY. 443 * 444 * @param child The child to add in one of the workspace's screens. 445 * @param screen The screen in which to add the child. 446 * @param x The X position of the child in the screen's grid. 447 * @param y The Y position of the child in the screen's grid. 448 * @param spanX The number of cells spanned horizontally by the child. 449 * @param spanY The number of cells spanned vertically by the child. 450 */ 451 void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY) { 452 addInScreen(child, container, screen, x, y, spanX, spanY, false); 453 } 454 455 /** 456 * Adds the specified child in the specified screen. The position and dimension of 457 * the child are defined by x, y, spanX and spanY. 458 * 459 * @param child The child to add in one of the workspace's screens. 460 * @param screen The screen in which to add the child. 461 * @param x The X position of the child in the screen's grid. 462 * @param y The Y position of the child in the screen's grid. 463 * @param spanX The number of cells spanned horizontally by the child. 464 * @param spanY The number of cells spanned vertically by the child. 465 * @param insert When true, the child is inserted at the beginning of the children list. 466 */ 467 void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY, 468 boolean insert) { 469 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { 470 if (screen < 0 || screen >= getChildCount()) { 471 Log.e(TAG, "The screen must be >= 0 and < " + getChildCount() 472 + " (was " + screen + "); skipping child"); 473 return; 474 } 475 } 476 477 final CellLayout layout; 478 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { 479 layout = mLauncher.getHotseat().getLayout(); 480 child.setOnKeyListener(null); 481 482 if (screen < 0) { 483 screen = mLauncher.getHotseat().getOrderInHotseat(x, y); 484 } else { 485 // Note: We do this to ensure that the hotseat is always laid out in the orientation 486 // of the hotseat in order regardless of which orientation they were added 487 x = mLauncher.getHotseat().getCellXFromOrder(screen); 488 y = mLauncher.getHotseat().getCellYFromOrder(screen); 489 } 490 } else { 491 layout = (CellLayout) getChildAt(screen); 492 child.setOnKeyListener(new BubbleTextViewKeyEventListener()); 493 } 494 495 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 496 if (lp == null) { 497 lp = new CellLayout.LayoutParams(x, y, spanX, spanY); 498 } else { 499 lp.cellX = x; 500 lp.cellY = y; 501 lp.cellHSpan = spanX; 502 lp.cellVSpan = spanY; 503 } 504 505 if (spanX < 0 && spanY < 0) { 506 lp.isLockedToGrid = false; 507 } 508 509 // Get the canonical child id to uniquely represent this view in this screen 510 int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY); 511 boolean markCellsAsOccupied = !(child instanceof Folder); 512 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) { 513 // TODO: This branch occurs when the workspace is adding views 514 // outside of the defined grid 515 // maybe we should be deleting these items from the LauncherModel? 516 Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout"); 517 } 518 519 if (!(child instanceof Folder)) { 520 child.setHapticFeedbackEnabled(false); 521 child.setOnLongClickListener(mLongClickListener); 522 } 523 if (child instanceof DropTarget) { 524 mDragController.addDropTarget((DropTarget) child); 525 } 526 } 527 528 /** 529 * Check if the point (x, y) hits a given page. 530 */ 531 private boolean hitsPage(int index, float x, float y) { 532 final View page = getChildAt(index); 533 if (page != null) { 534 float[] localXY = { x, y }; 535 mapPointFromSelfToChild(page, localXY); 536 return (localXY[0] >= 0 && localXY[0] < page.getWidth() 537 && localXY[1] >= 0 && localXY[1] < page.getHeight()); 538 } 539 return false; 540 } 541 542 @Override 543 protected boolean hitsPreviousPage(float x, float y) { 544 // mNextPage is set to INVALID_PAGE whenever we are stationary. 545 // Calculating "next page" this way ensures that you scroll to whatever page you tap on 546 final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage; 547 return hitsPage(current - 1, x, y); 548 } 549 550 @Override 551 protected boolean hitsNextPage(float x, float y) { 552 // mNextPage is set to INVALID_PAGE whenever we are stationary. 553 // Calculating "next page" this way ensures that you scroll to whatever page you tap on 554 final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage; 555 return hitsPage(current + 1, x, y); 556 } 557 558 /** 559 * Called directly from a CellLayout (not by the framework), after we've been added as a 560 * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout 561 * that it should intercept touch events, which is not something that is normally supported. 562 */ 563 @Override 564 public boolean onTouch(View v, MotionEvent event) { 565 return (isSmall() || mIsSwitchingState); 566 } 567 568 public boolean isSwitchingState() { 569 return mIsSwitchingState; 570 } 571 572 protected void onWindowVisibilityChanged (int visibility) { 573 mLauncher.onWindowVisibilityChanged(visibility); 574 } 575 576 @Override 577 public boolean dispatchUnhandledMove(View focused, int direction) { 578 if (isSmall() || mIsSwitchingState) { 579 // when the home screens are shrunken, shouldn't allow side-scrolling 580 return false; 581 } 582 return super.dispatchUnhandledMove(focused, direction); 583 } 584 585 @Override 586 public boolean onInterceptTouchEvent(MotionEvent ev) { 587 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 588 mXDown = ev.getX(); 589 mYDown = ev.getY(); 590 } 591 592 return super.onInterceptTouchEvent(ev); 593 } 594 595 @Override 596 protected void determineScrollingStart(MotionEvent ev) { 597 if (!isSmall() && !mIsSwitchingState) { 598 float deltaX = Math.abs(ev.getX() - mXDown); 599 float deltaY = Math.abs(ev.getY() - mYDown); 600 601 if (Float.compare(deltaX, 0f) == 0) return; 602 603 float slope = deltaY / deltaX; 604 float theta = (float) Math.atan(slope); 605 606 if (deltaX > mTouchSlop || deltaY > mTouchSlop) { 607 cancelCurrentPageLongPress(); 608 } 609 610 if (theta > MAX_SWIPE_ANGLE) { 611 // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace 612 return; 613 } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) { 614 // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to 615 // increase the touch slop to make it harder to begin scrolling the workspace. This 616 // results in vertically scrolling widgets to more easily. The higher the angle, the 617 // more we increase touch slop. 618 theta -= START_DAMPING_TOUCH_SLOP_ANGLE; 619 float extraRatio = (float) 620 Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE))); 621 super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio); 622 } else { 623 // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special 624 super.determineScrollingStart(ev); 625 } 626 } 627 } 628 629 @Override 630 protected boolean isScrollingIndicatorEnabled() { 631 return mState != State.SPRING_LOADED; 632 } 633 634 protected void onPageBeginMoving() { 635 super.onPageBeginMoving(); 636 637 if (isHardwareAccelerated()) { 638 updateChildrenLayersEnabled(); 639 } else { 640 if (mNextPage != INVALID_PAGE) { 641 // we're snapping to a particular screen 642 enableChildrenCache(mCurrentPage, mNextPage); 643 } else { 644 // this is when user is actively dragging a particular screen, they might 645 // swipe it either left or right (but we won't advance by more than one screen) 646 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1); 647 } 648 } 649 650 // Only show page outlines as we pan if we are on large screen 651 if (LauncherApplication.isScreenLarge()) { 652 showOutlines(); 653 } 654 } 655 656 protected void onPageEndMoving() { 657 super.onPageEndMoving(); 658 659 if (isHardwareAccelerated()) { 660 updateChildrenLayersEnabled(); 661 } else { 662 clearChildrenCache(); 663 } 664 665 // Hide the outlines, as long as we're not dragging 666 if (!mDragController.dragging()) { 667 // Only hide page outlines as we pan if we are on large screen 668 if (LauncherApplication.isScreenLarge()) { 669 hideOutlines(); 670 } 671 } 672 mOverScrollMaxBackgroundAlpha = 0.0f; 673 mOverScrollPageIndex = -1; 674 675 if (mDelayedResizeRunnable != null) { 676 mDelayedResizeRunnable.run(); 677 mDelayedResizeRunnable = null; 678 } 679 } 680 681 @Override 682 protected void notifyPageSwitchListener() { 683 super.notifyPageSwitchListener(); 684 Launcher.setScreen(mCurrentPage); 685 }; 686 687 // As a ratio of screen height, the total distance we want the parallax effect to span 688 // vertically 689 private float wallpaperTravelToScreenHeightRatio(int width, int height) { 690 return 1.1f; 691 } 692 693 // As a ratio of screen height, the total distance we want the parallax effect to span 694 // horizontally 695 private float wallpaperTravelToScreenWidthRatio(int width, int height) { 696 float aspectRatio = width / (float) height; 697 698 // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width 699 // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width 700 // We will use these two data points to extrapolate how much the wallpaper parallax effect 701 // to span (ie travel) at any aspect ratio: 702 703 final float ASPECT_RATIO_LANDSCAPE = 16/10f; 704 final float ASPECT_RATIO_PORTRAIT = 10/16f; 705 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f; 706 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f; 707 708 // To find out the desired width at different aspect ratios, we use the following two 709 // formulas, where the coefficient on x is the aspect ratio (width/height): 710 // (16/10)x + y = 1.5 711 // (10/16)x + y = 1.2 712 // We solve for x and y and end up with a final formula: 713 final float x = 714 (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) / 715 (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); 716 final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; 717 return x * aspectRatio + y; 718 } 719 720 // The range of scroll values for Workspace 721 private int getScrollRange() { 722 return getChildOffset(getChildCount() - 1) - getChildOffset(0); 723 } 724 725 protected void setWallpaperDimension() { 726 Display display = mLauncher.getWindowManager().getDefaultDisplay(); 727 DisplayMetrics displayMetrics = new DisplayMetrics(); 728 display.getRealMetrics(displayMetrics); 729 final int maxDim = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels); 730 final int minDim = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels); 731 732 // We need to ensure that there is enough extra space in the wallpaper for the intended 733 // parallax effects 734 mWallpaperWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim)); 735 mWallpaperHeight = (int)(maxDim * wallpaperTravelToScreenHeightRatio(maxDim, minDim)); 736 new Thread("setWallpaperDimension") { 737 public void run() { 738 mWallpaperManager.suggestDesiredDimensions(mWallpaperWidth, mWallpaperHeight); 739 } 740 }.start(); 741 } 742 743 public void setVerticalWallpaperOffset(float offset) { 744 mWallpaperOffset.setFinalY(offset); 745 } 746 public float getVerticalWallpaperOffset() { 747 return mWallpaperOffset.getCurrY(); 748 } 749 public void setHorizontalWallpaperOffset(float offset) { 750 mWallpaperOffset.setFinalX(offset); 751 } 752 public float getHorizontalWallpaperOffset() { 753 return mWallpaperOffset.getCurrX(); 754 } 755 756 private float wallpaperOffsetForCurrentScroll() { 757 Display display = mLauncher.getWindowManager().getDefaultDisplay(); 758 final boolean isStaticWallpaper = (mWallpaperManager.getWallpaperInfo() == null); 759 // The wallpaper travel width is how far, from left to right, the wallpaper will move 760 // at this orientation (for example, in portrait mode we don't move all the way to the 761 // edges of the wallpaper, or otherwise the parallax effect would be too strong) 762 int wallpaperTravelWidth = (int) (display.getWidth() * 763 wallpaperTravelToScreenWidthRatio(display.getWidth(), display.getHeight())); 764 if (!isStaticWallpaper) { 765 wallpaperTravelWidth = mWallpaperWidth; 766 } 767 768 // Set wallpaper offset steps (1 / (number of screens - 1)) 769 // We have 3 vertical offset states (centered, and then top/bottom aligned 770 // for all apps/customize) 771 mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f / (3 - 1)); 772 773 int scrollRange = getScrollRange(); 774 float scrollProgressOffset = 0; 775 776 // Account for overscroll: you only see the absolute edge of the wallpaper if 777 // you overscroll as far as you can in landscape mode. Only do this for static wallpapers 778 // because live wallpapers (and probably 3rd party wallpaper providers) rely on the offset 779 // being even intervals from 0 to 1 (eg [0, 0.25, 0.5, 0.75, 1]) 780 if (isStaticWallpaper) { 781 int overscrollOffset = (int) (maxOverScroll() * display.getWidth()); 782 scrollProgressOffset += overscrollOffset / (float) getScrollRange(); 783 scrollRange += 2 * overscrollOffset; 784 } 785 786 float scrollProgress = 787 mScrollX / (float) scrollRange + scrollProgressOffset; 788 float offsetInDips = wallpaperTravelWidth * scrollProgress + 789 (mWallpaperWidth - wallpaperTravelWidth) / 2; // center it 790 float offset = offsetInDips / (float) mWallpaperWidth; 791 return offset; 792 } 793 private void syncWallpaperOffsetWithScroll() { 794 final boolean enableWallpaperEffects = isHardwareAccelerated(); 795 if (enableWallpaperEffects) { 796 mWallpaperOffset.setFinalX(wallpaperOffsetForCurrentScroll()); 797 } 798 } 799 800 public void updateWallpaperOffsetImmediately() { 801 mUpdateWallpaperOffsetImmediately = true; 802 } 803 804 private void updateWallpaperOffsets() { 805 boolean updateNow = false; 806 boolean keepUpdating = true; 807 if (mUpdateWallpaperOffsetImmediately) { 808 updateNow = true; 809 keepUpdating = false; 810 mWallpaperOffset.jumpToFinal(); 811 mUpdateWallpaperOffsetImmediately = false; 812 } else { 813 updateNow = keepUpdating = mWallpaperOffset.computeScrollOffset(); 814 } 815 if (updateNow) { 816 if (mWindowToken != null) { 817 mWallpaperManager.setWallpaperOffsets(mWindowToken, 818 mWallpaperOffset.getCurrX(), mWallpaperOffset.getCurrY()); 819 } 820 } 821 if (keepUpdating) { 822 fastInvalidate(); 823 } 824 } 825 826 class WallpaperOffsetInterpolator { 827 float mFinalHorizontalWallpaperOffset = 0.0f; 828 float mFinalVerticalWallpaperOffset = 0.5f; 829 float mHorizontalWallpaperOffset = 0.0f; 830 float mVerticalWallpaperOffset = 0.5f; 831 long mLastWallpaperOffsetUpdateTime; 832 boolean mIsMovingFast; 833 boolean mOverrideHorizontalCatchupConstant; 834 float mHorizontalCatchupConstant = 0.35f; 835 float mVerticalCatchupConstant = 0.35f; 836 837 public WallpaperOffsetInterpolator() { 838 } 839 840 public void setOverrideHorizontalCatchupConstant(boolean override) { 841 mOverrideHorizontalCatchupConstant = override; 842 } 843 844 public void setHorizontalCatchupConstant(float f) { 845 mHorizontalCatchupConstant = f; 846 } 847 848 public void setVerticalCatchupConstant(float f) { 849 mVerticalCatchupConstant = f; 850 } 851 852 public boolean computeScrollOffset() { 853 if (Float.compare(mHorizontalWallpaperOffset, mFinalHorizontalWallpaperOffset) == 0 && 854 Float.compare(mVerticalWallpaperOffset, mFinalVerticalWallpaperOffset) == 0) { 855 mIsMovingFast = false; 856 return false; 857 } 858 Display display = mLauncher.getWindowManager().getDefaultDisplay(); 859 boolean isLandscape = display.getWidth() > display.getHeight(); 860 861 long currentTime = System.currentTimeMillis(); 862 long timeSinceLastUpdate = currentTime - mLastWallpaperOffsetUpdateTime; 863 timeSinceLastUpdate = Math.min((long) (1000/30f), timeSinceLastUpdate); 864 timeSinceLastUpdate = Math.max(1L, timeSinceLastUpdate); 865 866 float xdiff = Math.abs(mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset); 867 if (!mIsMovingFast && xdiff > 0.07) { 868 mIsMovingFast = true; 869 } 870 871 float fractionToCatchUpIn1MsHorizontal; 872 if (mOverrideHorizontalCatchupConstant) { 873 fractionToCatchUpIn1MsHorizontal = mHorizontalCatchupConstant; 874 } else if (mIsMovingFast) { 875 fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.5f : 0.75f; 876 } else { 877 // slow 878 fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.27f : 0.5f; 879 } 880 float fractionToCatchUpIn1MsVertical = mVerticalCatchupConstant; 881 882 fractionToCatchUpIn1MsHorizontal /= 33f; 883 fractionToCatchUpIn1MsVertical /= 33f; 884 885 final float UPDATE_THRESHOLD = 0.00001f; 886 float hOffsetDelta = mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset; 887 float vOffsetDelta = mFinalVerticalWallpaperOffset - mVerticalWallpaperOffset; 888 boolean jumpToFinalValue = Math.abs(hOffsetDelta) < UPDATE_THRESHOLD && 889 Math.abs(vOffsetDelta) < UPDATE_THRESHOLD; 890 if (jumpToFinalValue) { 891 mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset; 892 mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset; 893 } else { 894 float percentToCatchUpVertical = 895 Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsVertical); 896 float percentToCatchUpHorizontal = 897 Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsHorizontal); 898 mHorizontalWallpaperOffset += percentToCatchUpHorizontal * hOffsetDelta; 899 mVerticalWallpaperOffset += percentToCatchUpVertical * vOffsetDelta; 900 } 901 902 mLastWallpaperOffsetUpdateTime = System.currentTimeMillis(); 903 return true; 904 } 905 906 public float getCurrX() { 907 return mHorizontalWallpaperOffset; 908 } 909 910 public float getFinalX() { 911 return mFinalHorizontalWallpaperOffset; 912 } 913 914 public float getCurrY() { 915 return mVerticalWallpaperOffset; 916 } 917 918 public float getFinalY() { 919 return mFinalVerticalWallpaperOffset; 920 } 921 922 public void setFinalX(float x) { 923 mFinalHorizontalWallpaperOffset = Math.max(0f, Math.min(x, 1.0f)); 924 } 925 926 public void setFinalY(float y) { 927 mFinalVerticalWallpaperOffset = Math.max(0f, Math.min(y, 1.0f)); 928 } 929 930 public void jumpToFinal() { 931 mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset; 932 mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset; 933 } 934 } 935 936 @Override 937 public void computeScroll() { 938 super.computeScroll(); 939 if (mSyncWallpaperOffsetWithScroll) { 940 syncWallpaperOffsetWithScroll(); 941 } 942 } 943 944 void showOutlines() { 945 if (!isSmall() && !mIsSwitchingState) { 946 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel(); 947 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel(); 948 mChildrenOutlineFadeInAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 1.0f); 949 mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION); 950 mChildrenOutlineFadeInAnimation.start(); 951 } 952 } 953 954 void hideOutlines() { 955 if (!isSmall() && !mIsSwitchingState) { 956 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel(); 957 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel(); 958 mChildrenOutlineFadeOutAnimation = ObjectAnimator.ofFloat(this, "childrenOutlineAlpha", 0.0f); 959 mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION); 960 mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY); 961 mChildrenOutlineFadeOutAnimation.start(); 962 } 963 } 964 965 public void showOutlinesTemporarily() { 966 if (!mIsPageMoving && !isTouchActive()) { 967 snapToPage(mCurrentPage); 968 } 969 } 970 971 public void setChildrenOutlineAlpha(float alpha) { 972 mChildrenOutlineAlpha = alpha; 973 for (int i = 0; i < getChildCount(); i++) { 974 CellLayout cl = (CellLayout) getChildAt(i); 975 cl.setBackgroundAlpha(alpha); 976 } 977 } 978 979 public float getChildrenOutlineAlpha() { 980 return mChildrenOutlineAlpha; 981 } 982 983 void disableBackground() { 984 mDrawBackground = false; 985 } 986 void enableBackground() { 987 mDrawBackground = true; 988 } 989 990 private void showBackgroundGradientForAllApps() { 991 showBackgroundGradient(); 992 } 993 994 private void showBackgroundGradient() { 995 if (mBackground == null) return; 996 if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel(); 997 if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel(); 998 mBackgroundFadeInAnimation = ValueAnimator.ofFloat(getBackgroundAlpha(), 1f); 999 mBackgroundFadeInAnimation.addUpdateListener(new AnimatorUpdateListener() { 1000 public void onAnimationUpdate(ValueAnimator animation) { 1001 setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue()); 1002 } 1003 }); 1004 mBackgroundFadeInAnimation.setInterpolator(new DecelerateInterpolator(1.5f)); 1005 mBackgroundFadeInAnimation.setDuration(BACKGROUND_FADE_IN_DURATION); 1006 mBackgroundFadeInAnimation.start(); 1007 } 1008 1009 private void hideBackgroundGradient(float finalAlpha) { 1010 if (mBackground == null) return; 1011 if (mBackgroundFadeInAnimation != null) mBackgroundFadeInAnimation.cancel(); 1012 if (mBackgroundFadeOutAnimation != null) mBackgroundFadeOutAnimation.cancel(); 1013 mBackgroundFadeOutAnimation = ValueAnimator.ofFloat(getBackgroundAlpha(), finalAlpha); 1014 mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() { 1015 public void onAnimationUpdate(ValueAnimator animation) { 1016 setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue()); 1017 } 1018 }); 1019 mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f)); 1020 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION); 1021 mBackgroundFadeOutAnimation.start(); 1022 } 1023 1024 public void setBackgroundAlpha(float alpha) { 1025 if (alpha != mBackgroundAlpha) { 1026 mBackgroundAlpha = alpha; 1027 invalidate(); 1028 } 1029 } 1030 1031 public float getBackgroundAlpha() { 1032 return mBackgroundAlpha; 1033 } 1034 1035 /** 1036 * Due to 3D transformations, if two CellLayouts are theoretically touching each other, 1037 * on the xy plane, when one is rotated along the y-axis, the gap between them is perceived 1038 * as being larger. This method computes what offset the rotated view should be translated 1039 * in order to minimize this perceived gap. 1040 * @param degrees Angle of the view 1041 * @param width Width of the view 1042 * @param height Height of the view 1043 * @return Offset to be used in a View.setTranslationX() call 1044 */ 1045 private float getOffsetXForRotation(float degrees, int width, int height) { 1046 mMatrix.reset(); 1047 mCamera.save(); 1048 mCamera.rotateY(Math.abs(degrees)); 1049 mCamera.getMatrix(mMatrix); 1050 mCamera.restore(); 1051 1052 mMatrix.preTranslate(-width * 0.5f, -height * 0.5f); 1053 mMatrix.postTranslate(width * 0.5f, height * 0.5f); 1054 mTempFloat2[0] = width; 1055 mTempFloat2[1] = height; 1056 mMatrix.mapPoints(mTempFloat2); 1057 return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f); 1058 } 1059 1060 float backgroundAlphaInterpolator(float r) { 1061 float pivotA = 0.1f; 1062 float pivotB = 0.4f; 1063 if (r < pivotA) { 1064 return 0; 1065 } else if (r > pivotB) { 1066 return 1.0f; 1067 } else { 1068 return (r - pivotA)/(pivotB - pivotA); 1069 } 1070 } 1071 1072 float overScrollBackgroundAlphaInterpolator(float r) { 1073 float threshold = 0.08f; 1074 1075 if (r > mOverScrollMaxBackgroundAlpha) { 1076 mOverScrollMaxBackgroundAlpha = r; 1077 } else if (r < mOverScrollMaxBackgroundAlpha) { 1078 r = mOverScrollMaxBackgroundAlpha; 1079 } 1080 1081 return Math.min(r / threshold, 1.0f); 1082 } 1083 1084 @Override 1085 protected void screenScrolled(int screenCenter) { 1086 super.screenScrolled(screenCenter); 1087 1088 // If the screen is not xlarge, then don't rotate the CellLayouts 1089 // NOTE: If we don't update the side pages alpha, then we should not hide the side pages. 1090 // see unshrink(). 1091 if (!LauncherApplication.isScreenLarge()) return; 1092 1093 final int halfScreenSize = getMeasuredWidth() / 2; 1094 1095 for (int i = 0; i < getChildCount(); i++) { 1096 CellLayout cl = (CellLayout) getChildAt(i); 1097 if (cl != null) { 1098 int totalDistance = getScaledMeasuredWidth(cl) + mPageSpacing; 1099 int delta = screenCenter - (getChildOffset(i) - 1100 getRelativeChildOffset(i) + halfScreenSize); 1101 1102 float scrollProgress = delta / (totalDistance * 1.0f); 1103 scrollProgress = Math.min(scrollProgress, 1.0f); 1104 scrollProgress = Math.max(scrollProgress, -1.0f); 1105 1106 // If the current page (i) is being overscrolled, we use a different 1107 // set of rules for setting the background alpha multiplier. 1108 if ((mScrollX < 0 && i == 0) || (mScrollX > mMaxScrollX && 1109 i == getChildCount() -1 )) { 1110 cl.setBackgroundAlphaMultiplier( 1111 overScrollBackgroundAlphaInterpolator(Math.abs(scrollProgress))); 1112 mOverScrollPageIndex = i; 1113 } else if (mOverScrollPageIndex != i) { 1114 cl.setBackgroundAlphaMultiplier( 1115 backgroundAlphaInterpolator(Math.abs(scrollProgress))); 1116 } 1117 1118 float rotation = WORKSPACE_ROTATION * scrollProgress; 1119 float translationX = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight()); 1120 cl.setTranslationX(translationX); 1121 1122 cl.setRotationY(rotation); 1123 } 1124 } 1125 } 1126 1127 protected void onAttachedToWindow() { 1128 super.onAttachedToWindow(); 1129 mWindowToken = getWindowToken(); 1130 computeScroll(); 1131 mDragController.setWindowToken(mWindowToken); 1132 } 1133 1134 protected void onDetachedFromWindow() { 1135 mWindowToken = null; 1136 } 1137 1138 @Override 1139 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1140 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { 1141 mUpdateWallpaperOffsetImmediately = true; 1142 } 1143 super.onLayout(changed, left, top, right, bottom); 1144 1145 // if shrinkToBottom() is called on initialization, it has to be deferred 1146 // until after the first call to onLayout so that it has the correct width 1147 if (mSwitchStateAfterFirstLayout) { 1148 mSwitchStateAfterFirstLayout = false; 1149 // shrink can trigger a synchronous onLayout call, so we 1150 // post this to avoid a stack overflow / tangled onLayout calls 1151 post(new Runnable() { 1152 public void run() { 1153 shrink(mStateAfterFirstLayout, false); 1154 } 1155 }); 1156 } 1157 } 1158 1159 @Override 1160 protected void onDraw(Canvas canvas) { 1161 updateWallpaperOffsets(); 1162 1163 // Draw the background gradient if necessary 1164 if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) { 1165 int alpha = (int) (mBackgroundAlpha * 255); 1166 mBackground.setAlpha(alpha); 1167 mBackground.setBounds(mScrollX, 0, mScrollX + getMeasuredWidth(), 1168 getMeasuredHeight()); 1169 mBackground.draw(canvas); 1170 } 1171 1172 super.onDraw(canvas); 1173 } 1174 1175 @Override 1176 protected void dispatchDraw(Canvas canvas) { 1177 super.dispatchDraw(canvas); 1178 1179 if (mInScrollArea && !LauncherApplication.isScreenLarge()) { 1180 final int width = getWidth(); 1181 final int height = getHeight(); 1182 final int pageHeight = getChildAt(0).getHeight(); 1183 1184 // This determines the height of the glowing edge: 90% of the page height 1185 final int padding = (int) ((height - pageHeight) * 0.5f + pageHeight * 0.1f); 1186 1187 final CellLayout leftPage = (CellLayout) getChildAt(mCurrentPage - 1); 1188 final CellLayout rightPage = (CellLayout) getChildAt(mCurrentPage + 1); 1189 1190 if (leftPage != null && leftPage.getIsDragOverlapping()) { 1191 final Drawable d = getResources().getDrawable(R.drawable.page_hover_left_holo); 1192 d.setBounds(mScrollX, padding, mScrollX + d.getIntrinsicWidth(), height - padding); 1193 d.draw(canvas); 1194 } else if (rightPage != null && rightPage.getIsDragOverlapping()) { 1195 final Drawable d = getResources().getDrawable(R.drawable.page_hover_right_holo); 1196 d.setBounds(mScrollX + width - d.getIntrinsicWidth(), padding, mScrollX + width, height - padding); 1197 d.draw(canvas); 1198 } 1199 } 1200 } 1201 1202 @Override 1203 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 1204 if (!mLauncher.isAllAppsVisible()) { 1205 final Folder openFolder = getOpenFolder(); 1206 if (openFolder != null) { 1207 return openFolder.requestFocus(direction, previouslyFocusedRect); 1208 } else { 1209 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 1210 } 1211 } 1212 return false; 1213 } 1214 1215 @Override 1216 public int getDescendantFocusability() { 1217 if (isSmall()) { 1218 return ViewGroup.FOCUS_BLOCK_DESCENDANTS; 1219 } 1220 return super.getDescendantFocusability(); 1221 } 1222 1223 @Override 1224 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 1225 if (!mLauncher.isAllAppsVisible()) { 1226 final Folder openFolder = getOpenFolder(); 1227 if (openFolder != null) { 1228 openFolder.addFocusables(views, direction); 1229 } else { 1230 super.addFocusables(views, direction, focusableMode); 1231 } 1232 } 1233 } 1234 1235 public boolean isSmall() { 1236 return mState == State.SMALL || mState == State.SPRING_LOADED; 1237 } 1238 1239 void enableChildrenCache(int fromPage, int toPage) { 1240 if (fromPage > toPage) { 1241 final int temp = fromPage; 1242 fromPage = toPage; 1243 toPage = temp; 1244 } 1245 1246 final int screenCount = getChildCount(); 1247 1248 fromPage = Math.max(fromPage, 0); 1249 toPage = Math.min(toPage, screenCount - 1); 1250 1251 for (int i = fromPage; i <= toPage; i++) { 1252 final CellLayout layout = (CellLayout) getChildAt(i); 1253 layout.setChildrenDrawnWithCacheEnabled(true); 1254 layout.setChildrenDrawingCacheEnabled(true); 1255 } 1256 } 1257 1258 void clearChildrenCache() { 1259 final int screenCount = getChildCount(); 1260 for (int i = 0; i < screenCount; i++) { 1261 final CellLayout layout = (CellLayout) getChildAt(i); 1262 layout.setChildrenDrawnWithCacheEnabled(false); 1263 } 1264 } 1265 1266 private boolean childLayersEnabled() { 1267 boolean isSmallOrSpringloaded = 1268 isSmall() || mIsSwitchingState || mState == State.SPRING_LOADED; 1269 return isSmallOrSpringloaded || isPageMoving() || mIsDragOccuring; 1270 } 1271 1272 private void updateChildrenLayersEnabled() { 1273 boolean small = 1274 isSmall() || mIsSwitchingState || mState == State.SPRING_LOADED; 1275 boolean dragging = mAnimatingViewIntoPlace || mIsDragOccuring; 1276 boolean enableChildrenLayers = small || dragging || isPageMoving(); 1277 1278 if (enableChildrenLayers != mChildrenLayersEnabled) { 1279 mChildrenLayersEnabled = enableChildrenLayers; 1280 for (int i = 0; i < getPageCount(); i++) { 1281 ((ViewGroup)getChildAt(i)).setChildrenLayersEnabled(enableChildrenLayers); 1282 } 1283 } 1284 } 1285 1286 @Override 1287 protected void onWallpaperTap(MotionEvent ev) { 1288 final int[] position = mTempCell; 1289 getLocationOnScreen(position); 1290 1291 int pointerIndex = ev.getActionIndex(); 1292 position[0] += (int) ev.getX(pointerIndex); 1293 position[1] += (int) ev.getY(pointerIndex); 1294 1295 mWallpaperManager.sendWallpaperCommand(getWindowToken(), 1296 ev.getAction() == MotionEvent.ACTION_UP 1297 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP, 1298 position[0], position[1], 0, null); 1299 } 1300 1301 private float getYScaleForScreen(int screen) { 1302 int x = Math.abs(screen - 2); 1303 1304 // TODO: This should be generalized for use with arbitrary rotation angles. 1305 switch(x) { 1306 case 0: return EXTRA_SCALE_FACTOR_0; 1307 case 1: return EXTRA_SCALE_FACTOR_1; 1308 case 2: return EXTRA_SCALE_FACTOR_2; 1309 } 1310 return 1.0f; 1311 } 1312 1313 public void shrink(State shrinkState) { 1314 shrink(shrinkState, true); 1315 } 1316 1317 // we use this to shrink the workspace for the all apps view and the customize view 1318 public void shrink(State shrinkState, boolean animated) { 1319 if (mFirstLayout) { 1320 // (mFirstLayout == "first layout has not happened yet") 1321 // if we get a call to shrink() as part of our initialization (for example, if 1322 // Launcher is started in All Apps mode) then we need to wait for a layout call 1323 // to get our width so we can layout the mini-screen views correctly 1324 mSwitchStateAfterFirstLayout = true; 1325 mStateAfterFirstLayout = shrinkState; 1326 return; 1327 } 1328 1329 // Stop any scrolling, move to the current page right away 1330 setCurrentPage((mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage); 1331 1332 CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); 1333 if (currentPage == null) { 1334 Log.w(TAG, "currentPage is NULL! mCurrentPage " + mCurrentPage 1335 + " mNextPage " + mNextPage); 1336 return; 1337 } 1338 if (currentPage.getBackgroundAlphaMultiplier() < 1.0f) { 1339 currentPage.setBackgroundAlpha(0.0f); 1340 } 1341 currentPage.setBackgroundAlphaMultiplier(1.0f); 1342 1343 mState = shrinkState; 1344 updateChildrenLayersEnabled(); 1345 1346 // we intercept and reject all touch events when we're small, so be sure to reset the state 1347 mTouchState = TOUCH_STATE_REST; 1348 mActivePointerId = INVALID_POINTER; 1349 1350 final Resources res = getResources(); 1351 final int screenWidth = getWidth(); 1352 final int screenHeight = getHeight(); 1353 1354 // How much the workspace shrinks when we enter all apps or customization mode 1355 final float shrinkFactor = res.getInteger(R.integer.config_workspaceShrinkPercent) / 100.0f; 1356 1357 // Making the assumption that all pages have the same width as the 0th 1358 final int pageWidth = getChildAt(0).getMeasuredWidth(); 1359 final int pageHeight = getChildAt(0).getMeasuredHeight(); 1360 1361 final int scaledPageWidth = (int) (shrinkFactor * pageWidth); 1362 final int scaledPageHeight = (int) (shrinkFactor * pageHeight); 1363 final float extraScaledSpacing = res.getDimension(R.dimen.smallScreenExtraSpacing); 1364 1365 final int screenCount = getChildCount(); 1366 float totalWidth = screenCount * scaledPageWidth + (screenCount - 1) * extraScaledSpacing; 1367 1368 boolean isPortrait = getMeasuredHeight() > getMeasuredWidth(); 1369 float y = (isPortrait ? 1370 getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginPortrait) : 1371 getResources().getDimension(R.dimen.allAppsSmallScreenVerticalMarginLandscape)); 1372 float finalAlpha = 1.0f; 1373 float extraShrinkFactor = 1.0f; 1374 1375 // We shrink and disappear to nothing 1376 y = screenHeight - y - scaledPageHeight; 1377 finalAlpha = 0.0f; 1378 1379 int duration = res.getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime); 1380 1381 // We animate all the screens to the centered position in workspace 1382 // At the same time, the screens become greyed/dimmed 1383 1384 // newX is initialized to the left-most position of the centered screens 1385 float x = mScroller.getFinalX() + screenWidth / 2 - totalWidth / 2; 1386 1387 // We are going to scale about the center of the view, so we need to adjust the positions 1388 // of the views accordingly 1389 x -= (pageWidth - scaledPageWidth) / 2.0f; 1390 y -= (pageHeight - scaledPageHeight) / 2.0f; 1391 1392 if (mAnimator != null) { 1393 mAnimator.cancel(); 1394 } 1395 1396 mAnimator = new AnimatorSet(); 1397 1398 final int childCount = getChildCount(); 1399 initAnimationArrays(); 1400 1401 for (int i = 0; i < screenCount; i++) { 1402 final CellLayout cl = (CellLayout) getChildAt(i); 1403 1404 float rotation = (-i + 2) * WORKSPACE_ROTATION; 1405 float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f)); 1406 float rotationScaleY = getYScaleForScreen(i); 1407 1408 mOldAlphas[i] = cl.getAlpha(); 1409 mNewAlphas[i] = finalAlpha; 1410 if (animated && (mOldAlphas[i] != 0f || mNewAlphas[i] != 0f)) { 1411 // if the CellLayout will be visible during the animation, force building its 1412 // hardware layer immediately so we don't see a blip later in the animation 1413 cl.buildChildrenLayer(); 1414 } 1415 if (animated) { 1416 mOldTranslationXs[i] = cl.getX(); 1417 mOldTranslationYs[i] = cl.getY(); 1418 mOldScaleXs[i] = cl.getScaleX(); 1419 mOldScaleYs[i] = cl.getScaleY(); 1420 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha(); 1421 mOldRotationYs[i] = cl.getRotationY(); 1422 mNewTranslationXs[i] = x; 1423 mNewTranslationYs[i] = y; 1424 mNewScaleXs[i] = shrinkFactor * rotationScaleX * extraShrinkFactor; 1425 mNewScaleYs[i] = shrinkFactor * rotationScaleY * extraShrinkFactor; 1426 mNewBackgroundAlphas[i] = finalAlpha; 1427 mNewRotationYs[i] = rotation; 1428 } else { 1429 cl.setX((int)x); 1430 cl.setY((int)y); 1431 cl.setScaleX(shrinkFactor * rotationScaleX * extraShrinkFactor); 1432 cl.setScaleY(shrinkFactor * rotationScaleY * extraShrinkFactor); 1433 cl.setBackgroundAlpha(finalAlpha); 1434 cl.setAlpha(finalAlpha); 1435 cl.setRotationY(rotation); 1436 mShrinkAnimationListener.onAnimationEnd(null); 1437 } 1438 // increment newX for the next screen 1439 x += scaledPageWidth + extraScaledSpacing; 1440 } 1441 1442 float wallpaperOffset = 0.5f; 1443 Display display = mLauncher.getWindowManager().getDefaultDisplay(); 1444 int wallpaperTravelHeight = (int) (display.getHeight() * 1445 wallpaperTravelToScreenHeightRatio(display.getWidth(), display.getHeight())); 1446 float offsetFromCenter = (wallpaperTravelHeight / (float) mWallpaperHeight) / 2f; 1447 boolean isLandscape = display.getWidth() > display.getHeight(); 1448 1449 // on phones, don't scroll the wallpaper horizontally or vertically when switching 1450 // to/from all apps 1451 final boolean enableWallpaperEffects = 1452 isHardwareAccelerated() && LauncherApplication.isScreenLarge(); 1453 if (enableWallpaperEffects) { 1454 switch (shrinkState) { 1455 // animating in 1456 case SPRING_LOADED: 1457 wallpaperOffset = 0.5f; 1458 mWallpaperOffset.setVerticalCatchupConstant(isLandscape ? 0.34f : 0.32f); 1459 break; 1460 case SMALL: 1461 // allapps 1462 wallpaperOffset = 0.5f - offsetFromCenter; 1463 mWallpaperOffset.setVerticalCatchupConstant(isLandscape ? 0.34f : 0.32f); 1464 break; 1465 } 1466 } 1467 1468 setLayoutScale(1.0f); 1469 if (animated) { 1470 if (enableWallpaperEffects) { 1471 mWallpaperOffset.setHorizontalCatchupConstant(0.46f); 1472 mWallpaperOffset.setOverrideHorizontalCatchupConstant(true); 1473 } 1474 1475 mSyncWallpaperOffsetWithScroll = false; 1476 1477 ValueAnimator animWithInterpolator = 1478 ValueAnimator.ofFloat(0f, 1f).setDuration(duration); 1479 animWithInterpolator.setInterpolator(mZoomOutInterpolator); 1480 1481 final float oldHorizontalWallpaperOffset = getHorizontalWallpaperOffset(); 1482 final float oldVerticalWallpaperOffset = getVerticalWallpaperOffset(); 1483 final float newHorizontalWallpaperOffset = 0.5f; 1484 final float newVerticalWallpaperOffset = wallpaperOffset; 1485 animWithInterpolator.addUpdateListener(new LauncherAnimatorUpdateListener() { 1486 public void onAnimationUpdate(float a, float b) { 1487 mTransitionProgress = b; 1488 if (b == 0f) { 1489 // an optimization, and required for correct behavior. 1490 return; 1491 } 1492 invalidate(); 1493 if (enableWallpaperEffects) { 1494 setHorizontalWallpaperOffset( 1495 a * oldHorizontalWallpaperOffset + b * newHorizontalWallpaperOffset); 1496 setVerticalWallpaperOffset( 1497 a * oldVerticalWallpaperOffset + b * newVerticalWallpaperOffset); 1498 } 1499 for (int i = 0; i < screenCount; i++) { 1500 final CellLayout cl = (CellLayout) getChildAt(i); 1501 cl.fastInvalidate(); 1502 cl.setFastX(a * mOldTranslationXs[i] + b * mNewTranslationXs[i]); 1503 cl.setFastY(a * mOldTranslationYs[i] + b * mNewTranslationYs[i]); 1504 cl.setFastScaleX(a * mOldScaleXs[i] + b * mNewScaleXs[i]); 1505 cl.setFastScaleY(a * mOldScaleYs[i] + b * mNewScaleYs[i]); 1506 cl.setFastBackgroundAlpha( 1507 a * mOldBackgroundAlphas[i] + b * mNewBackgroundAlphas[i]); 1508 cl.setFastAlpha(a * mOldAlphas[i] + b * mNewAlphas[i]); 1509 cl.setFastRotationY(a * mOldRotationYs[i] + b * mNewRotationYs[i]); 1510 } 1511 } 1512 }); 1513 mAnimator.playTogether(animWithInterpolator); 1514 mAnimator.addListener(mShrinkAnimationListener); 1515 mAnimator.start(); 1516 } else if (enableWallpaperEffects) { 1517 setVerticalWallpaperOffset(wallpaperOffset); 1518 setHorizontalWallpaperOffset(0.5f); 1519 updateWallpaperOffsetImmediately(); 1520 } 1521 setChildrenDrawnWithCacheEnabled(true); 1522 1523 showBackgroundGradientForAllApps(); 1524 } 1525 1526 /* 1527 * This interpolator emulates the rate at which the perceived scale of an object changes 1528 * as its distance from a camera increases. When this interpolator is applied to a scale 1529 * animation on a view, it evokes the sense that the object is shrinking due to moving away 1530 * from the camera. 1531 */ 1532 static class ZInterpolator implements TimeInterpolator { 1533 private float focalLength; 1534 1535 public ZInterpolator(float foc) { 1536 focalLength = foc; 1537 } 1538 1539 public float getInterpolation(float input) { 1540 return (1.0f - focalLength / (focalLength + input)) / 1541 (1.0f - focalLength / (focalLength + 1.0f)); 1542 } 1543 } 1544 1545 /* 1546 * The exact reverse of ZInterpolator. 1547 */ 1548 static class InverseZInterpolator implements TimeInterpolator { 1549 private ZInterpolator zInterpolator; 1550 public InverseZInterpolator(float foc) { 1551 zInterpolator = new ZInterpolator(foc); 1552 } 1553 public float getInterpolation(float input) { 1554 return 1 - zInterpolator.getInterpolation(1 - input); 1555 } 1556 } 1557 1558 /* 1559 * ZInterpolator compounded with an ease-out. 1560 */ 1561 static class ZoomOutInterpolator implements TimeInterpolator { 1562 private final ZInterpolator zInterpolator = new ZInterpolator(0.2f); 1563 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(1.8f); 1564 1565 public float getInterpolation(float input) { 1566 return decelerate.getInterpolation(zInterpolator.getInterpolation(input)); 1567 } 1568 } 1569 1570 /* 1571 * InvereZInterpolator compounded with an ease-out. 1572 */ 1573 static class ZoomInInterpolator implements TimeInterpolator { 1574 private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f); 1575 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f); 1576 1577 public float getInterpolation(float input) { 1578 return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input)); 1579 } 1580 } 1581 1582 private final ZoomOutInterpolator mZoomOutInterpolator = new ZoomOutInterpolator(); 1583 private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator(); 1584 1585 /* 1586 * 1587 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we 1588 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace 1589 * 1590 * These methods mark the appropriate pages as accepting drops (which alters their visual 1591 * appearance). 1592 * 1593 */ 1594 public void onDragStartedWithItem(View v) { 1595 final Canvas canvas = new Canvas(); 1596 1597 // We need to add extra padding to the bitmap to make room for the glow effect 1598 final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS; 1599 1600 // The outline is used to visualize where the item will land if dropped 1601 mDragOutline = createDragOutline(v, canvas, bitmapPadding); 1602 } 1603 1604 public void onDragStartedWithItemSpans(int spanX, int spanY, Bitmap b) { 1605 final Canvas canvas = new Canvas(); 1606 1607 // We need to add extra padding to the bitmap to make room for the glow effect 1608 final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS; 1609 1610 CellLayout cl = (CellLayout) getChildAt(0); 1611 1612 int[] size = cl.cellSpansToSize(spanX, spanY); 1613 1614 // The outline is used to visualize where the item will land if dropped 1615 mDragOutline = createDragOutline(b, canvas, bitmapPadding, size[0], size[1]); 1616 } 1617 1618 // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was 1619 // never dragged over 1620 public void onDragStopped(boolean success) { 1621 // In the success case, DragController has already called onDragExit() 1622 if (!success) { 1623 doDragExit(null); 1624 } 1625 } 1626 1627 // We call this when we trigger an unshrink by clicking on the CellLayout cl 1628 public void unshrink(CellLayout clThatWasClicked) { 1629 unshrink(clThatWasClicked, false); 1630 } 1631 1632 public void unshrink(CellLayout clThatWasClicked, boolean springLoaded) { 1633 int newCurrentPage = indexOfChild(clThatWasClicked); 1634 if (isSmall()) { 1635 if (springLoaded) { 1636 setLayoutScale(mSpringLoadedShrinkFactor); 1637 } 1638 scrollToNewPageWithoutMovingPages(newCurrentPage); 1639 unshrink(true, springLoaded); 1640 } 1641 } 1642 1643 1644 public void enterSpringLoadedDragMode(CellLayout clThatWasClicked) { 1645 unshrink(clThatWasClicked, true); 1646 } 1647 1648 public void exitSpringLoadedDragMode(State shrinkState) { 1649 shrink(shrinkState); 1650 } 1651 1652 public void exitWidgetResizeMode() { 1653 DragLayer dragLayer = mLauncher.getDragLayer(); 1654 dragLayer.clearAllResizeFrames(); 1655 } 1656 1657 void unshrink(boolean animated) { 1658 unshrink(animated, false); 1659 } 1660 1661 private void initAnimationArrays() { 1662 final int childCount = getChildCount(); 1663 if (mOldTranslationXs != null) return; 1664 mOldTranslationXs = new float[childCount]; 1665 mOldTranslationYs = new float[childCount]; 1666 mOldScaleXs = new float[childCount]; 1667 mOldScaleYs = new float[childCount]; 1668 mOldBackgroundAlphas = new float[childCount]; 1669 mOldBackgroundAlphaMultipliers = new float[childCount]; 1670 mOldAlphas = new float[childCount]; 1671 mOldRotationYs = new float[childCount]; 1672 mNewTranslationXs = new float[childCount]; 1673 mNewTranslationYs = new float[childCount]; 1674 mNewScaleXs = new float[childCount]; 1675 mNewScaleYs = new float[childCount]; 1676 mNewBackgroundAlphas = new float[childCount]; 1677 mNewBackgroundAlphaMultipliers = new float[childCount]; 1678 mNewAlphas = new float[childCount]; 1679 mNewRotationYs = new float[childCount]; 1680 } 1681 1682 void unshrink(boolean animated, boolean springLoaded) { 1683 if (isSmall()) { 1684 float finalScaleFactor = 1.0f; 1685 float finalBackgroundAlpha = 0.0f; 1686 if (springLoaded) { 1687 finalScaleFactor = mSpringLoadedShrinkFactor; 1688 finalBackgroundAlpha = 1.0f; 1689 mState = State.SPRING_LOADED; 1690 } else { 1691 mState = State.NORMAL; 1692 } 1693 if (mAnimator != null) { 1694 mAnimator.cancel(); 1695 } 1696 1697 mAnimator = new AnimatorSet(); 1698 final int screenCount = getChildCount(); 1699 initAnimationArrays(); 1700 1701 final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime); 1702 for (int i = 0; i < screenCount; i++) { 1703 final CellLayout cl = (CellLayout)getChildAt(i); 1704 float finalAlphaValue = 0f; 1705 float rotation = 0f; 1706 if (LauncherApplication.isScreenLarge()) { 1707 finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f; 1708 1709 if (i < mCurrentPage) { 1710 rotation = WORKSPACE_ROTATION; 1711 } else if (i > mCurrentPage) { 1712 rotation = -WORKSPACE_ROTATION; 1713 } 1714 } else { 1715 // Don't hide the side panes on the phone if we don't also update the side pages 1716 // alpha. See screenScrolled(). 1717 finalAlphaValue = 1f; 1718 } 1719 float finalAlphaMultiplierValue = 1f; 1720 1721 float translation = 0f; 1722 1723 // If the screen is not xlarge, then don't rotate the CellLayouts 1724 // NOTE: If we don't update the side pages alpha, then we should not hide the side 1725 // pages. see unshrink(). 1726 if (LauncherApplication.isScreenLarge()) { 1727 translation = getOffsetXForRotation(rotation, cl.getWidth(), cl.getHeight()); 1728 } 1729 1730 mOldAlphas[i] = cl.getAlpha(); 1731 mNewAlphas[i] = finalAlphaValue; 1732 if (animated) { 1733 mOldTranslationXs[i] = cl.getTranslationX(); 1734 mOldTranslationYs[i] = cl.getTranslationY(); 1735 mOldScaleXs[i] = cl.getScaleX(); 1736 mOldScaleYs[i] = cl.getScaleY(); 1737 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha(); 1738 mOldBackgroundAlphaMultipliers[i] = cl.getBackgroundAlphaMultiplier(); 1739 mOldRotationYs[i] = cl.getRotationY(); 1740 1741 mNewTranslationXs[i] = translation; 1742 mNewTranslationYs[i] = 0f; 1743 mNewScaleXs[i] = finalScaleFactor; 1744 mNewScaleYs[i] = finalScaleFactor; 1745 mNewBackgroundAlphas[i] = finalBackgroundAlpha; 1746 mNewBackgroundAlphaMultipliers[i] = finalAlphaMultiplierValue; 1747 mNewRotationYs[i] = rotation; 1748 } else { 1749 cl.setTranslationX(translation); 1750 cl.setTranslationY(0.0f); 1751 cl.setScaleX(finalScaleFactor); 1752 cl.setScaleY(finalScaleFactor); 1753 cl.setBackgroundAlpha(0.0f); 1754 cl.setBackgroundAlphaMultiplier(finalAlphaMultiplierValue); 1755 cl.setAlpha(finalAlphaValue); 1756 cl.setRotationY(rotation); 1757 mUnshrinkAnimationListener.onAnimationEnd(null); 1758 } 1759 } 1760 Display display = mLauncher.getWindowManager().getDefaultDisplay(); 1761 boolean isLandscape = display.getWidth() > display.getHeight(); 1762 // on phones, don't scroll the wallpaper horizontally or vertically when switching 1763 // to/from all apps 1764 final boolean enableWallpaperEffects = 1765 isHardwareAccelerated() && LauncherApplication.isScreenLarge(); 1766 if (enableWallpaperEffects) { 1767 switch (mState) { 1768 // animating out 1769 case SPRING_LOADED: 1770 if (animated) { 1771 mWallpaperOffset.setHorizontalCatchupConstant(isLandscape ? 0.49f : 0.46f); 1772 mWallpaperOffset.setVerticalCatchupConstant(isLandscape ? 0.49f : 0.46f); 1773 mWallpaperOffset.setOverrideHorizontalCatchupConstant(true); 1774 } 1775 break; 1776 case SMALL: 1777 // all apps 1778 if (animated) { 1779 mWallpaperOffset.setHorizontalCatchupConstant(isLandscape ? 0.65f : 0.65f); 1780 mWallpaperOffset.setVerticalCatchupConstant(isLandscape ? 0.65f : 0.65f); 1781 mWallpaperOffset.setOverrideHorizontalCatchupConstant(true); 1782 } 1783 break; 1784 } 1785 } 1786 if (animated) { 1787 ValueAnimator animWithInterpolator = 1788 ValueAnimator.ofFloat(0f, 1f).setDuration(duration); 1789 animWithInterpolator.setInterpolator(mZoomInInterpolator); 1790 1791 final float oldHorizontalWallpaperOffset = enableWallpaperEffects ? 1792 getHorizontalWallpaperOffset() : 0; 1793 final float oldVerticalWallpaperOffset = enableWallpaperEffects ? 1794 getVerticalWallpaperOffset() : 0; 1795 final float newHorizontalWallpaperOffset = enableWallpaperEffects ? 1796 wallpaperOffsetForCurrentScroll() : 0; 1797 final float newVerticalWallpaperOffset = enableWallpaperEffects ? 0.5f : 0; 1798 animWithInterpolator.addUpdateListener(new LauncherAnimatorUpdateListener() { 1799 public void onAnimationUpdate(float a, float b) { 1800 mTransitionProgress = b; 1801 if (b == 0f) { 1802 // an optimization, but not required 1803 return; 1804 } 1805 invalidate(); 1806 if (enableWallpaperEffects) { 1807 setHorizontalWallpaperOffset(a * oldHorizontalWallpaperOffset 1808 + b * newHorizontalWallpaperOffset); 1809 setVerticalWallpaperOffset(a * oldVerticalWallpaperOffset 1810 + b * newVerticalWallpaperOffset); 1811 } 1812 for (int i = 0; i < screenCount; i++) { 1813 final CellLayout cl = (CellLayout) getChildAt(i); 1814 cl.fastInvalidate(); 1815 cl.setFastTranslationX( 1816 a * mOldTranslationXs[i] + b * mNewTranslationXs[i]); 1817 cl.setFastTranslationY( 1818 a * mOldTranslationYs[i] + b * mNewTranslationYs[i]); 1819 cl.setFastScaleX(a * mOldScaleXs[i] + b * mNewScaleXs[i]); 1820 cl.setFastScaleY(a * mOldScaleYs[i] + b * mNewScaleYs[i]); 1821 cl.setFastBackgroundAlpha( 1822 a * mOldBackgroundAlphas[i] + b * mNewBackgroundAlphas[i]); 1823 cl.setBackgroundAlphaMultiplier(a * mOldBackgroundAlphaMultipliers[i] + 1824 b * mNewBackgroundAlphaMultipliers[i]); 1825 cl.setFastAlpha(a * mOldAlphas[i] + b * mNewAlphas[i]); 1826 } 1827 } 1828 }); 1829 1830 ValueAnimator rotationAnim = 1831 ValueAnimator.ofFloat(0f, 1f).setDuration(duration); 1832 rotationAnim.setInterpolator(new DecelerateInterpolator(2.0f)); 1833 rotationAnim.addUpdateListener(new LauncherAnimatorUpdateListener() { 1834 public void onAnimationUpdate(float a, float b) { 1835 // don't invalidate workspace because we did it above 1836 if (b == 0f) { 1837 // an optimization, but not required 1838 return; 1839 } 1840 for (int i = 0; i < screenCount; i++) { 1841 final CellLayout cl = (CellLayout) getChildAt(i); 1842 cl.setFastRotationY(a * mOldRotationYs[i] + b * mNewRotationYs[i]); 1843 } 1844 } 1845 }); 1846 1847 mAnimator.playTogether(animWithInterpolator, rotationAnim); 1848 // If we call this when we're not animated, onAnimationEnd is never called on 1849 // the listener; make sure we only use the listener when we're actually animating 1850 mAnimator.addListener(mUnshrinkAnimationListener); 1851 mAnimator.start(); 1852 } else { 1853 if (enableWallpaperEffects) { 1854 setHorizontalWallpaperOffset(wallpaperOffsetForCurrentScroll()); 1855 setVerticalWallpaperOffset(0.5f); 1856 updateWallpaperOffsetImmediately(); 1857 } 1858 } 1859 } 1860 1861 hideBackgroundGradient(springLoaded ? getResources().getInteger( 1862 R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f : 0f); 1863 } 1864 1865 /** 1866 * Draw the View v into the given Canvas. 1867 * 1868 * @param v the view to draw 1869 * @param destCanvas the canvas to draw on 1870 * @param padding the horizontal and vertical padding to use when drawing 1871 */ 1872 private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) { 1873 final Rect clipRect = mTempRect; 1874 v.getDrawingRect(clipRect); 1875 1876 destCanvas.save(); 1877 if (v instanceof TextView && pruneToDrawable) { 1878 Drawable d = ((TextView) v).getCompoundDrawables()[1]; 1879 clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding); 1880 destCanvas.translate(padding / 2, padding / 2); 1881 d.draw(destCanvas); 1882 } else { 1883 if (v instanceof FolderIcon) { 1884 clipRect.bottom = getResources().getDimensionPixelSize(R.dimen.folder_preview_size); 1885 } else if (v instanceof BubbleTextView) { 1886 final BubbleTextView tv = (BubbleTextView) v; 1887 clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + 1888 tv.getLayout().getLineTop(0); 1889 } else if (v instanceof TextView) { 1890 final TextView tv = (TextView) v; 1891 clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() + 1892 tv.getLayout().getLineTop(0); 1893 } 1894 destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2); 1895 destCanvas.clipRect(clipRect, Op.REPLACE); 1896 v.draw(destCanvas); 1897 } 1898 destCanvas.restore(); 1899 } 1900 1901 /** 1902 * Returns a new bitmap to show when the given View is being dragged around. 1903 * Responsibility for the bitmap is transferred to the caller. 1904 */ 1905 public Bitmap createDragBitmap(View v, Canvas canvas, int padding) { 1906 final int outlineColor = getResources().getColor(R.color.drag_outline_color); 1907 Bitmap b; 1908 1909 if (v instanceof TextView) { 1910 Drawable d = ((TextView) v).getCompoundDrawables()[1]; 1911 b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding, 1912 d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888); 1913 } else { 1914 b = Bitmap.createBitmap( 1915 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); 1916 } 1917 1918 canvas.setBitmap(b); 1919 drawDragView(v, canvas, padding, true); 1920 mOutlineHelper.applyOuterBlur(b, canvas, outlineColor); 1921 1922 return b; 1923 } 1924 1925 /** 1926 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 1927 * Responsibility for the bitmap is transferred to the caller. 1928 */ 1929 private Bitmap createDragOutline(View v, Canvas canvas, int padding) { 1930 final int outlineColor = getResources().getColor(R.color.drag_outline_color); 1931 final Bitmap b = Bitmap.createBitmap( 1932 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888); 1933 1934 canvas.setBitmap(b); 1935 drawDragView(v, canvas, padding, false); 1936 mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); 1937 return b; 1938 } 1939 1940 /** 1941 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location. 1942 * Responsibility for the bitmap is transferred to the caller. 1943 */ 1944 private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h) { 1945 final int outlineColor = getResources().getColor(R.color.drag_outline_color); 1946 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 1947 canvas.setBitmap(b); 1948 1949 Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight()); 1950 float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(), 1951 (h - padding) / (float) orig.getHeight()); 1952 int scaledWidth = (int) (scaleFactor * orig.getWidth()); 1953 int scaledHeight = (int) (scaleFactor * orig.getHeight()); 1954 Rect dst = new Rect(0, 0, scaledWidth, scaledHeight); 1955 1956 // center the image 1957 dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2); 1958 1959 Paint p = new Paint(); 1960 p.setFilterBitmap(true); 1961 canvas.drawBitmap(orig, src, dst, p); 1962 mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); 1963 1964 return b; 1965 } 1966 1967 /** 1968 * Creates a drag outline to represent a drop (that we don't have the actual information for 1969 * yet). May be changed in the future to alter the drop outline slightly depending on the 1970 * clip description mime data. 1971 */ 1972 private Bitmap createExternalDragOutline(Canvas canvas, int padding) { 1973 Resources r = getResources(); 1974 final int outlineColor = r.getColor(R.color.drag_outline_color); 1975 final int iconWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width); 1976 final int iconHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height); 1977 final int rectRadius = r.getDimensionPixelSize(R.dimen.external_drop_icon_rect_radius); 1978 final int inset = (int) (Math.min(iconWidth, iconHeight) * 0.2f); 1979 final Bitmap b = Bitmap.createBitmap( 1980 iconWidth + padding, iconHeight + padding, Bitmap.Config.ARGB_8888); 1981 1982 canvas.setBitmap(b); 1983 canvas.drawRoundRect(new RectF(inset, inset, iconWidth - inset, iconHeight - inset), 1984 rectRadius, rectRadius, mExternalDragOutlinePaint); 1985 mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor); 1986 return b; 1987 } 1988 1989 void startDrag(CellLayout.CellInfo cellInfo) { 1990 View child = cellInfo.cell; 1991 1992 // Make sure the drag was started by a long press as opposed to a long click. 1993 if (!child.isInTouchMode()) { 1994 return; 1995 } 1996 1997 mDragInfo = cellInfo; 1998 child.setVisibility(GONE); 1999 2000 child.clearFocus(); 2001 child.setPressed(false); 2002 2003 final Canvas canvas = new Canvas(); 2004 2005 // We need to add extra padding to the bitmap to make room for the glow effect 2006 final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS; 2007 2008 // The outline is used to visualize where the item will land if dropped 2009 mDragOutline = createDragOutline(child, canvas, bitmapPadding); 2010 beginDragShared(child, this); 2011 } 2012 2013 public void beginDragShared(View child, DragSource source) { 2014 // We need to add extra padding to the bitmap to make room for the glow effect 2015 final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS; 2016 2017 // The drag bitmap follows the touch point around on the screen 2018 final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding); 2019 2020 final int bmpWidth = b.getWidth(); 2021 final int bmpHeight = b.getHeight(); 2022 2023 mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY); 2024 final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2; 2025 int dragLayerY = mTempXY[1] - bitmapPadding / 2; 2026 2027 Rect dragRect = null; 2028 if (child instanceof BubbleTextView) { 2029 int iconSize = getResources().getDimensionPixelSize(R.dimen.app_icon_size); 2030 int top = child.getPaddingTop(); 2031 int left = (bmpWidth - iconSize) / 2; 2032 int right = left + iconSize; 2033 int bottom = top + iconSize; 2034 dragLayerY += top; 2035 dragRect = new Rect(left, top, right, bottom); 2036 } else if (child instanceof FolderIcon) { 2037 int previewSize = getResources().getDimensionPixelSize(R.dimen.folder_preview_size); 2038 dragRect = new Rect(0, 0, child.getWidth(), previewSize); 2039 } 2040 2041 mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), 2042 DragController.DRAG_ACTION_MOVE, dragRect); 2043 b.recycle(); 2044 } 2045 2046 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, int screen, 2047 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) { 2048 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info); 2049 2050 final int[] cellXY = new int[2]; 2051 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); 2052 addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst); 2053 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, cellXY[0], 2054 cellXY[1]); 2055 } 2056 2057 public boolean transitionStateShouldAllowDrop() { 2058 return (!isSwitchingState() || mTransitionProgress > 0.5f); 2059 } 2060 2061 /** 2062 * {@inheritDoc} 2063 */ 2064 public boolean acceptDrop(DragObject d) { 2065 // If it's an external drop (e.g. from All Apps), check if it should be accepted 2066 if (d.dragSource != this) { 2067 // Don't accept the drop if we're not over a screen at time of drop 2068 if (mDragTargetLayout == null) { 2069 return false; 2070 } 2071 if (!transitionStateShouldAllowDrop()) return false; 2072 2073 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, 2074 d.dragView, mDragViewVisualCenter); 2075 2076 int spanX = 1; 2077 int spanY = 1; 2078 View ignoreView = null; 2079 if (mDragInfo != null) { 2080 final CellLayout.CellInfo dragCellInfo = mDragInfo; 2081 spanX = dragCellInfo.spanX; 2082 spanY = dragCellInfo.spanY; 2083 ignoreView = dragCellInfo.cell; 2084 } else { 2085 final ItemInfo dragInfo = (ItemInfo) d.dragInfo; 2086 spanX = dragInfo.spanX; 2087 spanY = dragInfo.spanY; 2088 } 2089 2090 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 2091 (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell); 2092 if (willCreateUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout, mTargetCell, true)) { 2093 return true; 2094 } 2095 if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout, 2096 mTargetCell)) { 2097 return true; 2098 } 2099 2100 2101 // Don't accept the drop if there's no room for the item 2102 if (!mDragTargetLayout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) { 2103 mLauncher.showOutOfSpaceMessage(); 2104 return false; 2105 } 2106 } 2107 return true; 2108 } 2109 2110 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, 2111 boolean considerTimeout) { 2112 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2113 2114 boolean hasntMoved = false; 2115 if (mDragInfo != null) { 2116 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell); 2117 hasntMoved = (mDragInfo.cellX == targetCell[0] && 2118 mDragInfo.cellY == targetCell[1]) && (cellParent == target); 2119 } 2120 2121 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) { 2122 return false; 2123 } 2124 2125 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo); 2126 boolean willBecomeShortcut = 2127 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 2128 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT); 2129 2130 return (aboveShortcut && willBecomeShortcut); 2131 } 2132 2133 boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell) { 2134 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2135 if (dropOverView instanceof FolderIcon) { 2136 FolderIcon fi = (FolderIcon) dropOverView; 2137 if (fi.acceptDrop(dragInfo)) { 2138 return true; 2139 } 2140 } 2141 return false; 2142 } 2143 2144 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target, 2145 int[] targetCell, boolean external, DragView dragView, Runnable postAnimationRunnable) { 2146 View v = target.getChildAt(targetCell[0], targetCell[1]); 2147 boolean hasntMoved = false; 2148 if (mDragInfo != null) { 2149 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell); 2150 hasntMoved = (mDragInfo.cellX == targetCell[0] && 2151 mDragInfo.cellY == targetCell[1]) && (cellParent == target); 2152 } 2153 2154 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false; 2155 mCreateUserFolderOnDrop = false; 2156 final int screen = (targetCell == null) ? mDragInfo.screen : indexOfChild(target); 2157 2158 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo); 2159 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo); 2160 2161 if (aboveShortcut && willBecomeShortcut) { 2162 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag(); 2163 ShortcutInfo destInfo = (ShortcutInfo) v.getTag(); 2164 // if the drag started here, we need to remove it from the workspace 2165 if (!external) { 2166 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 2167 } 2168 2169 Rect folderLocation = new Rect(); 2170 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation); 2171 target.removeView(v); 2172 2173 FolderIcon fi = 2174 mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]); 2175 destInfo.cellX = -1; 2176 destInfo.cellY = -1; 2177 sourceInfo.cellX = -1; 2178 sourceInfo.cellY = -1; 2179 2180 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale, 2181 postAnimationRunnable); 2182 return true; 2183 } 2184 return false; 2185 } 2186 2187 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell, 2188 DragObject d, boolean external) { 2189 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); 2190 if (dropOverView instanceof FolderIcon) { 2191 FolderIcon fi = (FolderIcon) dropOverView; 2192 if (fi.acceptDrop(d.dragInfo)) { 2193 fi.onDrop(d); 2194 2195 // if the drag started here, we need to remove it from the workspace 2196 if (!external) { 2197 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 2198 } 2199 return true; 2200 } 2201 } 2202 return false; 2203 } 2204 2205 public void onDrop(DragObject d) { 2206 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, 2207 mDragViewVisualCenter); 2208 2209 // We want the point to be mapped to the dragTarget. 2210 if (mDragTargetLayout != null) { 2211 if (mLauncher.isHotseatLayout(mDragTargetLayout)) { 2212 mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter); 2213 } else { 2214 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null); 2215 } 2216 } 2217 2218 // When you are in customization mode and drag to a particular screen, make that the 2219 // new current/default screen, so any subsequent taps add items to that screen 2220 if (!mLauncher.isAllAppsVisible()) { 2221 int dragTargetIndex = indexOfChild(mDragTargetLayout); 2222 if (mCurrentPage != dragTargetIndex && (isSmall() || mIsSwitchingState)) { 2223 scrollToNewPageWithoutMovingPages(dragTargetIndex); 2224 } 2225 } 2226 CellLayout dropTargetLayout = mDragTargetLayout; 2227 2228 if (d.dragSource != this) { 2229 final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], 2230 (int) mDragViewVisualCenter[1] }; 2231 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d); 2232 } else if (mDragInfo != null) { 2233 final View cell = mDragInfo.cell; 2234 2235 boolean continueDrop = true; 2236 if (mLauncher.isHotseatLayout(mDragTargetLayout) && d.dragInfo instanceof ItemInfo) { 2237 ItemInfo info = (ItemInfo) d.dragInfo; 2238 if (info.spanX > 1 || info.spanY > 1) { 2239 continueDrop = false; 2240 Toast.makeText(getContext(), R.string.invalid_hotseat_item, 2241 Toast.LENGTH_SHORT).show(); 2242 } 2243 } 2244 2245 if (continueDrop && dropTargetLayout != null) { 2246 // Move internally 2247 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); 2248 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout); 2249 long container = hasMovedIntoHotseat ? 2250 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 2251 LauncherSettings.Favorites.CONTAINER_DESKTOP; 2252 int screen = (mTargetCell[0] < 0) ? 2253 mDragInfo.screen : indexOfChild(dropTargetLayout); 2254 int spanX = mDragInfo != null ? mDragInfo.spanX : 1; 2255 int spanY = mDragInfo != null ? mDragInfo.spanY : 1; 2256 // First we find the cell nearest to point at which the item is 2257 // dropped, without any consideration to whether there is an item there. 2258 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int) 2259 mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell); 2260 // If the item being dropped is a shortcut and the nearest drop 2261 // cell also contains a shortcut, then create a folder with the two shortcuts. 2262 boolean dropInscrollArea = hasMovedLayouts && !hasMovedIntoHotseat; 2263 if (!dropInscrollArea && createUserFolderIfNecessary(cell, container, 2264 dropTargetLayout, mTargetCell, false, d.dragView, null)) { 2265 return; 2266 } 2267 2268 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, d, false)) { 2269 return; 2270 } 2271 2272 // Aside from the special case where we're dropping a shortcut onto a shortcut, 2273 // we need to find the nearest cell location that is vacant 2274 mTargetCell = findNearestVacantArea((int) mDragViewVisualCenter[0], 2275 (int) mDragViewVisualCenter[1], mDragInfo.spanX, mDragInfo.spanY, cell, 2276 dropTargetLayout, mTargetCell); 2277 2278 if (dropInscrollArea && mState != State.SPRING_LOADED) { 2279 snapToPage(screen); 2280 } 2281 2282 2283 if (mTargetCell[0] >= 0 && mTargetCell[1] >= 0) { 2284 if (hasMovedLayouts) { 2285 // Reparent the view 2286 getParentCellLayoutForView(cell).removeView(cell); 2287 addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1], 2288 mDragInfo.spanX, mDragInfo.spanY); 2289 } 2290 2291 // update the item's position after drop 2292 final ItemInfo info = (ItemInfo) cell.getTag(); 2293 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 2294 dropTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]); 2295 lp.cellX = mTargetCell[0]; 2296 lp.cellY = mTargetCell[1]; 2297 cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen, 2298 mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); 2299 2300 if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && 2301 cell instanceof LauncherAppWidgetHostView) { 2302 final CellLayout cellLayout = dropTargetLayout; 2303 // We post this call so that the widget has a chance to be placed 2304 // in its final location 2305 2306 final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; 2307 AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo(); 2308 if (pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) { 2309 final Runnable resizeRunnable = new Runnable() { 2310 public void run() { 2311 DragLayer dragLayer = mLauncher.getDragLayer(); 2312 dragLayer.addResizeFrame(info, hostView, cellLayout); 2313 } 2314 }; 2315 post(new Runnable() { 2316 public void run() { 2317 if (!isPageMoving()) { 2318 resizeRunnable.run(); 2319 } else { 2320 mDelayedResizeRunnable = resizeRunnable; 2321 } 2322 } 2323 }); 2324 } 2325 } 2326 2327 LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX, 2328 lp.cellY); 2329 } 2330 } 2331 2332 final CellLayout parent = (CellLayout) cell.getParent().getParent(); 2333 2334 // Prepare it to be animated into its new position 2335 // This must be called after the view has been re-parented 2336 final Runnable disableHardwareLayersRunnable = new Runnable() { 2337 @Override 2338 public void run() { 2339 mAnimatingViewIntoPlace = false; 2340 updateChildrenLayersEnabled(); 2341 } 2342 }; 2343 mAnimatingViewIntoPlace = true; 2344 if (d.dragView.hasDrawn()) { 2345 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, 2346 disableHardwareLayersRunnable); 2347 } else { 2348 cell.setVisibility(VISIBLE); 2349 } 2350 parent.onDropChild(cell); 2351 } 2352 } 2353 2354 public void getViewLocationRelativeToSelf(View v, int[] location) { 2355 getLocationInWindow(location); 2356 int x = location[0]; 2357 int y = location[1]; 2358 2359 v.getLocationInWindow(location); 2360 int vX = location[0]; 2361 int vY = location[1]; 2362 2363 location[0] = vX - x; 2364 location[1] = vY - y; 2365 } 2366 2367 public void onDragEnter(DragObject d) { 2368 if (mDragTargetLayout != null) { 2369 mDragTargetLayout.setIsDragOverlapping(false); 2370 mDragTargetLayout.onDragExit(); 2371 } 2372 mDragTargetLayout = getCurrentDropLayout(); 2373 mDragTargetLayout.setIsDragOverlapping(true); 2374 mDragTargetLayout.onDragEnter(); 2375 2376 // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we 2377 // don't need to show the outlines 2378 if (LauncherApplication.isScreenLarge()) { 2379 showOutlines(); 2380 } 2381 } 2382 2383 private void doDragExit(DragObject d) { 2384 // Clean up folders 2385 cleanupFolderCreation(d); 2386 2387 // Reset the scroll area and previous drag target 2388 onResetScrollArea(); 2389 2390 if (mDragTargetLayout != null) { 2391 mDragTargetLayout.setIsDragOverlapping(false); 2392 mDragTargetLayout.onDragExit(); 2393 } 2394 mLastDragOverView = null; 2395 2396 if (!mIsPageMoving) { 2397 hideOutlines(); 2398 } 2399 } 2400 2401 public void onDragExit(DragObject d) { 2402 doDragExit(d); 2403 } 2404 2405 public DropTarget getDropTargetDelegate(DragObject d) { 2406 return null; 2407 } 2408 2409 /** 2410 * Tests to see if the drop will be accepted by Launcher, and if so, includes additional data 2411 * in the returned structure related to the widgets that match the drop (or a null list if it is 2412 * a shortcut drop). If the drop is not accepted then a null structure is returned. 2413 */ 2414 private Pair<Integer, List<WidgetMimeTypeHandlerData>> validateDrag(DragEvent event) { 2415 final LauncherModel model = mLauncher.getModel(); 2416 final ClipDescription desc = event.getClipDescription(); 2417 final int mimeTypeCount = desc.getMimeTypeCount(); 2418 for (int i = 0; i < mimeTypeCount; ++i) { 2419 final String mimeType = desc.getMimeType(i); 2420 if (mimeType.equals(InstallShortcutReceiver.SHORTCUT_MIMETYPE)) { 2421 return new Pair<Integer, List<WidgetMimeTypeHandlerData>>(i, null); 2422 } else { 2423 final List<WidgetMimeTypeHandlerData> widgets = 2424 model.resolveWidgetsForMimeType(mContext, mimeType); 2425 if (widgets.size() > 0) { 2426 return new Pair<Integer, List<WidgetMimeTypeHandlerData>>(i, widgets); 2427 } 2428 } 2429 } 2430 return null; 2431 } 2432 2433 /** 2434 * Global drag and drop handler 2435 */ 2436 @Override 2437 public boolean onDragEvent(DragEvent event) { 2438 final ClipDescription desc = event.getClipDescription(); 2439 final CellLayout layout = (CellLayout) getChildAt(mCurrentPage); 2440 final int[] pos = new int[2]; 2441 layout.getLocationOnScreen(pos); 2442 // We need to offset the drag coordinates to layout coordinate space 2443 final int x = (int) event.getX() - pos[0]; 2444 final int y = (int) event.getY() - pos[1]; 2445 2446 switch (event.getAction()) { 2447 case DragEvent.ACTION_DRAG_STARTED: { 2448 // Validate this drag 2449 Pair<Integer, List<WidgetMimeTypeHandlerData>> test = validateDrag(event); 2450 if (test != null) { 2451 boolean isShortcut = (test.second == null); 2452 if (isShortcut) { 2453 // Check if we have enough space on this screen to add a new shortcut 2454 if (!layout.findCellForSpan(pos, 1, 1)) { 2455 mLauncher.showOutOfSpaceMessage(); 2456 return false; 2457 } 2458 } 2459 } else { 2460 // Show error message if we couldn't accept any of the items 2461 Toast.makeText(mContext, mContext.getString(R.string.external_drop_widget_error), 2462 Toast.LENGTH_SHORT).show(); 2463 return false; 2464 } 2465 2466 // Create the drag outline 2467 // We need to add extra padding to the bitmap to make room for the glow effect 2468 final Canvas canvas = new Canvas(); 2469 final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS; 2470 mDragOutline = createExternalDragOutline(canvas, bitmapPadding); 2471 2472 // Show the current page outlines to indicate that we can accept this drop 2473 showOutlines(); 2474 layout.setIsDragOccuring(true); 2475 layout.onDragEnter(); 2476 layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1); 2477 2478 return true; 2479 } 2480 case DragEvent.ACTION_DRAG_LOCATION: 2481 // Visualize the drop location 2482 layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1); 2483 return true; 2484 case DragEvent.ACTION_DROP: { 2485 // Try and add any shortcuts 2486 final LauncherModel model = mLauncher.getModel(); 2487 final ClipData data = event.getClipData(); 2488 2489 // We assume that the mime types are ordered in descending importance of 2490 // representation. So we enumerate the list of mime types and alert the 2491 // user if any widgets can handle the drop. Only the most preferred 2492 // representation will be handled. 2493 pos[0] = x; 2494 pos[1] = y; 2495 Pair<Integer, List<WidgetMimeTypeHandlerData>> test = validateDrag(event); 2496 if (test != null) { 2497 final int index = test.first; 2498 final List<WidgetMimeTypeHandlerData> widgets = test.second; 2499 final boolean isShortcut = (widgets == null); 2500 final String mimeType = desc.getMimeType(index); 2501 if (isShortcut) { 2502 final Intent intent = data.getItemAt(index).getIntent(); 2503 Object info = model.infoFromShortcutIntent(mContext, intent, data.getIcon()); 2504 onDropExternal(new int[] { x, y }, info, layout, false); 2505 } else { 2506 if (widgets.size() == 1) { 2507 // If there is only one item, then go ahead and add and configure 2508 // that widget 2509 final AppWidgetProviderInfo widgetInfo = widgets.get(0).widgetInfo; 2510 final PendingAddWidgetInfo createInfo = 2511 new PendingAddWidgetInfo(widgetInfo, mimeType, data); 2512 mLauncher.addAppWidgetFromDrop(createInfo, 2513 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentPage, null, pos); 2514 } else { 2515 // Show the widget picker dialog if there is more than one widget 2516 // that can handle this data type 2517 final InstallWidgetReceiver.WidgetListAdapter adapter = 2518 new InstallWidgetReceiver.WidgetListAdapter(mLauncher, mimeType, 2519 data, widgets, layout, mCurrentPage, pos); 2520 final AlertDialog.Builder builder = 2521 new AlertDialog.Builder(mContext); 2522 builder.setAdapter(adapter, adapter); 2523 builder.setCancelable(true); 2524 builder.setTitle(mContext.getString( 2525 R.string.external_drop_widget_pick_title)); 2526 builder.setIcon(R.drawable.ic_no_applications); 2527 builder.show(); 2528 } 2529 } 2530 } 2531 return true; 2532 } 2533 case DragEvent.ACTION_DRAG_ENDED: 2534 // Hide the page outlines after the drop 2535 layout.setIsDragOccuring(false); 2536 layout.onDragExit(); 2537 hideOutlines(); 2538 return true; 2539 } 2540 return super.onDragEvent(event); 2541 } 2542 2543 /* 2544 * 2545 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 2546 * coordinate space. The argument xy is modified with the return result. 2547 * 2548 */ 2549 void mapPointFromSelfToChild(View v, float[] xy) { 2550 mapPointFromSelfToChild(v, xy, null); 2551 } 2552 2553 /* 2554 * 2555 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 2556 * coordinate space. The argument xy is modified with the return result. 2557 * 2558 * if cachedInverseMatrix is not null, this method will just use that matrix instead of 2559 * computing it itself; we use this to avoid redundant matrix inversions in 2560 * findMatchingPageForDragOver 2561 * 2562 */ 2563 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { 2564 if (cachedInverseMatrix == null) { 2565 v.getMatrix().invert(mTempInverseMatrix); 2566 cachedInverseMatrix = mTempInverseMatrix; 2567 } 2568 xy[0] = xy[0] + mScrollX - v.getLeft(); 2569 xy[1] = xy[1] + mScrollY - v.getTop(); 2570 cachedInverseMatrix.mapPoints(xy); 2571 } 2572 2573 /* 2574 * Maps a point from the Workspace's coordinate system to another sibling view's. (Workspace 2575 * covers the full screen) 2576 */ 2577 void mapPointFromSelfToSibling(View v, float[] xy) { 2578 xy[0] = xy[0] - v.getLeft(); 2579 xy[1] = xy[1] - v.getTop(); 2580 } 2581 2582 /* 2583 * 2584 * Convert the 2D coordinate xy from this CellLayout's coordinate space to 2585 * the parent View's coordinate space. The argument xy is modified with the return result. 2586 * 2587 */ 2588 void mapPointFromChildToSelf(View v, float[] xy) { 2589 v.getMatrix().mapPoints(xy); 2590 xy[0] -= (mScrollX - v.getLeft()); 2591 xy[1] -= (mScrollY - v.getTop()); 2592 } 2593 2594 static private float squaredDistance(float[] point1, float[] point2) { 2595 float distanceX = point1[0] - point2[0]; 2596 float distanceY = point2[1] - point2[1]; 2597 return distanceX * distanceX + distanceY * distanceY; 2598 } 2599 2600 /* 2601 * 2602 * Returns true if the passed CellLayout cl overlaps with dragView 2603 * 2604 */ 2605 boolean overlaps(CellLayout cl, DragView dragView, 2606 int dragViewX, int dragViewY, Matrix cachedInverseMatrix) { 2607 // Transform the coordinates of the item being dragged to the CellLayout's coordinates 2608 final float[] draggedItemTopLeft = mTempDragCoordinates; 2609 draggedItemTopLeft[0] = dragViewX; 2610 draggedItemTopLeft[1] = dragViewY; 2611 final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates; 2612 draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getDragRegionWidth(); 2613 draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getDragRegionHeight(); 2614 2615 // Transform the dragged item's top left coordinates 2616 // to the CellLayout's local coordinates 2617 mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix); 2618 float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]); 2619 float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]); 2620 2621 if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) { 2622 // Transform the dragged item's bottom right coordinates 2623 // to the CellLayout's local coordinates 2624 mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix); 2625 float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]); 2626 float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]); 2627 2628 if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) { 2629 float overlap = (overlapRegionRight - overlapRegionLeft) * 2630 (overlapRegionBottom - overlapRegionTop); 2631 if (overlap > 0) { 2632 return true; 2633 } 2634 } 2635 } 2636 return false; 2637 } 2638 2639 /* 2640 * 2641 * This method returns the CellLayout that is currently being dragged to. In order to drag 2642 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second 2643 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one 2644 * 2645 * Return null if no CellLayout is currently being dragged over 2646 * 2647 */ 2648 private CellLayout findMatchingPageForDragOver( 2649 DragView dragView, float originX, float originY, boolean exact) { 2650 // We loop through all the screens (ie CellLayouts) and see which ones overlap 2651 // with the item being dragged and then choose the one that's closest to the touch point 2652 final int screenCount = getChildCount(); 2653 CellLayout bestMatchingScreen = null; 2654 float smallestDistSoFar = Float.MAX_VALUE; 2655 2656 for (int i = 0; i < screenCount; i++) { 2657 CellLayout cl = (CellLayout) getChildAt(i); 2658 2659 final float[] touchXy = {originX, originY}; 2660 // Transform the touch coordinates to the CellLayout's local coordinates 2661 // If the touch point is within the bounds of the cell layout, we can return immediately 2662 cl.getMatrix().invert(mTempInverseMatrix); 2663 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); 2664 2665 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && 2666 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { 2667 return cl; 2668 } 2669 2670 if (!exact && overlaps(cl, dragView, (int) originX, (int) originY, mTempInverseMatrix)) { 2671 // Get the center of the cell layout in screen coordinates 2672 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates; 2673 cellLayoutCenter[0] = cl.getWidth()/2; 2674 cellLayoutCenter[1] = cl.getHeight()/2; 2675 mapPointFromChildToSelf(cl, cellLayoutCenter); 2676 2677 touchXy[0] = originX; 2678 touchXy[1] = originY; 2679 2680 // Calculate the distance between the center of the CellLayout 2681 // and the touch point 2682 float dist = squaredDistance(touchXy, cellLayoutCenter); 2683 2684 if (dist < smallestDistSoFar) { 2685 smallestDistSoFar = dist; 2686 bestMatchingScreen = cl; 2687 } 2688 } 2689 } 2690 return bestMatchingScreen; 2691 } 2692 2693 // This is used to compute the visual center of the dragView. This point is then 2694 // used to visualize drop locations and determine where to drop an item. The idea is that 2695 // the visual center represents the user's interpretation of where the item is, and hence 2696 // is the appropriate point to use when determining drop location. 2697 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, 2698 DragView dragView, float[] recycle) { 2699 float res[]; 2700 if (recycle == null) { 2701 res = new float[2]; 2702 } else { 2703 res = recycle; 2704 } 2705 2706 // First off, the drag view has been shifted in a way that is not represented in the 2707 // x and y values or the x/yOffsets. Here we account for that shift. 2708 x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX); 2709 y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY); 2710 2711 // These represent the visual top and left of drag view if a dragRect was provided. 2712 // If a dragRect was not provided, then they correspond to the actual view left and 2713 // top, as the dragRect is in that case taken to be the entire dragView. 2714 // R.dimen.dragViewOffsetY. 2715 int left = x - xOffset; 2716 int top = y - yOffset; 2717 2718 // In order to find the visual center, we shift by half the dragRect 2719 res[0] = left + dragView.getDragRegion().width() / 2; 2720 res[1] = top + dragView.getDragRegion().height() / 2; 2721 2722 return res; 2723 } 2724 2725 public void onDragOver(DragObject d) { 2726 // Skip drag over events while we are dragging over side pages 2727 if (mInScrollArea) return; 2728 if (mIsSwitchingState) return; 2729 2730 CellLayout layout = null; 2731 ItemInfo item = (ItemInfo) d.dragInfo; 2732 2733 // Ensure that we have proper spans for the item that we are dropping 2734 if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found"); 2735 2736 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, 2737 d.dragView, mDragViewVisualCenter); 2738 2739 // Identify whether we have dragged over a side page 2740 if (isSmall()) { 2741 int left = d.x - d.xOffset; 2742 int top = d.y - d.yOffset; 2743 layout = findMatchingPageForDragOver(d.dragView, mDragViewVisualCenter[0], 2744 mDragViewVisualCenter[1], true); 2745 if (layout != mDragTargetLayout) { 2746 // Cancel all intermediate folder states 2747 cleanupFolderCreation(d); 2748 2749 if (mDragTargetLayout != null) { 2750 mDragTargetLayout.setIsDragOverlapping(false); 2751 mDragTargetLayout.onDragExit(); 2752 } 2753 mDragTargetLayout = layout; 2754 if (mDragTargetLayout != null) { 2755 mDragTargetLayout.setIsDragOverlapping(true); 2756 mDragTargetLayout.onDragEnter(); 2757 } else { 2758 mLastDragOverView = null; 2759 } 2760 2761 boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED); 2762 if (isInSpringLoadedMode) { 2763 mSpringLoadedDragController.setAlarm(mDragTargetLayout); 2764 } 2765 } 2766 } else { 2767 // Test to see if we are over the hotseat otherwise just use the current page 2768 Rect r = new Rect(); 2769 if (mLauncher.getHotseat() != null) { 2770 mLauncher.getHotseat().getHitRect(r); 2771 if (r.contains(d.x, d.y)) { 2772 layout = mLauncher.getHotseat().getLayout(); 2773 } 2774 } 2775 if (layout == null) { 2776 layout = getCurrentDropLayout(); 2777 } 2778 if (layout != mDragTargetLayout) { 2779 if (mDragTargetLayout != null) { 2780 mDragTargetLayout.setIsDragOverlapping(false); 2781 mDragTargetLayout.onDragExit(); 2782 } 2783 mDragTargetLayout = layout; 2784 mDragTargetLayout.setIsDragOverlapping(true); 2785 mDragTargetLayout.onDragEnter(); 2786 } 2787 } 2788 2789 // Handle the drag over 2790 if (mDragTargetLayout != null) { 2791 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 2792 2793 // We want the point to be mapped to the dragTarget. 2794 if (mLauncher.isHotseatLayout(mDragTargetLayout)) { 2795 mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter); 2796 } else { 2797 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null); 2798 } 2799 ItemInfo info = (ItemInfo) d.dragInfo; 2800 2801 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], 2802 (int) mDragViewVisualCenter[1], 1, 1, mDragTargetLayout, mTargetCell); 2803 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], 2804 mTargetCell[1]); 2805 2806 boolean userFolderPending = willCreateUserFolder(info, mDragTargetLayout, 2807 mTargetCell, false); 2808 boolean isOverFolder = dragOverView instanceof FolderIcon; 2809 if (dragOverView != mLastDragOverView) { 2810 cancelFolderCreation(); 2811 if (mLastDragOverView != null && mLastDragOverView instanceof FolderIcon) { 2812 ((FolderIcon) mLastDragOverView).onDragExit(d.dragInfo); 2813 } 2814 } 2815 2816 if (userFolderPending && dragOverView != mLastDragOverView) { 2817 mFolderCreationAlarm.setOnAlarmListener(new 2818 FolderCreationAlarmListener(mDragTargetLayout, mTargetCell[0], mTargetCell[1])); 2819 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); 2820 } 2821 2822 if (dragOverView != mLastDragOverView && isOverFolder) { 2823 ((FolderIcon) dragOverView).onDragEnter(d.dragInfo); 2824 if (mDragTargetLayout != null) { 2825 mDragTargetLayout.clearDragOutlines(); 2826 } 2827 } 2828 mLastDragOverView = dragOverView; 2829 2830 if (!mCreateUserFolderOnDrop && !isOverFolder) { 2831 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, 2832 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], 2833 item.spanX, item.spanY); 2834 } 2835 } 2836 } 2837 2838 private void cleanupFolderCreation(DragObject d) { 2839 if (mDragFolderRingAnimator != null && mCreateUserFolderOnDrop) { 2840 mDragFolderRingAnimator.animateToNaturalState(); 2841 } 2842 if (mLastDragOverView != null && mLastDragOverView instanceof FolderIcon) { 2843 if (d != null) { 2844 ((FolderIcon) mLastDragOverView).onDragExit(d.dragInfo); 2845 } 2846 } 2847 mFolderCreationAlarm.cancelAlarm(); 2848 } 2849 2850 private void cancelFolderCreation() { 2851 if (mDragFolderRingAnimator != null && mCreateUserFolderOnDrop) { 2852 mDragFolderRingAnimator.animateToNaturalState(); 2853 } 2854 mCreateUserFolderOnDrop = false; 2855 mFolderCreationAlarm.cancelAlarm(); 2856 } 2857 2858 class FolderCreationAlarmListener implements OnAlarmListener { 2859 CellLayout layout; 2860 int cellX; 2861 int cellY; 2862 2863 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) { 2864 this.layout = layout; 2865 this.cellX = cellX; 2866 this.cellY = cellY; 2867 } 2868 2869 public void onAlarm(Alarm alarm) { 2870 if (mDragFolderRingAnimator == null) { 2871 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null); 2872 } 2873 mDragFolderRingAnimator.setCell(cellX, cellY); 2874 mDragFolderRingAnimator.setCellLayout(layout); 2875 mDragFolderRingAnimator.animateToAcceptState(); 2876 layout.showFolderAccept(mDragFolderRingAnimator); 2877 layout.clearDragOutlines(); 2878 mCreateUserFolderOnDrop = true; 2879 } 2880 } 2881 2882 @Override 2883 public void getHitRect(Rect outRect) { 2884 // We want the workspace to have the whole area of the display (it will find the correct 2885 // cell layout to drop to in the existing drag/drop logic. 2886 final Display d = mLauncher.getWindowManager().getDefaultDisplay(); 2887 outRect.set(0, 0, d.getWidth(), d.getHeight()); 2888 } 2889 2890 /** 2891 * Add the item specified by dragInfo to the given layout. 2892 * @return true if successful 2893 */ 2894 public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) { 2895 if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) { 2896 onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false); 2897 return true; 2898 } 2899 mLauncher.showOutOfSpaceMessage(); 2900 return false; 2901 } 2902 2903 private void onDropExternal(int[] touchXY, Object dragInfo, 2904 CellLayout cellLayout, boolean insertAtFirst) { 2905 onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null); 2906 } 2907 2908 /** 2909 * Drop an item that didn't originate on one of the workspace screens. 2910 * It may have come from Launcher (e.g. from all apps or customize), or it may have 2911 * come from another app altogether. 2912 * 2913 * NOTE: This can also be called when we are outside of a drag event, when we want 2914 * to add an item to one of the workspace screens. 2915 */ 2916 private void onDropExternal(final int[] touchXY, final Object dragInfo, 2917 final CellLayout cellLayout, boolean insertAtFirst, DragObject d) { 2918 final Runnable exitSpringLoadedRunnable = new Runnable() { 2919 @Override 2920 public void run() { 2921 mLauncher.exitSpringLoadedDragModeDelayed(false); 2922 } 2923 }; 2924 2925 ItemInfo info = (ItemInfo) dragInfo; 2926 int spanX = info.spanX; 2927 int spanY = info.spanY; 2928 if (mDragInfo != null) { 2929 spanX = mDragInfo.spanX; 2930 spanY = mDragInfo.spanY; 2931 } 2932 2933 final long container = mLauncher.isHotseatLayout(cellLayout) ? 2934 LauncherSettings.Favorites.CONTAINER_HOTSEAT : 2935 LauncherSettings.Favorites.CONTAINER_DESKTOP; 2936 final int screen = indexOfChild(cellLayout); 2937 if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage 2938 && mState != State.SPRING_LOADED) { 2939 snapToPage(screen); 2940 } 2941 2942 if (info instanceof PendingAddItemInfo) { 2943 final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo; 2944 2945 mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], spanX, spanY, null, 2946 cellLayout, mTargetCell); 2947 Runnable onAnimationCompleteRunnable = new Runnable() { 2948 @Override 2949 public void run() { 2950 // When dragging and dropping from customization tray, we deal with creating 2951 // widgets/shortcuts/folders in a slightly different way 2952 switch (pendingInfo.itemType) { 2953 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 2954 mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo, 2955 container, screen, mTargetCell, null); 2956 break; 2957 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 2958 mLauncher.processShortcutFromDrop(pendingInfo.componentName, 2959 container, screen, mTargetCell, null); 2960 break; 2961 default: 2962 throw new IllegalStateException("Unknown item type: " + 2963 pendingInfo.itemType); 2964 } 2965 cellLayout.onDragExit(); 2966 } 2967 }; 2968 2969 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final 2970 // location and size on the home screen. 2971 int loc[] = new int[2]; 2972 cellLayout.cellToPoint(mTargetCell[0], mTargetCell[1], loc); 2973 2974 RectF r = new RectF(); 2975 cellLayout.cellToRect(mTargetCell[0], mTargetCell[1], spanX, spanY, r); 2976 setFinalTransitionTransform(cellLayout); 2977 float cellLayoutScale = 2978 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(cellLayout, loc); 2979 resetTransitionTransform(cellLayout); 2980 2981 float dragViewScale = r.width() / d.dragView.getMeasuredWidth(); 2982 // The animation will scale the dragView about its center, so we need to center about 2983 // the final location. 2984 loc[0] -= (d.dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2; 2985 loc[1] -= (d.dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2; 2986 2987 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, loc, 2988 dragViewScale * cellLayoutScale, onAnimationCompleteRunnable); 2989 } else { 2990 // This is for other drag/drop cases, like dragging from All Apps 2991 View view = null; 2992 2993 switch (info.itemType) { 2994 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 2995 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 2996 if (info.container == NO_ID && info instanceof ApplicationInfo) { 2997 // Came from all apps -- make a copy 2998 info = new ShortcutInfo((ApplicationInfo) info); 2999 } 3000 view = mLauncher.createShortcut(R.layout.application, cellLayout, 3001 (ShortcutInfo) info); 3002 break; 3003 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: 3004 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, 3005 (FolderInfo) info, mIconCache); 3006 break; 3007 default: 3008 throw new IllegalStateException("Unknown item type: " + info.itemType); 3009 } 3010 3011 // First we find the cell nearest to point at which the item is 3012 // dropped, without any consideration to whether there is an item there. 3013 if (touchXY != null) { 3014 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY, 3015 cellLayout, mTargetCell); 3016 d.postAnimationRunnable = exitSpringLoadedRunnable; 3017 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, true, 3018 d.dragView, d.postAnimationRunnable)) { 3019 return; 3020 } 3021 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, d, true)) { 3022 return; 3023 } 3024 } 3025 3026 if (touchXY != null) { 3027 // when dragging and dropping, just find the closest free spot 3028 mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, null, 3029 cellLayout, mTargetCell); 3030 } else { 3031 cellLayout.findCellForSpan(mTargetCell, 1, 1); 3032 } 3033 addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX, 3034 info.spanY, insertAtFirst); 3035 cellLayout.onDropChild(view); 3036 cellLayout.animateDrop(); 3037 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 3038 cellLayout.getChildrenLayout().measureChild(view); 3039 3040 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, 3041 lp.cellX, lp.cellY); 3042 3043 if (d.dragView != null) { 3044 // We wrap the animation call in the temporary set and reset of the current 3045 // cellLayout to its final transform -- this means we animate the drag view to 3046 // the correct final location. 3047 setFinalTransitionTransform(cellLayout); 3048 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, 3049 exitSpringLoadedRunnable); 3050 resetTransitionTransform(cellLayout); 3051 } 3052 } 3053 } 3054 3055 public void setFinalTransitionTransform(CellLayout layout) { 3056 if (isSwitchingState()) { 3057 int index = indexOfChild(layout); 3058 mCurrentScaleX = layout.getScaleX(); 3059 mCurrentScaleY = layout.getScaleY(); 3060 mCurrentTranslationX = layout.getTranslationX(); 3061 mCurrentTranslationY = layout.getTranslationY(); 3062 mCurrentRotationY = layout.getRotationY(); 3063 layout.setScaleX(mNewScaleXs[index]); 3064 layout.setScaleY(mNewScaleYs[index]); 3065 layout.setTranslationX(mNewTranslationXs[index]); 3066 layout.setTranslationY(mNewTranslationYs[index]); 3067 layout.setRotationY(mNewRotationYs[index]); 3068 } 3069 } 3070 public void resetTransitionTransform(CellLayout layout) { 3071 if (isSwitchingState()) { 3072 int index = indexOfChild(layout); 3073 mCurrentScaleX = layout.getScaleX(); 3074 mCurrentScaleY = layout.getScaleY(); 3075 mCurrentTranslationX = layout.getTranslationX(); 3076 mCurrentTranslationY = layout.getTranslationY(); 3077 mCurrentRotationY = layout.getRotationY(); 3078 layout.setScaleX(mCurrentScaleX); 3079 layout.setScaleY(mCurrentScaleY); 3080 layout.setTranslationX(mCurrentTranslationX); 3081 layout.setTranslationY(mCurrentTranslationY); 3082 layout.setRotationY(mCurrentRotationY); 3083 } 3084 } 3085 3086 /** 3087 * Return the current {@link CellLayout}, correctly picking the destination 3088 * screen while a scroll is in progress. 3089 */ 3090 public CellLayout getCurrentDropLayout() { 3091 return (CellLayout) getChildAt(mNextPage == INVALID_PAGE ? mCurrentPage : mNextPage); 3092 } 3093 3094 /** 3095 * Return the current CellInfo describing our current drag; this method exists 3096 * so that Launcher can sync this object with the correct info when the activity is created/ 3097 * destroyed 3098 * 3099 */ 3100 public CellLayout.CellInfo getDragInfo() { 3101 return mDragInfo; 3102 } 3103 3104 /** 3105 * Calculate the nearest cell where the given object would be dropped. 3106 * 3107 * pixelX and pixelY should be in the coordinate system of layout 3108 */ 3109 private int[] findNearestVacantArea(int pixelX, int pixelY, 3110 int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) { 3111 return layout.findNearestVacantArea( 3112 pixelX, pixelY, spanX, spanY, ignoreView, recycle); 3113 } 3114 3115 /** 3116 * Calculate the nearest cell where the given object would be dropped. 3117 * 3118 * pixelX and pixelY should be in the coordinate system of layout 3119 */ 3120 private int[] findNearestArea(int pixelX, int pixelY, 3121 int spanX, int spanY, CellLayout layout, int[] recycle) { 3122 return layout.findNearestArea( 3123 pixelX, pixelY, spanX, spanY, recycle); 3124 } 3125 3126 void setup(Launcher launcher, DragController dragController) { 3127 mLauncher = launcher; 3128 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); 3129 mDragController = dragController; 3130 3131 3132 // hardware layers on children are enabled on startup, but should be disabled until 3133 // needed 3134 updateChildrenLayersEnabled(); 3135 setWallpaperDimension(); 3136 } 3137 3138 /** 3139 * Called at the end of a drag which originated on the workspace. 3140 */ 3141 public void onDropCompleted(View target, DragObject d, boolean success) { 3142 if (success) { 3143 if (target != this) { 3144 if (mDragInfo != null) { 3145 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell); 3146 if (mDragInfo.cell instanceof DropTarget) { 3147 mDragController.removeDropTarget((DropTarget) mDragInfo.cell); 3148 } 3149 } 3150 } 3151 } else if (mDragInfo != null) { 3152 // NOTE: When 'success' is true, onDragExit is called by the DragController before 3153 // calling onDropCompleted(). We call it ourselves here, but maybe this should be 3154 // moved into DragController.cancelDrag(). 3155 doDragExit(null); 3156 CellLayout cellLayout; 3157 if (mLauncher.isHotseatLayout(target)) { 3158 cellLayout = mLauncher.getHotseat().getLayout(); 3159 } else { 3160 cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 3161 } 3162 cellLayout.onDropChild(mDragInfo.cell); 3163 } 3164 mDragOutline = null; 3165 mDragInfo = null; 3166 } 3167 3168 public boolean isDropEnabled() { 3169 return true; 3170 } 3171 3172 @Override 3173 protected void onRestoreInstanceState(Parcelable state) { 3174 super.onRestoreInstanceState(state); 3175 Launcher.setScreen(mCurrentPage); 3176 } 3177 3178 @Override 3179 public void scrollLeft() { 3180 if (!isSmall() && !mIsSwitchingState) { 3181 super.scrollLeft(); 3182 } 3183 Folder openFolder = getOpenFolder(); 3184 if (openFolder != null) { 3185 openFolder.completeDragExit(); 3186 } 3187 } 3188 3189 @Override 3190 public void scrollRight() { 3191 if (!isSmall() && !mIsSwitchingState) { 3192 super.scrollRight(); 3193 } 3194 Folder openFolder = getOpenFolder(); 3195 if (openFolder != null) { 3196 openFolder.completeDragExit(); 3197 } 3198 } 3199 3200 @Override 3201 public void onEnterScrollArea(int direction) { 3202 if (!isSmall() && !mIsSwitchingState) { 3203 mInScrollArea = true; 3204 3205 final int page = mCurrentPage + (direction == DragController.SCROLL_LEFT ? -1 : 1); 3206 final CellLayout layout = (CellLayout) getChildAt(page); 3207 cancelFolderCreation(); 3208 3209 if (layout != null) { 3210 // Exit the current layout and mark the overlapping layout 3211 if (mDragTargetLayout != null) { 3212 mDragTargetLayout.setIsDragOverlapping(false); 3213 mDragTargetLayout.onDragExit(); 3214 } 3215 mDragTargetLayout = layout; 3216 mDragTargetLayout.setIsDragOverlapping(true); 3217 3218 // Workspace is responsible for drawing the edge glow on adjacent pages, 3219 // so we need to redraw the workspace when this may have changed. 3220 invalidate(); 3221 } 3222 } 3223 } 3224 3225 @Override 3226 public void onExitScrollArea() { 3227 if (mInScrollArea) { 3228 if (mDragTargetLayout != null) { 3229 // Unmark the overlapping layout and re-enter the current layout 3230 mDragTargetLayout.setIsDragOverlapping(false); 3231 mDragTargetLayout = getCurrentDropLayout(); 3232 mDragTargetLayout.onDragEnter(); 3233 3234 // Workspace is responsible for drawing the edge glow on adjacent pages, 3235 // so we need to redraw the workspace when this may have changed. 3236 invalidate(); 3237 } 3238 mInScrollArea = false; 3239 } 3240 } 3241 3242 private void onResetScrollArea() { 3243 if (mDragTargetLayout != null) { 3244 // Unmark the overlapping layout 3245 mDragTargetLayout.setIsDragOverlapping(false); 3246 3247 // Workspace is responsible for drawing the edge glow on adjacent pages, 3248 // so we need to redraw the workspace when this may have changed. 3249 invalidate(); 3250 } 3251 mInScrollArea = false; 3252 } 3253 3254 /** 3255 * Returns a specific CellLayout 3256 */ 3257 CellLayout getParentCellLayoutForView(View v) { 3258 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts(); 3259 for (CellLayout layout : layouts) { 3260 if (layout.getChildrenLayout().indexOfChild(v) > -1) { 3261 return layout; 3262 } 3263 } 3264 return null; 3265 } 3266 3267 /** 3268 * Returns a list of all the CellLayouts in the workspace. 3269 */ 3270 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() { 3271 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>(); 3272 int screenCount = getChildCount(); 3273 for (int screen = 0; screen < screenCount; screen++) { 3274 layouts.add(((CellLayout) getChildAt(screen))); 3275 } 3276 if (mLauncher.getHotseat() != null) { 3277 layouts.add(mLauncher.getHotseat().getLayout()); 3278 } 3279 return layouts; 3280 } 3281 3282 /** 3283 * We should only use this to search for specific children. Do not use this method to modify 3284 * CellLayoutChildren directly. 3285 */ 3286 ArrayList<CellLayoutChildren> getWorkspaceAndHotseatCellLayoutChildren() { 3287 ArrayList<CellLayoutChildren> childrenLayouts = new ArrayList<CellLayoutChildren>(); 3288 int screenCount = getChildCount(); 3289 for (int screen = 0; screen < screenCount; screen++) { 3290 childrenLayouts.add(((CellLayout) getChildAt(screen)).getChildrenLayout()); 3291 } 3292 if (mLauncher.getHotseat() != null) { 3293 childrenLayouts.add(mLauncher.getHotseat().getLayout().getChildrenLayout()); 3294 } 3295 return childrenLayouts; 3296 } 3297 3298 public Folder getFolderForTag(Object tag) { 3299 ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren(); 3300 for (CellLayoutChildren layout: childrenLayouts) { 3301 int count = layout.getChildCount(); 3302 for (int i = 0; i < count; i++) { 3303 View child = layout.getChildAt(i); 3304 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 3305 if (child instanceof Folder) { 3306 Folder f = (Folder) child; 3307 if (f.getInfo() == tag && f.getInfo().opened) { 3308 return f; 3309 } 3310 } 3311 } 3312 } 3313 return null; 3314 } 3315 3316 public View getViewForTag(Object tag) { 3317 ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren(); 3318 for (CellLayoutChildren layout: childrenLayouts) { 3319 int count = layout.getChildCount(); 3320 for (int i = 0; i < count; i++) { 3321 View child = layout.getChildAt(i); 3322 if (child.getTag() == tag) { 3323 return child; 3324 } 3325 } 3326 } 3327 return null; 3328 } 3329 3330 void clearDropTargets() { 3331 ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren(); 3332 for (CellLayoutChildren layout: childrenLayouts) { 3333 int childCount = layout.getChildCount(); 3334 for (int j = 0; j < childCount; j++) { 3335 View v = layout.getChildAt(j); 3336 if (v instanceof DropTarget) { 3337 mDragController.removeDropTarget((DropTarget) v); 3338 } 3339 } 3340 } 3341 } 3342 3343 void removeItems(final ArrayList<ApplicationInfo> apps) { 3344 final int screenCount = getChildCount(); 3345 final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); 3346 3347 final HashSet<String> packageNames = new HashSet<String>(); 3348 final int appCount = apps.size(); 3349 for (int i = 0; i < appCount; i++) { 3350 packageNames.add(apps.get(i).componentName.getPackageName()); 3351 } 3352 3353 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts(); 3354 for (final CellLayout layoutParent: cellLayouts) { 3355 final ViewGroup layout = layoutParent.getChildrenLayout(); 3356 3357 // Avoid ANRs by treating each screen separately 3358 post(new Runnable() { 3359 public void run() { 3360 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 3361 childrenToRemove.clear(); 3362 3363 int childCount = layout.getChildCount(); 3364 for (int j = 0; j < childCount; j++) { 3365 final View view = layout.getChildAt(j); 3366 Object tag = view.getTag(); 3367 3368 if (tag instanceof ShortcutInfo) { 3369 final ShortcutInfo info = (ShortcutInfo) tag; 3370 final Intent intent = info.intent; 3371 final ComponentName name = intent.getComponent(); 3372 3373 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 3374 for (String packageName: packageNames) { 3375 if (packageName.equals(name.getPackageName())) { 3376 LauncherModel.deleteItemFromDatabase(mLauncher, info); 3377 childrenToRemove.add(view); 3378 } 3379 } 3380 } 3381 } else if (tag instanceof FolderInfo) { 3382 final FolderInfo info = (FolderInfo) tag; 3383 final ArrayList<ShortcutInfo> contents = info.contents; 3384 final int contentsCount = contents.size(); 3385 final ArrayList<ShortcutInfo> appsToRemoveFromFolder = 3386 new ArrayList<ShortcutInfo>(); 3387 3388 for (int k = 0; k < contentsCount; k++) { 3389 final ShortcutInfo appInfo = contents.get(k); 3390 final Intent intent = appInfo.intent; 3391 final ComponentName name = intent.getComponent(); 3392 3393 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 3394 for (String packageName: packageNames) { 3395 if (packageName.equals(name.getPackageName())) { 3396 appsToRemoveFromFolder.add(appInfo); 3397 } 3398 } 3399 } 3400 } 3401 for (ShortcutInfo item: appsToRemoveFromFolder) { 3402 info.remove(item); 3403 LauncherModel.deleteItemFromDatabase(mLauncher, item); 3404 } 3405 } else if (tag instanceof LauncherAppWidgetInfo) { 3406 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; 3407 final AppWidgetProviderInfo provider = 3408 widgets.getAppWidgetInfo(info.appWidgetId); 3409 if (provider != null) { 3410 for (String packageName: packageNames) { 3411 if (packageName.equals(provider.provider.getPackageName())) { 3412 LauncherModel.deleteItemFromDatabase(mLauncher, info); 3413 childrenToRemove.add(view); 3414 } 3415 } 3416 } 3417 } 3418 } 3419 3420 childCount = childrenToRemove.size(); 3421 for (int j = 0; j < childCount; j++) { 3422 View child = childrenToRemove.get(j); 3423 // Note: We can not remove the view directly from CellLayoutChildren as this 3424 // does not re-mark the spaces as unoccupied. 3425 layoutParent.removeViewInLayout(child); 3426 if (child instanceof DropTarget) { 3427 mDragController.removeDropTarget((DropTarget)child); 3428 } 3429 } 3430 3431 if (childCount > 0) { 3432 layout.requestLayout(); 3433 layout.invalidate(); 3434 } 3435 } 3436 }); 3437 } 3438 } 3439 3440 void updateShortcuts(ArrayList<ApplicationInfo> apps) { 3441 ArrayList<CellLayoutChildren> childrenLayouts = getWorkspaceAndHotseatCellLayoutChildren(); 3442 for (CellLayoutChildren layout: childrenLayouts) { 3443 int childCount = layout.getChildCount(); 3444 for (int j = 0; j < childCount; j++) { 3445 final View view = layout.getChildAt(j); 3446 Object tag = view.getTag(); 3447 if (tag instanceof ShortcutInfo) { 3448 ShortcutInfo info = (ShortcutInfo)tag; 3449 // We need to check for ACTION_MAIN otherwise getComponent() might 3450 // return null for some shortcuts (for instance, for shortcuts to 3451 // web pages.) 3452 final Intent intent = info.intent; 3453 final ComponentName name = intent.getComponent(); 3454 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 3455 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 3456 final int appCount = apps.size(); 3457 for (int k = 0; k < appCount; k++) { 3458 ApplicationInfo app = apps.get(k); 3459 if (app.componentName.equals(name)) { 3460 info.setIcon(mIconCache.getIcon(info.intent)); 3461 ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null, 3462 new FastBitmapDrawable(info.getIcon(mIconCache)), 3463 null, null); 3464 } 3465 } 3466 } 3467 } 3468 } 3469 } 3470 } 3471 3472 void moveToDefaultScreen(boolean animate) { 3473 if (isSmall() || mIsSwitchingState) { 3474 mLauncher.showWorkspace(animate, (CellLayout) getChildAt(mDefaultPage)); 3475 } else if (animate) { 3476 snapToPage(mDefaultPage); 3477 } else { 3478 setCurrentPage(mDefaultPage); 3479 } 3480 getChildAt(mDefaultPage).requestFocus(); 3481 } 3482 3483 @Override 3484 public void syncPages() { 3485 } 3486 3487 @Override 3488 public void syncPageItems(int page) { 3489 } 3490 3491 @Override 3492 protected String getCurrentPageDescription() { 3493 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 3494 return String.format(mContext.getString(R.string.workspace_scroll_format), 3495 page + 1, getChildCount()); 3496 } 3497 3498 public void getLocationInDragLayer(int[] loc) { 3499 mLauncher.getDragLayer().getLocationInDragLayer(this, loc); 3500 } 3501 3502 /** 3503 * Return true because we want the scrolling indicator to stretch to fit the space. 3504 */ 3505 protected boolean hasElasticScrollIndicator() { 3506 return true; 3507 } 3508} 3509