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