Workspace.java revision 4516c11039d77066281f69f9b5d30fdaf1bc05ae
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 com.android.launcher.R; 20 21import android.animation.Animator; 22import android.animation.Animator.AnimatorListener; 23import android.animation.AnimatorSet; 24import android.animation.ObjectAnimator; 25import android.animation.PropertyValuesHolder; 26import android.app.WallpaperManager; 27import android.appwidget.AppWidgetManager; 28import android.appwidget.AppWidgetProviderInfo; 29import android.content.ComponentName; 30import android.content.Context; 31import android.content.Intent; 32import android.content.pm.PackageManager; 33import android.content.pm.ProviderInfo; 34import android.content.res.Resources; 35import android.content.res.TypedArray; 36import android.graphics.Canvas; 37import android.graphics.Matrix; 38import android.graphics.Rect; 39import android.graphics.drawable.Drawable; 40import android.net.Uri; 41import android.os.IBinder; 42import android.os.Parcelable; 43import android.util.AttributeSet; 44import android.util.Log; 45import android.view.MotionEvent; 46import android.view.View; 47import android.widget.TextView; 48 49import java.util.ArrayList; 50import java.util.HashSet; 51 52/** 53 * The workspace is a wide area with a wallpaper and a finite number of pages. 54 * Each page contains a number of icons, folders or widgets the user can 55 * interact with. A workspace is meant to be used with a fixed width only. 56 */ 57public class Workspace extends SmoothPagedView 58 implements DropTarget, DragSource, DragScroller, View.OnTouchListener { 59 @SuppressWarnings({"UnusedDeclaration"}) 60 private static final String TAG = "Launcher.Workspace"; 61 62 // This is how much the workspace shrinks when we enter all apps or 63 // customization mode 64 private static final float SHRINK_FACTOR = 0.16f; 65 66 // Y rotation to apply to the workspace screens 67 private static final float WORKSPACE_ROTATION = 12.5f; 68 69 // These are extra scale factors to apply to the mini home screens 70 // so as to achieve the desired transform 71 private static final float EXTRA_SCALE_FACTOR_0 = 0.97f; 72 private static final float EXTRA_SCALE_FACTOR_1 = 1.0f; 73 private static final float EXTRA_SCALE_FACTOR_2 = 1.08f; 74 75 private static final int BACKGROUND_FADE_OUT_DELAY = 300; 76 private static final int BACKGROUND_FADE_OUT_DURATION = 300; 77 private static final int BACKGROUND_FADE_IN_DURATION = 100; 78 79 // These animators are used to fade the 80 private ObjectAnimator<Float> mBackgroundFadeIn; 81 private ObjectAnimator<Float> mBackgroundFadeOut; 82 private float mBackgroundAlpha = 0; 83 84 private enum ShrinkPosition { SHRINK_TO_TOP, SHRINK_TO_MIDDLE, SHRINK_TO_BOTTOM }; 85 86 private final WallpaperManager mWallpaperManager; 87 88 private int mDefaultPage; 89 90 private boolean mWaitingToShrinkToBottom = false; 91 92 private boolean mPageMoving = false; 93 94 /** 95 * CellInfo for the cell that is currently being dragged 96 */ 97 private CellLayout.CellInfo mDragInfo; 98 99 /** 100 * Target drop area calculated during last acceptDrop call. 101 */ 102 private int[] mTargetCell = null; 103 104 /** 105 * The CellLayout that is currently being dragged over 106 */ 107 private CellLayout mDragTargetLayout = null; 108 109 private Launcher mLauncher; 110 private IconCache mIconCache; 111 private DragController mDragController; 112 113 // These are temporary variables to prevent having to allocate a new object just to 114 // return an (x, y) value from helper functions. Do NOT use them to maintain other state. 115 private int[] mTempCell = new int[2]; 116 private int[] mTempEstimate = new int[2]; 117 private float[] mTempOriginXY = new float[2]; 118 private float[] mTempDragCoordinates = new float[2]; 119 private float[] mTempTouchCoordinates = new float[2]; 120 private float[] mTempCellLayoutCenterCoordinates = new float[2]; 121 private float[] mTempDragBottomRightCoordinates = new float[2]; 122 private Matrix mTempInverseMatrix = new Matrix(); 123 124 private static final int DEFAULT_CELL_COUNT_X = 4; 125 private static final int DEFAULT_CELL_COUNT_Y = 4; 126 127 private Drawable mPreviousIndicator; 128 private Drawable mNextIndicator; 129 130 // State variable that indicates whether the pages are small (ie when you're 131 // in all apps or customize mode) 132 private boolean mIsSmall = false; 133 private boolean mIsInUnshrinkAnimation = false; 134 private AnimatorListener mUnshrinkAnimationListener; 135 136 private boolean mInScrollArea = false; 137 138 /** 139 * Used to inflate the Workspace from XML. 140 * 141 * @param context The application's context. 142 * @param attrs The attributes set containing the Workspace's customization values. 143 */ 144 public Workspace(Context context, AttributeSet attrs) { 145 this(context, attrs, 0); 146 } 147 148 /** 149 * Used to inflate the Workspace from XML. 150 * 151 * @param context The application's context. 152 * @param attrs The attributes set containing the Workspace's customization values. 153 * @param defStyle Unused. 154 */ 155 public Workspace(Context context, AttributeSet attrs, int defStyle) { 156 super(context, attrs, defStyle); 157 mContentIsRefreshable = false; 158 159 if (!LauncherApplication.isScreenXLarge()) { 160 mFadeInAdjacentScreens = false; 161 } 162 163 mWallpaperManager = WallpaperManager.getInstance(context); 164 165 TypedArray a = context.obtainStyledAttributes(attrs, 166 R.styleable.Workspace, defStyle, 0); 167 int cellCountX = a.getInt(R.styleable.Workspace_cellCountX, DEFAULT_CELL_COUNT_X); 168 int cellCountY = a.getInt(R.styleable.Workspace_cellCountY, DEFAULT_CELL_COUNT_Y); 169 mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1); 170 a.recycle(); 171 172 LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY); 173 setHapticFeedbackEnabled(false); 174 175 initWorkspace(); 176 } 177 178 /** 179 * Initializes various states for this workspace. 180 */ 181 protected void initWorkspace() { 182 Context context = getContext(); 183 mCurrentPage = mDefaultPage; 184 Launcher.setScreen(mCurrentPage); 185 LauncherApplication app = (LauncherApplication)context.getApplicationContext(); 186 mIconCache = app.getIconCache(); 187 188 mUnshrinkAnimationListener = new AnimatorListener() { 189 public void onAnimationStart(Animator animation) { 190 mIsInUnshrinkAnimation = true; 191 } 192 public void onAnimationEnd(Animator animation) { 193 mIsInUnshrinkAnimation = false; 194 } 195 public void onAnimationCancel(Animator animation) {} 196 public void onAnimationRepeat(Animator animation) {} 197 }; 198 199 mSnapVelocity = 600; 200 } 201 202 @Override 203 protected int getScrollMode() { 204 if (LauncherApplication.isScreenXLarge()) { 205 return SmoothPagedView.QUINTIC_MODE; 206 } else { 207 return SmoothPagedView.OVERSHOOT_MODE; 208 } 209 } 210 211 @Override 212 public void addView(View child, int index, LayoutParams params) { 213 if (!(child instanceof CellLayout)) { 214 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 215 } 216 ((CellLayout) child).setOnInterceptTouchListener(this); 217 super.addView(child, index, params); 218 } 219 220 @Override 221 public void addView(View child) { 222 if (!(child instanceof CellLayout)) { 223 throw new IllegalArgumentException("A Workspace can only have CellLayout children."); 224 } 225 ((CellLayout) child).setOnInterceptTouchListener(this); 226 super.addView(child); 227 } 228 229 @Override 230 public void addView(View child, int index) { 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); 236 } 237 238 @Override 239 public void addView(View child, int width, int height) { 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, width, height); 245 } 246 247 @Override 248 public void addView(View child, LayoutParams params) { 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, params); 254 } 255 256 /** 257 * @return The open folder on the current screen, or null if there is none 258 */ 259 Folder getOpenFolder() { 260 CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage); 261 int count = currentPage.getChildCount(); 262 for (int i = 0; i < count; i++) { 263 View child = currentPage.getChildAt(i); 264 if (child instanceof Folder) { 265 Folder folder = (Folder) child; 266 if (folder.getInfo().opened) 267 return folder; 268 } 269 } 270 return null; 271 } 272 273 ArrayList<Folder> getOpenFolders() { 274 final int screenCount = getChildCount(); 275 ArrayList<Folder> folders = new ArrayList<Folder>(screenCount); 276 277 for (int screen = 0; screen < screenCount; screen++) { 278 CellLayout currentPage = (CellLayout) getChildAt(screen); 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 folders.add(folder); 286 break; 287 } 288 } 289 } 290 291 return folders; 292 } 293 294 boolean isDefaultPageShowing() { 295 return mCurrentPage == mDefaultPage; 296 } 297 298 /** 299 * Sets the current screen. 300 * 301 * @param currentPage 302 */ 303 @Override 304 void setCurrentPage(int currentPage) { 305 super.setCurrentPage(currentPage); 306 updateWallpaperOffset(mScrollX); 307 } 308 309 /** 310 * Adds the specified child in the specified screen. The position and dimension of 311 * the child are defined by x, y, spanX and spanY. 312 * 313 * @param child The child to add in one of the workspace's screens. 314 * @param screen The screen in which to add the child. 315 * @param x The X position of the child in the screen's grid. 316 * @param y The Y position of the child in the screen's grid. 317 * @param spanX The number of cells spanned horizontally by the child. 318 * @param spanY The number of cells spanned vertically by the child. 319 */ 320 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) { 321 addInScreen(child, screen, x, y, spanX, spanY, false); 322 } 323 324 void addInFullScreen(View child, int screen) { 325 addInScreen(child, screen, 0, 0, -1, -1); 326 } 327 328 /** 329 * Adds the specified child in the specified screen. The position and dimension of 330 * the child are defined by x, y, spanX and spanY. 331 * 332 * @param child The child to add in one of the workspace's screens. 333 * @param screen The screen in which to add the child. 334 * @param x The X position of the child in the screen's grid. 335 * @param y The Y position of the child in the screen's grid. 336 * @param spanX The number of cells spanned horizontally by the child. 337 * @param spanY The number of cells spanned vertically by the child. 338 * @param insert When true, the child is inserted at the beginning of the children list. 339 */ 340 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) { 341 if (screen < 0 || screen >= getChildCount()) { 342 Log.e(TAG, "The screen must be >= 0 and < " + getChildCount() 343 + " (was " + screen + "); skipping child"); 344 return; 345 } 346 347 final CellLayout group = (CellLayout) getChildAt(screen); 348 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 349 if (lp == null) { 350 lp = new CellLayout.LayoutParams(x, y, spanX, spanY); 351 } else { 352 lp.cellX = x; 353 lp.cellY = y; 354 lp.cellHSpan = spanX; 355 lp.cellVSpan = spanY; 356 } 357 358 // Get the canonical child id to uniquely represent this view in this screen 359 int childId = LauncherModel.getCellLayoutChildId(-1, screen, x, y, spanX, spanY); 360 if (!group.addViewToCellLayout(child, insert ? 0 : -1, childId, lp)) { 361 // TODO: This branch occurs when the workspace is adding views 362 // outside of the defined grid 363 // maybe we should be deleting these items from the LauncherModel? 364 Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout"); 365 } 366 367 if (!(child instanceof Folder)) { 368 child.setHapticFeedbackEnabled(false); 369 child.setOnLongClickListener(mLongClickListener); 370 } 371 if (child instanceof DropTarget) { 372 mDragController.addDropTarget((DropTarget) child); 373 } 374 } 375 376 public boolean onTouch(View v, MotionEvent event) { 377 // this is an intercepted event being forwarded from a cell layout 378 if (mIsSmall || mIsInUnshrinkAnimation) { 379 mLauncher.onWorkspaceClick((CellLayout) v); 380 return true; 381 } else if (!mPageMoving) { 382 if (v == getChildAt(mCurrentPage - 1)) { 383 snapToPage(mCurrentPage - 1); 384 return true; 385 } else if (v == getChildAt(mCurrentPage + 1)) { 386 snapToPage(mCurrentPage + 1); 387 return true; 388 } 389 } 390 return false; 391 } 392 393 @Override 394 public boolean dispatchUnhandledMove(View focused, int direction) { 395 if (mIsSmall || mIsInUnshrinkAnimation) { 396 // when the home screens are shrunken, shouldn't allow side-scrolling 397 return false; 398 } 399 return super.dispatchUnhandledMove(focused, direction); 400 } 401 402 @Override 403 public boolean onInterceptTouchEvent(MotionEvent ev) { 404 if (mIsSmall || mIsInUnshrinkAnimation) { 405 // when the home screens are shrunken, shouldn't allow side-scrolling 406 return false; 407 } 408 return super.onInterceptTouchEvent(ev); 409 } 410 411 protected void onPageBeginMoving() { 412 if (mNextPage != INVALID_PAGE) { 413 // we're snapping to a particular screen 414 enableChildrenCache(mCurrentPage, mNextPage); 415 } else { 416 // this is when user is actively dragging a particular screen, they might 417 // swipe it either left or right (but we won't advance by more than one screen) 418 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1); 419 } 420 showOutlines(); 421 mPageMoving = true; 422 } 423 424 protected void onPageEndMoving() { 425 clearChildrenCache(); 426 // Hide the outlines, as long as we're not dragging 427 if (!mDragController.dragging()) { 428 hideOutlines(); 429 } 430 mPageMoving = false; 431 } 432 433 @Override 434 protected void notifyPageSwitchListener() { 435 super.notifyPageSwitchListener(); 436 437 if (mPreviousIndicator != null) { 438 // if we know the next page, we show the indication for it right away; it looks 439 // weird if the indicators are lagging 440 int page = mNextPage; 441 if (page == INVALID_PAGE) { 442 page = mCurrentPage; 443 } 444 mPreviousIndicator.setLevel(page); 445 mNextIndicator.setLevel(page); 446 } 447 Launcher.setScreen(mCurrentPage); 448 }; 449 450 private void updateWallpaperOffset() { 451 updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft)); 452 } 453 454 private void updateWallpaperOffset(int scrollRange) { 455 final boolean isStaticWallpaper = (mWallpaperManager != null) && 456 (mWallpaperManager.getWallpaperInfo() == null); 457 if (LauncherApplication.isScreenXLarge() && !isStaticWallpaper) { 458 IBinder token = getWindowToken(); 459 if (token != null) { 460 mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 ); 461 mWallpaperManager.setWallpaperOffsets(getWindowToken(), 462 Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0); 463 } 464 } 465 } 466 467 public void showOutlines() { 468 if (mBackgroundFadeOut != null) mBackgroundFadeOut.cancel(); 469 if (mBackgroundFadeIn != null) mBackgroundFadeIn.cancel(); 470 mBackgroundFadeIn = new ObjectAnimator<Float>(BACKGROUND_FADE_IN_DURATION, this, 471 new PropertyValuesHolder<Float>("backgroundAlpha", 1.0f)); 472 mBackgroundFadeIn.start(); 473 } 474 475 public void hideOutlines() { 476 if (mBackgroundFadeIn != null) mBackgroundFadeIn.cancel(); 477 if (mBackgroundFadeOut != null) mBackgroundFadeOut.cancel(); 478 mBackgroundFadeOut = new ObjectAnimator<Float>(BACKGROUND_FADE_OUT_DURATION, this, 479 new PropertyValuesHolder<Float>("backgroundAlpha", 0.0f)); 480 mBackgroundFadeOut.setStartDelay(BACKGROUND_FADE_OUT_DELAY); 481 mBackgroundFadeOut.start(); 482 } 483 484 public void setBackgroundAlpha(float alpha) { 485 mBackgroundAlpha = alpha; 486 for (int i = 0; i < getChildCount(); i++) { 487 CellLayout cl = (CellLayout) getChildAt(i); 488 cl.setBackgroundAlpha(alpha); 489 } 490 } 491 492 public float getBackgroundAlpha() { 493 return mBackgroundAlpha; 494 } 495 496 @Override 497 protected void screenScrolled(int screenCenter) { 498 final int halfScreenSize = getMeasuredWidth() / 2; 499 for (int i = 0; i < getChildCount(); i++) { 500 View v = getChildAt(i); 501 if (v != null) { 502 int totalDistance = v.getMeasuredWidth() + mPageSpacing; 503 int delta = screenCenter - (getChildOffset(i) - 504 getRelativeChildOffset(i) + halfScreenSize); 505 506 float scrollProgress = delta/(totalDistance*1.0f); 507 scrollProgress = Math.min(scrollProgress, 1.0f); 508 scrollProgress = Math.max(scrollProgress, -1.0f); 509 510 float rotation = WORKSPACE_ROTATION * scrollProgress; 511 v.setRotationY(rotation); 512 } 513 } 514 } 515 516 protected void onAttachedToWindow() { 517 super.onAttachedToWindow(); 518 computeScroll(); 519 mDragController.setWindowToken(getWindowToken()); 520 } 521 522 @Override 523 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 524 super.onLayout(changed, left, top, right, bottom); 525 526 // if shrinkToBottom() is called on initialization, it has to be deferred 527 // until after the first call to onLayout so that it has the correct width 528 if (mWaitingToShrinkToBottom) { 529 shrinkToBottom(false); 530 mWaitingToShrinkToBottom = false; 531 } 532 533 if (LauncherApplication.isInPlaceRotationEnabled()) { 534 // When the device is rotated, the scroll position of the current screen 535 // needs to be refreshed 536 setCurrentPage(getCurrentPage()); 537 } 538 } 539 540 @Override 541 protected void dispatchDraw(Canvas canvas) { 542 if (mIsSmall || mIsInUnshrinkAnimation) { 543 // Draw all the workspaces if we're small 544 final int pageCount = getChildCount(); 545 final long drawingTime = getDrawingTime(); 546 for (int i = 0; i < pageCount; i++) { 547 final View page = (View) getChildAt(i); 548 549 drawChild(canvas, page, drawingTime); 550 } 551 } else { 552 super.dispatchDraw(canvas); 553 } 554 } 555 556 @Override 557 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 558 if (!mLauncher.isAllAppsVisible()) { 559 final Folder openFolder = getOpenFolder(); 560 if (openFolder != null) { 561 return openFolder.requestFocus(direction, previouslyFocusedRect); 562 } else { 563 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 564 } 565 } 566 return false; 567 } 568 569 @Override 570 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 571 if (!mLauncher.isAllAppsVisible()) { 572 final Folder openFolder = getOpenFolder(); 573 if (openFolder != null) { 574 openFolder.addFocusables(views, direction); 575 } else { 576 super.addFocusables(views, direction, focusableMode); 577 } 578 } 579 } 580 581 @Override 582 public boolean dispatchTouchEvent(MotionEvent ev) { 583 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 584 // (In XLarge mode, the workspace is shrunken below all apps, and responds to taps 585 // ie when you click on a mini-screen, it zooms back to that screen) 586 if (!LauncherApplication.isScreenXLarge() && mLauncher.isAllAppsVisible()) { 587 return false; 588 } 589 } 590 return super.dispatchTouchEvent(ev); 591 } 592 593 void enableChildrenCache(int fromPage, int toPage) { 594 if (fromPage > toPage) { 595 final int temp = fromPage; 596 fromPage = toPage; 597 toPage = temp; 598 } 599 600 final int screenCount = getChildCount(); 601 602 fromPage = Math.max(fromPage, 0); 603 toPage = Math.min(toPage, screenCount - 1); 604 605 for (int i = fromPage; i <= toPage; i++) { 606 final CellLayout layout = (CellLayout) getChildAt(i); 607 layout.setChildrenDrawnWithCacheEnabled(true); 608 layout.setChildrenDrawingCacheEnabled(true); 609 } 610 } 611 612 void clearChildrenCache() { 613 final int screenCount = getChildCount(); 614 for (int i = 0; i < screenCount; i++) { 615 final CellLayout layout = (CellLayout) getChildAt(i); 616 layout.setChildrenDrawnWithCacheEnabled(false); 617 } 618 } 619 620 @Override 621 public boolean onTouchEvent(MotionEvent ev) { 622 if (mLauncher.isAllAppsVisible()) { 623 // Cancel any scrolling that is in progress. 624 if (!mScroller.isFinished()) { 625 mScroller.abortAnimation(); 626 } 627 snapToPage(mCurrentPage); 628 return false; // We don't want the events. Let them fall through to the all apps view. 629 } 630 631 return super.onTouchEvent(ev); 632 } 633 634 public boolean isSmall() { 635 return mIsSmall; 636 } 637 638 void shrinkToTop(boolean animated) { 639 shrink(ShrinkPosition.SHRINK_TO_TOP, animated); 640 } 641 642 void shrinkToMiddle() { 643 shrink(ShrinkPosition.SHRINK_TO_MIDDLE, true); 644 } 645 646 void shrinkToBottom() { 647 shrinkToBottom(true); 648 } 649 650 void shrinkToBottom(boolean animated) { 651 if (mFirstLayout) { 652 // (mFirstLayout == "first layout has not happened yet") 653 // if we get a call to shrink() as part of our initialization (for example, if 654 // Launcher is started in All Apps mode) then we need to wait for a layout call 655 // to get our width so we can layout the mini-screen views correctly 656 mWaitingToShrinkToBottom = true; 657 } else { 658 shrink(ShrinkPosition.SHRINK_TO_BOTTOM, animated); 659 } 660 } 661 662 private float getYScaleForScreen(int screen) { 663 int x = Math.abs(screen - 2); 664 665 // TODO: This should be generalized for use with arbitrary rotation angles. 666 switch(x) { 667 case 0: return EXTRA_SCALE_FACTOR_0; 668 case 1: return EXTRA_SCALE_FACTOR_1; 669 case 2: return EXTRA_SCALE_FACTOR_2; 670 } 671 return 1.0f; 672 } 673 674 // we use this to shrink the workspace for the all apps view and the customize view 675 private void shrink(ShrinkPosition shrinkPosition, boolean animated) { 676 mIsSmall = true; 677 // we intercept and reject all touch events when we're small, so be sure to reset the state 678 mTouchState = TOUCH_STATE_REST; 679 mActivePointerId = INVALID_POINTER; 680 681 final Resources res = getResources(); 682 final int screenWidth = getWidth(); 683 final int screenHeight = getHeight(); 684 685 // Making the assumption that all pages have the same width as the 0th 686 final int pageWidth = getChildAt(0).getMeasuredWidth(); 687 final int pageHeight = getChildAt(0).getMeasuredHeight(); 688 689 final int scaledPageWidth = (int) (SHRINK_FACTOR * pageWidth); 690 final int scaledPageHeight = (int) (SHRINK_FACTOR * pageHeight); 691 final float extraScaledSpacing = res.getDimension(R.dimen.smallScreenExtraSpacing); 692 693 final int screenCount = getChildCount(); 694 float totalWidth = screenCount * scaledPageWidth + (screenCount - 1) * extraScaledSpacing; 695 696 float newY = getResources().getDimension(R.dimen.smallScreenVerticalMargin); 697 if (shrinkPosition == ShrinkPosition.SHRINK_TO_BOTTOM) { 698 newY = screenHeight - newY - scaledPageHeight; 699 } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_MIDDLE) { 700 newY = screenHeight / 2 - scaledPageHeight / 2; 701 } 702 703 // We animate all the screens to the centered position in workspace 704 // At the same time, the screens become greyed/dimmed 705 706 // newX is initialized to the left-most position of the centered screens 707 float newX = mScroller.getFinalX() + screenWidth / 2 - totalWidth / 2; 708 709 // We are going to scale about the center of the view, so we need to adjust the positions 710 // of the views accordingly 711 newX -= (pageWidth - scaledPageWidth) / 2.0f; 712 newY -= (pageHeight - scaledPageHeight) / 2.0f; 713 for (int i = 0; i < screenCount; i++) { 714 CellLayout cl = (CellLayout) getChildAt(i); 715 716 float rotation = (-i + 2) * WORKSPACE_ROTATION; 717 float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f)); 718 float rotationScaleY = getYScaleForScreen(i); 719 720 if (animated) { 721 final int duration = res.getInteger(R.integer.config_workspaceShrinkTime); 722 new ObjectAnimator<Float>(duration, cl, 723 new PropertyValuesHolder<Float>("x", newX), 724 new PropertyValuesHolder<Float>("y", newY), 725 new PropertyValuesHolder<Float>("scaleX", SHRINK_FACTOR * rotationScaleX), 726 new PropertyValuesHolder<Float>("scaleY", SHRINK_FACTOR * rotationScaleY), 727 new PropertyValuesHolder<Float>("backgroundAlpha", 1.0f), 728 new PropertyValuesHolder<Float>("alpha", 1.0f), 729 new PropertyValuesHolder<Float>("rotationY", rotation)).start(); 730 } else { 731 cl.setX((int)newX); 732 cl.setY((int)newY); 733 cl.setScaleX(SHRINK_FACTOR * rotationScaleX); 734 cl.setScaleY(SHRINK_FACTOR * rotationScaleY); 735 cl.setBackgroundAlpha(1.0f); 736 cl.setAlpha(0.0f); 737 cl.setRotationY(rotation); 738 } 739 // increment newX for the next screen 740 newX += scaledPageWidth + extraScaledSpacing; 741 } 742 setChildrenDrawnWithCacheEnabled(true); 743 } 744 745 // We call this when we trigger an unshrink by clicking on the CellLayout cl 746 public void unshrink(CellLayout clThatWasClicked) { 747 int newCurrentPage = mCurrentPage; 748 final int screenCount = getChildCount(); 749 for (int i = 0; i < screenCount; i++) { 750 if (getChildAt(i) == clThatWasClicked) { 751 newCurrentPage = i; 752 } 753 } 754 unshrink(newCurrentPage); 755 } 756 757 @Override 758 protected boolean handlePagingClicks() { 759 return true; 760 } 761 762 private void unshrink(int newCurrentPage) { 763 if (mIsSmall) { 764 int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage); 765 int delta = newX - mScrollX; 766 767 final int screenCount = getChildCount(); 768 for (int i = 0; i < screenCount; i++) { 769 CellLayout cl = (CellLayout) getChildAt(i); 770 cl.setX(cl.getX() + delta); 771 } 772 setCurrentPage(newCurrentPage); 773 unshrink(); 774 } 775 } 776 777 void unshrink() { 778 unshrink(true); 779 } 780 781 void unshrink(boolean animated) { 782 if (mIsSmall) { 783 mIsSmall = false; 784 AnimatorSet s = new AnimatorSet(); 785 final int screenCount = getChildCount(); 786 787 final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime); 788 for (int i = 0; i < screenCount; i++) { 789 final CellLayout cl = (CellLayout)getChildAt(i); 790 float finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f; 791 float rotation = 0.0f; 792 793 if (i < mCurrentPage) { 794 rotation = WORKSPACE_ROTATION; 795 } else if (i > mCurrentPage) { 796 rotation = -WORKSPACE_ROTATION; 797 } 798 799 if (animated) { 800 s.playTogether( 801 new ObjectAnimator<Float>(duration, cl, "translationX", 0.0f), 802 new ObjectAnimator<Float>(duration, cl, "translationY", 0.0f), 803 new ObjectAnimator<Float>(duration, cl, "scaleX", 1.0f), 804 new ObjectAnimator<Float>(duration, cl, "scaleY", 1.0f), 805 new ObjectAnimator<Float>(duration, cl, "backgroundAlpha", 0.0f), 806 new ObjectAnimator<Float>(duration, cl, "alpha", finalAlphaValue), 807 new ObjectAnimator<Float>(duration, cl, "rotationY", rotation)); 808 } else { 809 cl.setTranslationX(0.0f); 810 cl.setTranslationY(0.0f); 811 cl.setScaleX(1.0f); 812 cl.setScaleY(1.0f); 813 cl.setBackgroundAlpha(0.0f); 814 cl.setAlpha(finalAlphaValue); 815 cl.setRotationY(rotation); 816 } 817 } 818 s.addListener(mUnshrinkAnimationListener); 819 s.start(); 820 } 821 } 822 823 void startDrag(CellLayout.CellInfo cellInfo) { 824 View child = cellInfo.cell; 825 826 // Make sure the drag was started by a long press as opposed to a long click. 827 if (!child.isInTouchMode()) { 828 return; 829 } 830 831 mDragInfo = cellInfo; 832 mDragInfo.screen = mCurrentPage; 833 834 CellLayout current = ((CellLayout) getChildAt(mCurrentPage)); 835 836 current.onDragChild(child); 837 mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE); 838 current.onDragEnter(child); 839 invalidate(); 840 } 841 842 void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY, 843 boolean insertAtFirst, int intersectX, int intersectY) { 844 final CellLayout cellLayout = (CellLayout) getChildAt(screen); 845 View view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo) info); 846 847 final int[] cellXY = new int[2]; 848 cellLayout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); 849 addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst); 850 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 851 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 852 cellXY[0], cellXY[1]); 853 } 854 855 856 public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, 857 DragView dragView, Object dragInfo) { 858 CellLayout cellLayout; 859 int originX = x - xOffset; 860 int originY = y - yOffset; 861 if (mIsSmall || mIsInUnshrinkAnimation) { 862 cellLayout = findMatchingPageForDragOver(dragView, originX, originY, xOffset, yOffset); 863 if (cellLayout == null) { 864 // cancel the drag if we're not over a mini-screen at time of drop 865 // TODO: maybe add a nice fade here? 866 return; 867 } 868 // get originX and originY in the local coordinate system of the screen 869 mTempOriginXY[0] = originX; 870 mTempOriginXY[1] = originY; 871 mapPointFromSelfToChild(cellLayout, mTempOriginXY); 872 originX = (int)mTempOriginXY[0]; 873 originY = (int)mTempOriginXY[1]; 874 } else { 875 cellLayout = getCurrentDropLayout(); 876 } 877 878 if (source != this) { 879 onDropExternal(originX, originY, dragInfo, cellLayout); 880 } else { 881 // Move internally 882 if (mDragInfo != null) { 883 final View cell = mDragInfo.cell; 884 885 mTargetCell = findNearestVacantArea(originX, originY, 886 mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, 887 mTargetCell); 888 889 int screen = indexOfChild(cellLayout); 890 if (screen != mDragInfo.screen) { 891 final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen); 892 originalCellLayout.removeView(cell); 893 addInScreen(cell, screen, mTargetCell[0], mTargetCell[1], 894 mDragInfo.spanX, mDragInfo.spanY); 895 } 896 cellLayout.onDropChild(cell); 897 898 // update the item's position after drop 899 final ItemInfo info = (ItemInfo) cell.getTag(); 900 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 901 cellLayout.onMove(cell, mTargetCell[0], mTargetCell[1]); 902 lp.cellX = mTargetCell[0]; 903 lp.cellY = mTargetCell[1]; 904 cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen, 905 mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); 906 907 LauncherModel.moveItemInDatabase(mLauncher, info, 908 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 909 lp.cellX, lp.cellY); 910 } 911 } 912 } 913 914 public void onDragEnter(DragSource source, int x, int y, int xOffset, 915 int yOffset, DragView dragView, Object dragInfo) { 916 getCurrentDropLayout().onDragEnter(dragView); 917 showOutlines(); 918 } 919 920 public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset, 921 DragView dragView, Object dragInfo) { 922 923 if (mIsSmall || mIsInUnshrinkAnimation) { 924 // If we're shrunken, don't let anyone drag on folders/etc that are on the mini-screens 925 return null; 926 } 927 // We may need to delegate the drag to a child view. If a 1x1 item 928 // would land in a cell occupied by a DragTarget (e.g. a Folder), 929 // then drag events should be handled by that child. 930 931 ItemInfo item = (ItemInfo)dragInfo; 932 CellLayout currentLayout = getCurrentDropLayout(); 933 934 int dragPointX, dragPointY; 935 if (item.spanX == 1 && item.spanY == 1) { 936 // For a 1x1, calculate the drop cell exactly as in onDragOver 937 dragPointX = x - xOffset; 938 dragPointY = y - yOffset; 939 } else { 940 // Otherwise, use the exact drag coordinates 941 dragPointX = x; 942 dragPointY = y; 943 } 944 dragPointX += mScrollX - currentLayout.getLeft(); 945 dragPointY += mScrollY - currentLayout.getTop(); 946 947 // If we are dragging over a cell that contains a DropTarget that will 948 // accept the drop, delegate to that DropTarget. 949 final int[] cellXY = mTempCell; 950 currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY); 951 View child = currentLayout.getChildAt(cellXY[0], cellXY[1]); 952 if (child instanceof DropTarget) { 953 DropTarget target = (DropTarget)child; 954 if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) { 955 return target; 956 } 957 } 958 return null; 959 } 960 961 /* 962 * 963 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 964 * coordinate space. The argument xy is modified with the return result. 965 * 966 */ 967 void mapPointFromSelfToChild(View v, float[] xy) { 968 mapPointFromSelfToChild(v, xy, null); 969 } 970 971 /* 972 * 973 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 974 * coordinate space. The argument xy is modified with the return result. 975 * 976 * if cachedInverseMatrix is not null, this method will just use that matrix instead of 977 * computing it itself; we use this to avoid redudant matrix inversions in 978 * findMatchingPageForDragOver 979 * 980 */ 981 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { 982 if (cachedInverseMatrix == null) { 983 v.getMatrix().invert(mTempInverseMatrix); 984 cachedInverseMatrix = mTempInverseMatrix; 985 } 986 xy[0] = xy[0] + mScrollX - v.getLeft(); 987 xy[1] = xy[1] + mScrollY - v.getTop(); 988 cachedInverseMatrix.mapPoints(xy); 989 } 990 991 /* 992 * 993 * Convert the 2D coordinate xy from this CellLayout's coordinate space to 994 * the parent View's coordinate space. The argument xy is modified with the return result. 995 * 996 */ 997 void mapPointFromChildToSelf(View v, float[] xy) { 998 v.getMatrix().mapPoints(xy); 999 xy[0] -= (mScrollX - v.getLeft()); 1000 xy[1] -= (mScrollY - v.getTop()); 1001 } 1002 1003 static private float squaredDistance(float[] point1, float[] point2) { 1004 float distanceX = point1[0] - point2[0]; 1005 float distanceY = point2[1] - point2[1]; 1006 return distanceX * distanceX + distanceY * distanceY; 1007 } 1008 1009 /* 1010 * 1011 * Returns true if the passed CellLayout cl overlaps with dragView 1012 * 1013 */ 1014 boolean overlaps(CellLayout cl, DragView dragView, 1015 int dragViewX, int dragViewY, Matrix cachedInverseMatrix) { 1016 // Transform the coordinates of the item being dragged to the CellLayout's coordinates 1017 final float[] draggedItemTopLeft = mTempDragCoordinates; 1018 draggedItemTopLeft[0] = dragViewX + dragView.getScaledDragRegionXOffset(); 1019 draggedItemTopLeft[1] = dragViewY + dragView.getScaledDragRegionYOffset(); 1020 final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates; 1021 draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getScaledDragRegionWidth(); 1022 draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getScaledDragRegionHeight(); 1023 1024 // Transform the dragged item's top left coordinates 1025 // to the CellLayout's local coordinates 1026 mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix); 1027 float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]); 1028 float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]); 1029 1030 if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) { 1031 // Transform the dragged item's bottom right coordinates 1032 // to the CellLayout's local coordinates 1033 mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix); 1034 float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]); 1035 float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]); 1036 1037 if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) { 1038 float overlap = (overlapRegionRight - overlapRegionLeft) * 1039 (overlapRegionBottom - overlapRegionTop); 1040 if (overlap > 0) { 1041 return true; 1042 } 1043 } 1044 } 1045 return false; 1046 } 1047 1048 /* 1049 * 1050 * This method returns the CellLayout that is currently being dragged to. In order to drag 1051 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second 1052 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one 1053 * 1054 * Return null if no CellLayout is currently being dragged over 1055 * 1056 */ 1057 private CellLayout findMatchingPageForDragOver( 1058 DragView dragView, int originX, int originY, int offsetX, int offsetY) { 1059 // We loop through all the screens (ie CellLayouts) and see which ones overlap 1060 // with the item being dragged and then choose the one that's closest to the touch point 1061 final int screenCount = getChildCount(); 1062 CellLayout bestMatchingScreen = null; 1063 float smallestDistSoFar = Float.MAX_VALUE; 1064 1065 for (int i = 0; i < screenCount; i++) { 1066 CellLayout cl = (CellLayout)getChildAt(i); 1067 1068 final float[] touchXy = mTempTouchCoordinates; 1069 touchXy[0] = originX + offsetX; 1070 touchXy[1] = originY + offsetY; 1071 1072 // Transform the touch coordinates to the CellLayout's local coordinates 1073 // If the touch point is within the bounds of the cell layout, we can return immediately 1074 cl.getMatrix().invert(mTempInverseMatrix); 1075 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); 1076 1077 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && 1078 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { 1079 return cl; 1080 } 1081 1082 if (overlaps(cl, dragView, originX, originY, mTempInverseMatrix)) { 1083 // Get the center of the cell layout in screen coordinates 1084 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates; 1085 cellLayoutCenter[0] = cl.getWidth()/2; 1086 cellLayoutCenter[1] = cl.getHeight()/2; 1087 mapPointFromChildToSelf(cl, cellLayoutCenter); 1088 1089 touchXy[0] = originX + offsetX; 1090 touchXy[1] = originY + offsetY; 1091 1092 // Calculate the distance between the center of the CellLayout 1093 // and the touch point 1094 float dist = squaredDistance(touchXy, cellLayoutCenter); 1095 1096 if (dist < smallestDistSoFar) { 1097 smallestDistSoFar = dist; 1098 bestMatchingScreen = cl; 1099 } 1100 } 1101 } 1102 1103 if (bestMatchingScreen != mDragTargetLayout) { 1104 if (mDragTargetLayout != null) { 1105 mDragTargetLayout.onDragExit(); 1106 } 1107 mDragTargetLayout = bestMatchingScreen; 1108 // TODO: Should we be calling mDragTargetLayout.onDragEnter() here? 1109 } 1110 return bestMatchingScreen; 1111 } 1112 1113 public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, 1114 DragView dragView, Object dragInfo) { 1115 CellLayout currentLayout; 1116 int originX = x - xOffset; 1117 int originY = y - yOffset; 1118 if (mIsSmall || mIsInUnshrinkAnimation) { 1119 currentLayout = findMatchingPageForDragOver( 1120 dragView, originX, originY, xOffset, yOffset); 1121 1122 if (currentLayout == null) { 1123 return; 1124 } 1125 1126 currentLayout.setHover(true); 1127 // get originX and originY in the local coordinate system of the screen 1128 mTempOriginXY[0] = originX; 1129 mTempOriginXY[1] = originY; 1130 mapPointFromSelfToChild(currentLayout, mTempOriginXY); 1131 originX = (int)mTempOriginXY[0]; 1132 originY = (int)mTempOriginXY[1]; 1133 } else { 1134 currentLayout = getCurrentDropLayout(); 1135 } 1136 1137 final ItemInfo item = (ItemInfo)dragInfo; 1138 1139 if (dragInfo instanceof LauncherAppWidgetInfo) { 1140 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo; 1141 1142 if (widgetInfo.spanX == -1) { 1143 // Calculate the grid spans needed to fit this widget 1144 int[] spans = currentLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, null); 1145 item.spanX = spans[0]; 1146 item.spanY = spans[1]; 1147 } 1148 } 1149 1150 if (source instanceof AllAppsPagedView) { 1151 // This is a hack to fix the point used to determine which cell an icon from the all 1152 // apps screen is over 1153 if (item != null && item.spanX == 1 && currentLayout != null) { 1154 int dragRegionLeft = (dragView.getWidth() - currentLayout.getCellWidth()) / 2; 1155 1156 originX += dragRegionLeft - dragView.getDragRegionLeft(); 1157 if (dragView.getDragRegionWidth() != currentLayout.getCellWidth()) { 1158 dragView.setDragRegion(dragView.getDragRegionLeft(), dragView.getDragRegionTop(), 1159 currentLayout.getCellWidth(), dragView.getDragRegionHeight()); 1160 } 1161 } 1162 } 1163 1164 // When touch is inside the scroll area, skip dragOver actions for the current screen 1165 if (!mInScrollArea) { 1166 if (currentLayout != mDragTargetLayout) { 1167 if (mDragTargetLayout != null) { 1168 mDragTargetLayout.onDragExit(); 1169 } 1170 currentLayout.onDragEnter(dragView); 1171 mDragTargetLayout = currentLayout; 1172 } 1173 1174 // only visualize the drop locations for moving icons within the home screen on tablet 1175 // on phone, we also visualize icons dragged in from All Apps 1176 if ((!LauncherApplication.isScreenXLarge() || source == this) 1177 && mDragTargetLayout != null) { 1178 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 1179 int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX); 1180 int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY); 1181 mDragTargetLayout.visualizeDropLocation( 1182 child, localOriginX, localOriginY, item.spanX, item.spanY); 1183 } 1184 } 1185 } 1186 1187 public void onDragExit(DragSource source, int x, int y, int xOffset, 1188 int yOffset, DragView dragView, Object dragInfo) { 1189 if (mDragTargetLayout != null) { 1190 mDragTargetLayout.onDragExit(); 1191 mDragTargetLayout = null; 1192 } 1193 if (!mIsPageMoving) { 1194 hideOutlines(); 1195 } 1196 } 1197 1198 private void onDropExternal(int x, int y, Object dragInfo, 1199 CellLayout cellLayout) { 1200 onDropExternal(x, y, dragInfo, cellLayout, false); 1201 } 1202 1203 /** 1204 * Add the item specified by dragInfo to the given layout. 1205 * This is basically the equivalent of onDropExternal, except it's not initiated 1206 * by drag and drop. 1207 * @return true if successful 1208 */ 1209 public boolean addExternalItemToScreen(Object dragInfo, View layout) { 1210 CellLayout cl = (CellLayout) layout; 1211 ItemInfo info = (ItemInfo) dragInfo; 1212 1213 if (cl.findCellForSpan(mTempEstimate, info.spanX, info.spanY)) { 1214 onDropExternal(0, 0, dragInfo, cl, false); 1215 return true; 1216 } 1217 mLauncher.showOutOfSpaceMessage(); 1218 return false; 1219 } 1220 1221 // Drag from somewhere else 1222 private void onDropExternal(int x, int y, Object dragInfo, 1223 CellLayout cellLayout, boolean insertAtFirst) { 1224 int screen = indexOfChild(cellLayout); 1225 if (dragInfo instanceof PendingAddItemInfo) { 1226 PendingAddItemInfo info = (PendingAddItemInfo) dragInfo; 1227 // When dragging and dropping from customization tray, we deal with creating 1228 // widgets/shortcuts/folders in a slightly different way 1229 int[] touchXY = new int[2]; 1230 touchXY[0] = x; 1231 touchXY[1] = y; 1232 switch (info.itemType) { 1233 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1234 mLauncher.addAppWidgetFromDrop(info.componentName, screen, touchXY); 1235 break; 1236 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: 1237 mLauncher.addLiveFolderFromDrop(info.componentName, screen, touchXY); 1238 break; 1239 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1240 mLauncher.processShortcutFromDrop(info.componentName, screen, touchXY); 1241 break; 1242 default: 1243 throw new IllegalStateException("Unknown item type: " + info.itemType); 1244 } 1245 cellLayout.onDragExit(); 1246 return; 1247 } 1248 1249 // This is for other drag/drop cases, like dragging from All Apps 1250 ItemInfo info = (ItemInfo) dragInfo; 1251 1252 View view = null; 1253 1254 switch (info.itemType) { 1255 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1256 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1257 if (info.container == NO_ID && info instanceof ApplicationInfo) { 1258 // Came from all apps -- make a copy 1259 info = new ShortcutInfo((ApplicationInfo) info); 1260 } 1261 view = mLauncher.createShortcut(R.layout.application, cellLayout, 1262 (ShortcutInfo) info); 1263 break; 1264 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 1265 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, 1266 cellLayout, ((UserFolderInfo) info)); 1267 break; 1268 default: 1269 throw new IllegalStateException("Unknown item type: " + info.itemType); 1270 } 1271 1272 // If the view is null, it has already been added. 1273 if (view == null) { 1274 cellLayout.onDragExit(); 1275 } else { 1276 mTargetCell = findNearestVacantArea(x, y, 1, 1, null, cellLayout, mTargetCell); 1277 addInScreen(view, indexOfChild(cellLayout), mTargetCell[0], 1278 mTargetCell[1], info.spanX, info.spanY, insertAtFirst); 1279 cellLayout.onDropChild(view); 1280 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 1281 1282 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 1283 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 1284 lp.cellX, lp.cellY); 1285 } 1286 } 1287 1288 /** 1289 * Return the current {@link CellLayout}, correctly picking the destination 1290 * screen while a scroll is in progress. 1291 */ 1292 private CellLayout getCurrentDropLayout() { 1293 int index = mScroller.isFinished() ? mCurrentPage : mNextPage; 1294 return (CellLayout) getChildAt(index); 1295 } 1296 1297 /** 1298 * Return the current CellInfo describing our current drag; this method exists 1299 * so that Launcher can sync this object with the correct info when the activity is created/ 1300 * destroyed 1301 * 1302 */ 1303 public CellLayout.CellInfo getDragInfo() { 1304 return mDragInfo; 1305 } 1306 1307 /** 1308 * {@inheritDoc} 1309 */ 1310 public boolean acceptDrop(DragSource source, int x, int y, 1311 int xOffset, int yOffset, DragView dragView, Object dragInfo) { 1312 CellLayout layout; 1313 if (mIsSmall || mIsInUnshrinkAnimation) { 1314 layout = findMatchingPageForDragOver( 1315 dragView, x - xOffset, y - yOffset, xOffset, yOffset); 1316 if (layout == null) { 1317 // cancel the drag if we're not over a mini-screen at time of drop 1318 return false; 1319 } 1320 } else { 1321 layout = getCurrentDropLayout(); 1322 } 1323 final CellLayout.CellInfo dragCellInfo = mDragInfo; 1324 final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX; 1325 final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY; 1326 1327 final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell; 1328 1329 if (layout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) { 1330 return true; 1331 } else { 1332 mLauncher.showOutOfSpaceMessage(); 1333 return false; 1334 } 1335 } 1336 1337 /** 1338 * Calculate the nearest cell where the given object would be dropped. 1339 */ 1340 private int[] findNearestVacantArea(int pixelX, int pixelY, 1341 int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) { 1342 1343 int localPixelX = pixelX - (layout.getLeft() - mScrollX); 1344 int localPixelY = pixelY - (layout.getTop() - mScrollY); 1345 1346 // Find the best target drop location 1347 return layout.findNearestVacantArea( 1348 localPixelX, localPixelY, spanX, spanY, ignoreView, recycle); 1349 } 1350 1351 /** 1352 * Estimate the size that a child with the given dimensions will take in the current screen. 1353 */ 1354 void estimateChildSize(int minWidth, int minHeight, int[] result) { 1355 ((CellLayout)getChildAt(mCurrentPage)).estimateChildSize(minWidth, minHeight, result); 1356 } 1357 1358 void setLauncher(Launcher launcher) { 1359 mLauncher = launcher; 1360 } 1361 1362 public void setDragController(DragController dragController) { 1363 mDragController = dragController; 1364 } 1365 1366 public void onDropCompleted(View target, boolean success) { 1367 if (success) { 1368 if (target != this && mDragInfo != null) { 1369 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1370 cellLayout.removeView(mDragInfo.cell); 1371 if (mDragInfo.cell instanceof DropTarget) { 1372 mDragController.removeDropTarget((DropTarget)mDragInfo.cell); 1373 } 1374 // final Object tag = mDragInfo.cell.getTag(); 1375 } 1376 } else { 1377 if (mDragInfo != null) { 1378 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1379 cellLayout.onDropAborted(mDragInfo.cell); 1380 } 1381 } 1382 1383 mDragInfo = null; 1384 } 1385 1386 public boolean isDropEnabled() { 1387 return true; 1388 } 1389 1390 @Override 1391 protected void onRestoreInstanceState(Parcelable state) { 1392 super.onRestoreInstanceState(state); 1393 Launcher.setScreen(mCurrentPage); 1394 } 1395 1396 @Override 1397 public void scrollLeft() { 1398 if (!mIsSmall && !mIsInUnshrinkAnimation) { 1399 super.scrollLeft(); 1400 } 1401 } 1402 1403 @Override 1404 public void scrollRight() { 1405 if (!mIsSmall && !mIsInUnshrinkAnimation) { 1406 super.scrollRight(); 1407 } 1408 } 1409 1410 @Override 1411 public void onEnterScrollArea(int direction) { 1412 mInScrollArea = true; 1413 final int screen = getCurrentPage() + ((direction == DragController.SCROLL_LEFT) ? -1 : 1); 1414 if (0 <= screen && screen < getChildCount()) { 1415 ((CellLayout) getChildAt(screen)).setHover(true); 1416 } 1417 1418 if (mDragTargetLayout != null) { 1419 mDragTargetLayout.onDragExit(); 1420 mDragTargetLayout = null; 1421 } 1422 } 1423 1424 @Override 1425 public void onExitScrollArea() { 1426 mInScrollArea = false; 1427 final int childCount = getChildCount(); 1428 for (int i = 0; i < childCount; i++) { 1429 ((CellLayout) getChildAt(i)).setHover(false); 1430 } 1431 } 1432 1433 public Folder getFolderForTag(Object tag) { 1434 final int screenCount = getChildCount(); 1435 for (int screen = 0; screen < screenCount; screen++) { 1436 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1437 int count = currentScreen.getChildCount(); 1438 for (int i = 0; i < count; i++) { 1439 View child = currentScreen.getChildAt(i); 1440 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 1441 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { 1442 Folder f = (Folder) child; 1443 if (f.getInfo() == tag && f.getInfo().opened) { 1444 return f; 1445 } 1446 } 1447 } 1448 } 1449 return null; 1450 } 1451 1452 public View getViewForTag(Object tag) { 1453 int screenCount = getChildCount(); 1454 for (int screen = 0; screen < screenCount; screen++) { 1455 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1456 int count = currentScreen.getChildCount(); 1457 for (int i = 0; i < count; i++) { 1458 View child = currentScreen.getChildAt(i); 1459 if (child.getTag() == tag) { 1460 return child; 1461 } 1462 } 1463 } 1464 return null; 1465 } 1466 1467 1468 void removeItems(final ArrayList<ApplicationInfo> apps) { 1469 final int screenCount = getChildCount(); 1470 final PackageManager manager = getContext().getPackageManager(); 1471 final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); 1472 1473 final HashSet<String> packageNames = new HashSet<String>(); 1474 final int appCount = apps.size(); 1475 for (int i = 0; i < appCount; i++) { 1476 packageNames.add(apps.get(i).componentName.getPackageName()); 1477 } 1478 1479 for (int i = 0; i < screenCount; i++) { 1480 final CellLayout layout = (CellLayout) getChildAt(i); 1481 1482 // Avoid ANRs by treating each screen separately 1483 post(new Runnable() { 1484 public void run() { 1485 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 1486 childrenToRemove.clear(); 1487 1488 int childCount = layout.getChildCount(); 1489 for (int j = 0; j < childCount; j++) { 1490 final View view = layout.getChildAt(j); 1491 Object tag = view.getTag(); 1492 1493 if (tag instanceof ShortcutInfo) { 1494 final ShortcutInfo info = (ShortcutInfo) tag; 1495 final Intent intent = info.intent; 1496 final ComponentName name = intent.getComponent(); 1497 1498 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1499 for (String packageName: packageNames) { 1500 if (packageName.equals(name.getPackageName())) { 1501 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1502 childrenToRemove.add(view); 1503 } 1504 } 1505 } 1506 } else if (tag instanceof UserFolderInfo) { 1507 final UserFolderInfo info = (UserFolderInfo) tag; 1508 final ArrayList<ShortcutInfo> contents = info.contents; 1509 final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1); 1510 final int contentsCount = contents.size(); 1511 boolean removedFromFolder = false; 1512 1513 for (int k = 0; k < contentsCount; k++) { 1514 final ShortcutInfo appInfo = contents.get(k); 1515 final Intent intent = appInfo.intent; 1516 final ComponentName name = intent.getComponent(); 1517 1518 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1519 for (String packageName: packageNames) { 1520 if (packageName.equals(name.getPackageName())) { 1521 toRemove.add(appInfo); 1522 LauncherModel.deleteItemFromDatabase(mLauncher, appInfo); 1523 removedFromFolder = true; 1524 } 1525 } 1526 } 1527 } 1528 1529 contents.removeAll(toRemove); 1530 if (removedFromFolder) { 1531 final Folder folder = getOpenFolder(); 1532 if (folder != null) 1533 folder.notifyDataSetChanged(); 1534 } 1535 } else if (tag instanceof LiveFolderInfo) { 1536 final LiveFolderInfo info = (LiveFolderInfo) tag; 1537 final Uri uri = info.uri; 1538 final ProviderInfo providerInfo = manager.resolveContentProvider( 1539 uri.getAuthority(), 0); 1540 1541 if (providerInfo != null) { 1542 for (String packageName: packageNames) { 1543 if (packageName.equals(providerInfo.packageName)) { 1544 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1545 childrenToRemove.add(view); 1546 } 1547 } 1548 } 1549 } else if (tag instanceof LauncherAppWidgetInfo) { 1550 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; 1551 final AppWidgetProviderInfo provider = 1552 widgets.getAppWidgetInfo(info.appWidgetId); 1553 if (provider != null) { 1554 for (String packageName: packageNames) { 1555 if (packageName.equals(provider.provider.getPackageName())) { 1556 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1557 childrenToRemove.add(view); 1558 } 1559 } 1560 } 1561 } 1562 } 1563 1564 childCount = childrenToRemove.size(); 1565 for (int j = 0; j < childCount; j++) { 1566 View child = childrenToRemove.get(j); 1567 layout.removeViewInLayout(child); 1568 if (child instanceof DropTarget) { 1569 mDragController.removeDropTarget((DropTarget)child); 1570 } 1571 } 1572 1573 if (childCount > 0) { 1574 layout.requestLayout(); 1575 layout.invalidate(); 1576 } 1577 } 1578 }); 1579 } 1580 } 1581 1582 void updateShortcuts(ArrayList<ApplicationInfo> apps) { 1583 final int screenCount = getChildCount(); 1584 for (int i = 0; i < screenCount; i++) { 1585 final CellLayout layout = (CellLayout) getChildAt(i); 1586 int childCount = layout.getChildCount(); 1587 for (int j = 0; j < childCount; j++) { 1588 final View view = layout.getChildAt(j); 1589 Object tag = view.getTag(); 1590 if (tag instanceof ShortcutInfo) { 1591 ShortcutInfo info = (ShortcutInfo)tag; 1592 // We need to check for ACTION_MAIN otherwise getComponent() might 1593 // return null for some shortcuts (for instance, for shortcuts to 1594 // web pages.) 1595 final Intent intent = info.intent; 1596 final ComponentName name = intent.getComponent(); 1597 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 1598 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1599 final int appCount = apps.size(); 1600 for (int k = 0; k < appCount; k++) { 1601 ApplicationInfo app = apps.get(k); 1602 if (app.componentName.equals(name)) { 1603 info.setIcon(mIconCache.getIcon(info.intent)); 1604 ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null, 1605 new FastBitmapDrawable(info.getIcon(mIconCache)), 1606 null, null); 1607 } 1608 } 1609 } 1610 } 1611 } 1612 } 1613 } 1614 1615 void moveToDefaultScreen(boolean animate) { 1616 if (mIsSmall || mIsInUnshrinkAnimation) { 1617 mLauncher.showWorkspace(animate, (CellLayout)getChildAt(mDefaultPage)); 1618 } else if (animate) { 1619 snapToPage(mDefaultPage); 1620 } else { 1621 setCurrentPage(mDefaultPage); 1622 } 1623 getChildAt(mDefaultPage).requestFocus(); 1624 } 1625 1626 void setIndicators(Drawable previous, Drawable next) { 1627 mPreviousIndicator = previous; 1628 mNextIndicator = next; 1629 previous.setLevel(mCurrentPage); 1630 next.setLevel(mCurrentPage); 1631 } 1632 1633 @Override 1634 public void syncPages() { 1635 } 1636 1637 @Override 1638 public void syncPageItems(int page) { 1639 } 1640 1641} 1642