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