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