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