Workspace.java revision b0b2e6f588367cf40a4270cca81af7d78f8e382e
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 } else if (shrinkPosition == ShrinkPosition.SHRINK_TO_TOP) { 723 newY = screenHeight / 10; 724 } 725 726 // We animate all the screens to the centered position in workspace 727 // At the same time, the screens become greyed/dimmed 728 729 // newX is initialized to the left-most position of the centered screens 730 float newX = mScroller.getFinalX() + screenWidth / 2 - totalWidth / 2; 731 732 // We are going to scale about the center of the view, so we need to adjust the positions 733 // of the views accordingly 734 newX -= (pageWidth - scaledPageWidth) / 2.0f; 735 newY -= (pageHeight - scaledPageHeight) / 2.0f; 736 for (int i = 0; i < screenCount; i++) { 737 CellLayout cl = (CellLayout) getChildAt(i); 738 739 float rotation = (-i + 2) * WORKSPACE_ROTATION; 740 float rotationScaleX = (float) (1.0f / Math.cos(Math.PI * rotation / 180.0f)); 741 float rotationScaleY = getYScaleForScreen(i); 742 743 if (animated) { 744 final int duration = res.getInteger(R.integer.config_workspaceShrinkTime); 745 new ObjectAnimator<Float>(duration, cl, 746 new PropertyValuesHolder<Float>("x", newX), 747 new PropertyValuesHolder<Float>("y", newY), 748 new PropertyValuesHolder<Float>("scaleX", 749 SHRINK_FACTOR * rotationScaleX * extraShrinkFactor), 750 new PropertyValuesHolder<Float>("scaleY", 751 SHRINK_FACTOR * rotationScaleY * extraShrinkFactor), 752 new PropertyValuesHolder<Float>("backgroundAlpha", finalAlpha), 753 new PropertyValuesHolder<Float>("alpha", finalAlpha), 754 new PropertyValuesHolder<Float>("rotationY", rotation)).start(); 755 } else { 756 cl.setX((int)newX); 757 cl.setY((int)newY); 758 cl.setScaleX(SHRINK_FACTOR * rotationScaleX); 759 cl.setScaleY(SHRINK_FACTOR * rotationScaleY); 760 cl.setBackgroundAlpha(1.0f); 761 cl.setAlpha(finalAlpha); 762 cl.setRotationY(rotation); 763 } 764 // increment newX for the next screen 765 newX += scaledPageWidth + extraScaledSpacing; 766 } 767 setChildrenDrawnWithCacheEnabled(true); 768 } 769 770 771 private void updateWhichPagesAcceptDrops(ShrinkPosition state) { 772 updateWhichPagesAcceptDropsHelper(state, false, 1, 1); 773 } 774 775 776 private void updateWhichPagesAcceptDropsDuringDrag(ShrinkPosition state, int spanX, int spanY) { 777 updateWhichPagesAcceptDropsHelper(state, true, spanX, spanY); 778 } 779 780 private void updateWhichPagesAcceptDropsHelper( 781 ShrinkPosition state, boolean isDragHappening, int spanX, int spanY) { 782 final int screenCount = getChildCount(); 783 for (int i = 0; i < screenCount; i++) { 784 CellLayout cl = (CellLayout) getChildAt(i); 785 786 switch (state) { 787 case SHRINK_TO_TOP: 788 if (!isDragHappening) { 789 boolean showDropHighlight = i == mCurrentPage; 790 cl.setAcceptsDrops(showDropHighlight); 791 break; 792 } 793 // otherwise, fall through below and mark non-full screens as accepting drops 794 case SHRINK_TO_BOTTOM_HIDDEN: 795 case SHRINK_TO_BOTTOM_VISIBLE: 796 if (!isDragHappening) { 797 // even if a drag isn't happening, we don't want to show a screen as 798 // accepting drops if it doesn't have at least one free cell 799 spanX = 1; 800 spanY = 1; 801 } 802 // the page accepts drops if we can find at least one empty spot 803 cl.setAcceptsDrops(cl.findCellForSpan(null, spanX, spanY)); 804 break; 805 default: 806 throw new RuntimeException( 807 "updateWhichPagesAcceptDropsHelper passed an unhandled ShrinkPosition"); 808 } 809 } 810 } 811 812 /* 813 * 814 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithItemMinSize) whenever we 815 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace 816 * 817 * These methods mark the appropriate pages as accepting drops (which alters their visual 818 * appearance) and, if the pages are hidden, makes them visible. 819 * 820 */ 821 public void onDragStartedWithItemSpans(int spanX, int spanY) { 822 updateWhichPagesAcceptDropsDuringDrag(mShrunkenState, spanX, spanY); 823 if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN) { 824 shrink(ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE, true); 825 } 826 } 827 828 public void onDragStartedWithItemMinSize(int minWidth, int minHeight) { 829 int[] spanXY = CellLayout.rectToCell(getResources(), minWidth, minHeight, null); 830 onDragStartedWithItemSpans(spanXY[0], spanXY[1]); 831 } 832 833 // we call this method whenever a drag and drop in Launcher finishes, even if Workspace was 834 // never dragged over 835 public void onDragStopped() { 836 updateWhichPagesAcceptDrops(mShrunkenState); 837 if (mShrunkenState == ShrinkPosition.SHRINK_TO_BOTTOM_VISIBLE) { 838 shrink(ShrinkPosition.SHRINK_TO_BOTTOM_HIDDEN, true); 839 } 840 } 841 842 // We call this when we trigger an unshrink by clicking on the CellLayout cl 843 public void unshrink(CellLayout clThatWasClicked) { 844 int newCurrentPage = mCurrentPage; 845 final int screenCount = getChildCount(); 846 for (int i = 0; i < screenCount; i++) { 847 if (getChildAt(i) == clThatWasClicked) { 848 newCurrentPage = i; 849 } 850 } 851 unshrink(newCurrentPage); 852 } 853 854 @Override 855 protected boolean handlePagingClicks() { 856 return true; 857 } 858 859 private void unshrink(int newCurrentPage) { 860 if (mIsSmall) { 861 int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage); 862 int delta = newX - mScrollX; 863 864 final int screenCount = getChildCount(); 865 for (int i = 0; i < screenCount; i++) { 866 CellLayout cl = (CellLayout) getChildAt(i); 867 cl.setX(cl.getX() + delta); 868 } 869 setCurrentPage(newCurrentPage); 870 unshrink(); 871 } 872 } 873 874 void unshrink() { 875 unshrink(true); 876 } 877 878 void unshrink(boolean animated) { 879 if (mIsSmall) { 880 mIsSmall = false; 881 AnimatorSet s = new AnimatorSet(); 882 final int screenCount = getChildCount(); 883 884 final int duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime); 885 for (int i = 0; i < screenCount; i++) { 886 final CellLayout cl = (CellLayout)getChildAt(i); 887 float finalAlphaValue = (i == mCurrentPage) ? 1.0f : 0.0f; 888 float rotation = 0.0f; 889 890 if (i < mCurrentPage) { 891 rotation = WORKSPACE_ROTATION; 892 } else if (i > mCurrentPage) { 893 rotation = -WORKSPACE_ROTATION; 894 } 895 896 if (animated) { 897 s.playTogether( 898 new ObjectAnimator<Float>(duration, cl, "translationX", 0.0f), 899 new ObjectAnimator<Float>(duration, cl, "translationY", 0.0f), 900 new ObjectAnimator<Float>(duration, cl, "scaleX", 1.0f), 901 new ObjectAnimator<Float>(duration, cl, "scaleY", 1.0f), 902 new ObjectAnimator<Float>(duration, cl, "backgroundAlpha", 0.0f), 903 new ObjectAnimator<Float>(duration, cl, "alpha", finalAlphaValue), 904 new ObjectAnimator<Float>(duration, cl, "rotationY", rotation)); 905 } else { 906 cl.setTranslationX(0.0f); 907 cl.setTranslationY(0.0f); 908 cl.setScaleX(1.0f); 909 cl.setScaleY(1.0f); 910 cl.setBackgroundAlpha(0.0f); 911 cl.setAlpha(finalAlphaValue); 912 cl.setRotationY(rotation); 913 } 914 } 915 if (animated) { 916 // If we call this when we're not animated, onAnimationEnd is never called on 917 // the listener; make sure we only use the listener when we're actually animating 918 s.addListener(mUnshrinkAnimationListener); 919 s.start(); 920 } 921 } 922 } 923 924 void startDrag(CellLayout.CellInfo cellInfo) { 925 View child = cellInfo.cell; 926 927 // Make sure the drag was started by a long press as opposed to a long click. 928 if (!child.isInTouchMode()) { 929 return; 930 } 931 932 mDragInfo = cellInfo; 933 mDragInfo.screen = mCurrentPage; 934 935 CellLayout current = ((CellLayout) getChildAt(mCurrentPage)); 936 937 current.onDragChild(child); 938 mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE); 939 current.onDragEnter(child); 940 invalidate(); 941 } 942 943 void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY, 944 boolean insertAtFirst, int intersectX, int intersectY) { 945 final CellLayout cellLayout = (CellLayout) getChildAt(screen); 946 View view = mLauncher.createShortcut(R.layout.application, cellLayout, (ShortcutInfo) info); 947 948 final int[] cellXY = new int[2]; 949 cellLayout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY); 950 addInScreen(view, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst); 951 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 952 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 953 cellXY[0], cellXY[1]); 954 } 955 956 957 public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, 958 DragView dragView, Object dragInfo) { 959 CellLayout cellLayout; 960 int originX = x - xOffset; 961 int originY = y - yOffset; 962 if (mIsSmall || mIsInUnshrinkAnimation) { 963 cellLayout = findMatchingPageForDragOver(dragView, originX, originY, xOffset, yOffset); 964 if (cellLayout == null) { 965 // cancel the drag if we're not over a mini-screen at time of drop 966 // TODO: maybe add a nice fade here? 967 return; 968 } 969 // get originX and originY in the local coordinate system of the screen 970 mTempOriginXY[0] = originX; 971 mTempOriginXY[1] = originY; 972 mapPointFromSelfToChild(cellLayout, mTempOriginXY); 973 originX = (int)mTempOriginXY[0]; 974 originY = (int)mTempOriginXY[1]; 975 } else { 976 cellLayout = getCurrentDropLayout(); 977 } 978 979 if (source != this) { 980 onDropExternal(originX, originY, dragInfo, cellLayout); 981 } else { 982 // Move internally 983 if (mDragInfo != null) { 984 final View cell = mDragInfo.cell; 985 986 mTargetCell = findNearestVacantArea(originX, originY, 987 mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, 988 mTargetCell); 989 990 int screen = indexOfChild(cellLayout); 991 if (screen != mDragInfo.screen) { 992 final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen); 993 originalCellLayout.removeView(cell); 994 addInScreen(cell, screen, mTargetCell[0], mTargetCell[1], 995 mDragInfo.spanX, mDragInfo.spanY); 996 } 997 cellLayout.onDropChild(cell); 998 999 // update the item's position after drop 1000 final ItemInfo info = (ItemInfo) cell.getTag(); 1001 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams(); 1002 cellLayout.onMove(cell, mTargetCell[0], mTargetCell[1]); 1003 lp.cellX = mTargetCell[0]; 1004 lp.cellY = mTargetCell[1]; 1005 cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen, 1006 mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); 1007 1008 LauncherModel.moveItemInDatabase(mLauncher, info, 1009 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 1010 lp.cellX, lp.cellY); 1011 } 1012 } 1013 } 1014 1015 public void onDragEnter(DragSource source, int x, int y, int xOffset, 1016 int yOffset, DragView dragView, Object dragInfo) { 1017 getCurrentDropLayout().onDragEnter(dragView); 1018 showOutlines(); 1019 } 1020 1021 public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset, 1022 DragView dragView, Object dragInfo) { 1023 1024 if (mIsSmall || mIsInUnshrinkAnimation) { 1025 // If we're shrunken, don't let anyone drag on folders/etc that are on the mini-screens 1026 return null; 1027 } 1028 // We may need to delegate the drag to a child view. If a 1x1 item 1029 // would land in a cell occupied by a DragTarget (e.g. a Folder), 1030 // then drag events should be handled by that child. 1031 1032 ItemInfo item = (ItemInfo)dragInfo; 1033 CellLayout currentLayout = getCurrentDropLayout(); 1034 1035 int dragPointX, dragPointY; 1036 if (item.spanX == 1 && item.spanY == 1) { 1037 // For a 1x1, calculate the drop cell exactly as in onDragOver 1038 dragPointX = x - xOffset; 1039 dragPointY = y - yOffset; 1040 } else { 1041 // Otherwise, use the exact drag coordinates 1042 dragPointX = x; 1043 dragPointY = y; 1044 } 1045 dragPointX += mScrollX - currentLayout.getLeft(); 1046 dragPointY += mScrollY - currentLayout.getTop(); 1047 1048 // If we are dragging over a cell that contains a DropTarget that will 1049 // accept the drop, delegate to that DropTarget. 1050 final int[] cellXY = mTempCell; 1051 currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY); 1052 View child = currentLayout.getChildAt(cellXY[0], cellXY[1]); 1053 if (child instanceof DropTarget) { 1054 DropTarget target = (DropTarget)child; 1055 if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) { 1056 return target; 1057 } 1058 } 1059 return null; 1060 } 1061 1062 /* 1063 * 1064 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 1065 * coordinate space. The argument xy is modified with the return result. 1066 * 1067 */ 1068 void mapPointFromSelfToChild(View v, float[] xy) { 1069 mapPointFromSelfToChild(v, xy, null); 1070 } 1071 1072 /* 1073 * 1074 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's 1075 * coordinate space. The argument xy is modified with the return result. 1076 * 1077 * if cachedInverseMatrix is not null, this method will just use that matrix instead of 1078 * computing it itself; we use this to avoid redudant matrix inversions in 1079 * findMatchingPageForDragOver 1080 * 1081 */ 1082 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { 1083 if (cachedInverseMatrix == null) { 1084 v.getMatrix().invert(mTempInverseMatrix); 1085 cachedInverseMatrix = mTempInverseMatrix; 1086 } 1087 xy[0] = xy[0] + mScrollX - v.getLeft(); 1088 xy[1] = xy[1] + mScrollY - v.getTop(); 1089 cachedInverseMatrix.mapPoints(xy); 1090 } 1091 1092 /* 1093 * 1094 * Convert the 2D coordinate xy from this CellLayout's coordinate space to 1095 * the parent View's coordinate space. The argument xy is modified with the return result. 1096 * 1097 */ 1098 void mapPointFromChildToSelf(View v, float[] xy) { 1099 v.getMatrix().mapPoints(xy); 1100 xy[0] -= (mScrollX - v.getLeft()); 1101 xy[1] -= (mScrollY - v.getTop()); 1102 } 1103 1104 static private float squaredDistance(float[] point1, float[] point2) { 1105 float distanceX = point1[0] - point2[0]; 1106 float distanceY = point2[1] - point2[1]; 1107 return distanceX * distanceX + distanceY * distanceY; 1108 } 1109 1110 /* 1111 * 1112 * Returns true if the passed CellLayout cl overlaps with dragView 1113 * 1114 */ 1115 boolean overlaps(CellLayout cl, DragView dragView, 1116 int dragViewX, int dragViewY, Matrix cachedInverseMatrix) { 1117 // Transform the coordinates of the item being dragged to the CellLayout's coordinates 1118 final float[] draggedItemTopLeft = mTempDragCoordinates; 1119 draggedItemTopLeft[0] = dragViewX + dragView.getScaledDragRegionXOffset(); 1120 draggedItemTopLeft[1] = dragViewY + dragView.getScaledDragRegionYOffset(); 1121 final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates; 1122 draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getScaledDragRegionWidth(); 1123 draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getScaledDragRegionHeight(); 1124 1125 // Transform the dragged item's top left coordinates 1126 // to the CellLayout's local coordinates 1127 mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix); 1128 float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]); 1129 float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]); 1130 1131 if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) { 1132 // Transform the dragged item's bottom right coordinates 1133 // to the CellLayout's local coordinates 1134 mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix); 1135 float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]); 1136 float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]); 1137 1138 if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) { 1139 float overlap = (overlapRegionRight - overlapRegionLeft) * 1140 (overlapRegionBottom - overlapRegionTop); 1141 if (overlap > 0) { 1142 return true; 1143 } 1144 } 1145 } 1146 return false; 1147 } 1148 1149 /* 1150 * 1151 * This method returns the CellLayout that is currently being dragged to. In order to drag 1152 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second 1153 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one 1154 * 1155 * Return null if no CellLayout is currently being dragged over 1156 * 1157 */ 1158 private CellLayout findMatchingPageForDragOver( 1159 DragView dragView, int originX, int originY, int offsetX, int offsetY) { 1160 // We loop through all the screens (ie CellLayouts) and see which ones overlap 1161 // with the item being dragged and then choose the one that's closest to the touch point 1162 final int screenCount = getChildCount(); 1163 CellLayout bestMatchingScreen = null; 1164 float smallestDistSoFar = Float.MAX_VALUE; 1165 1166 for (int i = 0; i < screenCount; i++) { 1167 CellLayout cl = (CellLayout)getChildAt(i); 1168 1169 final float[] touchXy = mTempTouchCoordinates; 1170 touchXy[0] = originX + offsetX; 1171 touchXy[1] = originY + offsetY; 1172 1173 // Transform the touch coordinates to the CellLayout's local coordinates 1174 // If the touch point is within the bounds of the cell layout, we can return immediately 1175 cl.getMatrix().invert(mTempInverseMatrix); 1176 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); 1177 1178 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && 1179 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { 1180 return cl; 1181 } 1182 1183 if (overlaps(cl, dragView, originX, originY, mTempInverseMatrix)) { 1184 // Get the center of the cell layout in screen coordinates 1185 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates; 1186 cellLayoutCenter[0] = cl.getWidth()/2; 1187 cellLayoutCenter[1] = cl.getHeight()/2; 1188 mapPointFromChildToSelf(cl, cellLayoutCenter); 1189 1190 touchXy[0] = originX + offsetX; 1191 touchXy[1] = originY + offsetY; 1192 1193 // Calculate the distance between the center of the CellLayout 1194 // and the touch point 1195 float dist = squaredDistance(touchXy, cellLayoutCenter); 1196 1197 if (dist < smallestDistSoFar) { 1198 smallestDistSoFar = dist; 1199 bestMatchingScreen = cl; 1200 } 1201 } 1202 } 1203 1204 if (bestMatchingScreen != mDragTargetLayout) { 1205 if (mDragTargetLayout != null) { 1206 mDragTargetLayout.onDragExit(); 1207 } 1208 mDragTargetLayout = bestMatchingScreen; 1209 // TODO: Should we be calling mDragTargetLayout.onDragEnter() here? 1210 } 1211 return bestMatchingScreen; 1212 } 1213 1214 public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, 1215 DragView dragView, Object dragInfo) { 1216 CellLayout currentLayout; 1217 int originX = x - xOffset; 1218 int originY = y - yOffset; 1219 if (mIsSmall || mIsInUnshrinkAnimation) { 1220 currentLayout = findMatchingPageForDragOver( 1221 dragView, originX, originY, xOffset, yOffset); 1222 1223 if (currentLayout == null) { 1224 return; 1225 } 1226 1227 currentLayout.setHover(true); 1228 // get originX and originY in the local coordinate system of the screen 1229 mTempOriginXY[0] = originX; 1230 mTempOriginXY[1] = originY; 1231 mapPointFromSelfToChild(currentLayout, mTempOriginXY); 1232 originX = (int)mTempOriginXY[0]; 1233 originY = (int)mTempOriginXY[1]; 1234 } else { 1235 currentLayout = getCurrentDropLayout(); 1236 } 1237 1238 final ItemInfo item = (ItemInfo)dragInfo; 1239 1240 if (dragInfo instanceof LauncherAppWidgetInfo) { 1241 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo)dragInfo; 1242 1243 if (widgetInfo.spanX == -1) { 1244 // Calculate the grid spans needed to fit this widget 1245 int[] spans = currentLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, null); 1246 item.spanX = spans[0]; 1247 item.spanY = spans[1]; 1248 } 1249 } 1250 1251 if (source instanceof AllAppsPagedView) { 1252 // This is a hack to fix the point used to determine which cell an icon from the all 1253 // apps screen is over 1254 if (item != null && item.spanX == 1 && currentLayout != null) { 1255 int dragRegionLeft = (dragView.getWidth() - currentLayout.getCellWidth()) / 2; 1256 1257 originX += dragRegionLeft - dragView.getDragRegionLeft(); 1258 if (dragView.getDragRegionWidth() != currentLayout.getCellWidth()) { 1259 dragView.setDragRegion(dragView.getDragRegionLeft(), dragView.getDragRegionTop(), 1260 currentLayout.getCellWidth(), dragView.getDragRegionHeight()); 1261 } 1262 } 1263 } 1264 1265 // When touch is inside the scroll area, skip dragOver actions for the current screen 1266 if (!mInScrollArea) { 1267 if (currentLayout != mDragTargetLayout) { 1268 if (mDragTargetLayout != null) { 1269 mDragTargetLayout.onDragExit(); 1270 } 1271 currentLayout.onDragEnter(dragView); 1272 mDragTargetLayout = currentLayout; 1273 } 1274 1275 // only visualize the drop locations for moving icons within the home screen on tablet 1276 // on phone, we also visualize icons dragged in from All Apps 1277 if ((!LauncherApplication.isScreenXLarge() || source == this) 1278 && mDragTargetLayout != null) { 1279 final View child = (mDragInfo == null) ? null : mDragInfo.cell; 1280 int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX); 1281 int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY); 1282 mDragTargetLayout.visualizeDropLocation( 1283 child, localOriginX, localOriginY, item.spanX, item.spanY); 1284 } 1285 } 1286 } 1287 1288 public void onDragExit(DragSource source, int x, int y, int xOffset, 1289 int yOffset, DragView dragView, Object dragInfo) { 1290 if (mDragTargetLayout != null) { 1291 mDragTargetLayout.onDragExit(); 1292 mDragTargetLayout = null; 1293 } 1294 if (!mIsPageMoving) { 1295 hideOutlines(); 1296 } 1297 } 1298 1299 private void onDropExternal(int x, int y, Object dragInfo, 1300 CellLayout cellLayout) { 1301 onDropExternal(x, y, dragInfo, cellLayout, false); 1302 } 1303 1304 /** 1305 * Add the item specified by dragInfo to the given layout. 1306 * This is basically the equivalent of onDropExternal, except it's not initiated 1307 * by drag and drop. 1308 * @return true if successful 1309 */ 1310 public boolean addExternalItemToScreen(Object dragInfo, View layout) { 1311 CellLayout cl = (CellLayout) layout; 1312 ItemInfo info = (ItemInfo) dragInfo; 1313 1314 if (cl.findCellForSpan(mTempEstimate, info.spanX, info.spanY)) { 1315 onDropExternal(0, 0, dragInfo, cl, false); 1316 return true; 1317 } 1318 mLauncher.showOutOfSpaceMessage(); 1319 return false; 1320 } 1321 1322 // Drag from somewhere else 1323 private void onDropExternal(int x, int y, Object dragInfo, 1324 CellLayout cellLayout, boolean insertAtFirst) { 1325 int screen = indexOfChild(cellLayout); 1326 if (dragInfo instanceof PendingAddItemInfo) { 1327 PendingAddItemInfo info = (PendingAddItemInfo) dragInfo; 1328 // When dragging and dropping from customization tray, we deal with creating 1329 // widgets/shortcuts/folders in a slightly different way 1330 int[] touchXY = new int[2]; 1331 touchXY[0] = x; 1332 touchXY[1] = y; 1333 switch (info.itemType) { 1334 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 1335 mLauncher.addAppWidgetFromDrop(info.componentName, screen, touchXY); 1336 break; 1337 case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER: 1338 mLauncher.addLiveFolderFromDrop(info.componentName, screen, touchXY); 1339 break; 1340 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1341 mLauncher.processShortcutFromDrop(info.componentName, screen, touchXY); 1342 break; 1343 default: 1344 throw new IllegalStateException("Unknown item type: " + info.itemType); 1345 } 1346 cellLayout.onDragExit(); 1347 return; 1348 } 1349 1350 // This is for other drag/drop cases, like dragging from All Apps 1351 ItemInfo info = (ItemInfo) dragInfo; 1352 1353 View view = null; 1354 1355 switch (info.itemType) { 1356 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 1357 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 1358 if (info.container == NO_ID && info instanceof ApplicationInfo) { 1359 // Came from all apps -- make a copy 1360 info = new ShortcutInfo((ApplicationInfo) info); 1361 } 1362 view = mLauncher.createShortcut(R.layout.application, cellLayout, 1363 (ShortcutInfo) info); 1364 break; 1365 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER: 1366 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, 1367 cellLayout, ((UserFolderInfo) info)); 1368 break; 1369 default: 1370 throw new IllegalStateException("Unknown item type: " + info.itemType); 1371 } 1372 1373 // If the view is null, it has already been added. 1374 if (view == null) { 1375 cellLayout.onDragExit(); 1376 } else { 1377 mTargetCell = findNearestVacantArea(x, y, 1, 1, null, cellLayout, mTargetCell); 1378 addInScreen(view, indexOfChild(cellLayout), mTargetCell[0], 1379 mTargetCell[1], info.spanX, info.spanY, insertAtFirst); 1380 cellLayout.onDropChild(view); 1381 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 1382 1383 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, 1384 LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, 1385 lp.cellX, lp.cellY); 1386 } 1387 } 1388 1389 /** 1390 * Return the current {@link CellLayout}, correctly picking the destination 1391 * screen while a scroll is in progress. 1392 */ 1393 private CellLayout getCurrentDropLayout() { 1394 int index = mScroller.isFinished() ? mCurrentPage : mNextPage; 1395 return (CellLayout) getChildAt(index); 1396 } 1397 1398 /** 1399 * Return the current CellInfo describing our current drag; this method exists 1400 * so that Launcher can sync this object with the correct info when the activity is created/ 1401 * destroyed 1402 * 1403 */ 1404 public CellLayout.CellInfo getDragInfo() { 1405 return mDragInfo; 1406 } 1407 1408 /** 1409 * {@inheritDoc} 1410 */ 1411 public boolean acceptDrop(DragSource source, int x, int y, 1412 int xOffset, int yOffset, DragView dragView, Object dragInfo) { 1413 CellLayout layout; 1414 if (mIsSmall || mIsInUnshrinkAnimation) { 1415 layout = findMatchingPageForDragOver( 1416 dragView, x - xOffset, y - yOffset, xOffset, yOffset); 1417 if (layout == null) { 1418 // cancel the drag if we're not over a mini-screen at time of drop 1419 return false; 1420 } 1421 } else { 1422 layout = getCurrentDropLayout(); 1423 } 1424 final CellLayout.CellInfo dragCellInfo = mDragInfo; 1425 final int spanX = dragCellInfo == null ? 1 : dragCellInfo.spanX; 1426 final int spanY = dragCellInfo == null ? 1 : dragCellInfo.spanY; 1427 1428 final View ignoreView = dragCellInfo == null ? null : dragCellInfo.cell; 1429 1430 if (layout.findCellForSpanIgnoring(null, spanX, spanY, ignoreView)) { 1431 return true; 1432 } else { 1433 mLauncher.showOutOfSpaceMessage(); 1434 return false; 1435 } 1436 } 1437 1438 /** 1439 * Calculate the nearest cell where the given object would be dropped. 1440 */ 1441 private int[] findNearestVacantArea(int pixelX, int pixelY, 1442 int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) { 1443 1444 int localPixelX = pixelX - (layout.getLeft() - mScrollX); 1445 int localPixelY = pixelY - (layout.getTop() - mScrollY); 1446 1447 // Find the best target drop location 1448 return layout.findNearestVacantArea( 1449 localPixelX, localPixelY, spanX, spanY, ignoreView, recycle); 1450 } 1451 1452 /** 1453 * Estimate the size that a child with the given dimensions will take in the current screen. 1454 */ 1455 void estimateChildSize(int minWidth, int minHeight, int[] result) { 1456 ((CellLayout)getChildAt(mCurrentPage)).estimateChildSize(minWidth, minHeight, result); 1457 } 1458 1459 void setLauncher(Launcher launcher) { 1460 mLauncher = launcher; 1461 } 1462 1463 public void setDragController(DragController dragController) { 1464 mDragController = dragController; 1465 } 1466 1467 public void onDropCompleted(View target, boolean success) { 1468 if (success) { 1469 if (target != this && mDragInfo != null) { 1470 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1471 cellLayout.removeView(mDragInfo.cell); 1472 if (mDragInfo.cell instanceof DropTarget) { 1473 mDragController.removeDropTarget((DropTarget)mDragInfo.cell); 1474 } 1475 // final Object tag = mDragInfo.cell.getTag(); 1476 } 1477 } else { 1478 if (mDragInfo != null) { 1479 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen); 1480 cellLayout.onDropAborted(mDragInfo.cell); 1481 } 1482 } 1483 1484 mDragInfo = null; 1485 } 1486 1487 public boolean isDropEnabled() { 1488 return true; 1489 } 1490 1491 @Override 1492 protected void onRestoreInstanceState(Parcelable state) { 1493 super.onRestoreInstanceState(state); 1494 Launcher.setScreen(mCurrentPage); 1495 } 1496 1497 @Override 1498 public void scrollLeft() { 1499 if (!mIsSmall && !mIsInUnshrinkAnimation) { 1500 super.scrollLeft(); 1501 } 1502 } 1503 1504 @Override 1505 public void scrollRight() { 1506 if (!mIsSmall && !mIsInUnshrinkAnimation) { 1507 super.scrollRight(); 1508 } 1509 } 1510 1511 @Override 1512 public void onEnterScrollArea(int direction) { 1513 mInScrollArea = true; 1514 final int screen = getCurrentPage() + ((direction == DragController.SCROLL_LEFT) ? -1 : 1); 1515 if (0 <= screen && screen < getChildCount()) { 1516 ((CellLayout) getChildAt(screen)).setHover(true); 1517 } 1518 1519 if (mDragTargetLayout != null) { 1520 mDragTargetLayout.onDragExit(); 1521 mDragTargetLayout = null; 1522 } 1523 } 1524 1525 @Override 1526 public void onExitScrollArea() { 1527 mInScrollArea = false; 1528 final int childCount = getChildCount(); 1529 for (int i = 0; i < childCount; i++) { 1530 ((CellLayout) getChildAt(i)).setHover(false); 1531 } 1532 } 1533 1534 public Folder getFolderForTag(Object tag) { 1535 final int screenCount = getChildCount(); 1536 for (int screen = 0; screen < screenCount; screen++) { 1537 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1538 int count = currentScreen.getChildCount(); 1539 for (int i = 0; i < count; i++) { 1540 View child = currentScreen.getChildAt(i); 1541 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 1542 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) { 1543 Folder f = (Folder) child; 1544 if (f.getInfo() == tag && f.getInfo().opened) { 1545 return f; 1546 } 1547 } 1548 } 1549 } 1550 return null; 1551 } 1552 1553 public View getViewForTag(Object tag) { 1554 int screenCount = getChildCount(); 1555 for (int screen = 0; screen < screenCount; screen++) { 1556 CellLayout currentScreen = ((CellLayout) getChildAt(screen)); 1557 int count = currentScreen.getChildCount(); 1558 for (int i = 0; i < count; i++) { 1559 View child = currentScreen.getChildAt(i); 1560 if (child.getTag() == tag) { 1561 return child; 1562 } 1563 } 1564 } 1565 return null; 1566 } 1567 1568 1569 void removeItems(final ArrayList<ApplicationInfo> apps) { 1570 final int screenCount = getChildCount(); 1571 final PackageManager manager = getContext().getPackageManager(); 1572 final AppWidgetManager widgets = AppWidgetManager.getInstance(getContext()); 1573 1574 final HashSet<String> packageNames = new HashSet<String>(); 1575 final int appCount = apps.size(); 1576 for (int i = 0; i < appCount; i++) { 1577 packageNames.add(apps.get(i).componentName.getPackageName()); 1578 } 1579 1580 for (int i = 0; i < screenCount; i++) { 1581 final CellLayout layout = (CellLayout) getChildAt(i); 1582 1583 // Avoid ANRs by treating each screen separately 1584 post(new Runnable() { 1585 public void run() { 1586 final ArrayList<View> childrenToRemove = new ArrayList<View>(); 1587 childrenToRemove.clear(); 1588 1589 int childCount = layout.getChildCount(); 1590 for (int j = 0; j < childCount; j++) { 1591 final View view = layout.getChildAt(j); 1592 Object tag = view.getTag(); 1593 1594 if (tag instanceof ShortcutInfo) { 1595 final ShortcutInfo info = (ShortcutInfo) tag; 1596 final Intent intent = info.intent; 1597 final ComponentName name = intent.getComponent(); 1598 1599 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1600 for (String packageName: packageNames) { 1601 if (packageName.equals(name.getPackageName())) { 1602 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1603 childrenToRemove.add(view); 1604 } 1605 } 1606 } 1607 } else if (tag instanceof UserFolderInfo) { 1608 final UserFolderInfo info = (UserFolderInfo) tag; 1609 final ArrayList<ShortcutInfo> contents = info.contents; 1610 final ArrayList<ShortcutInfo> toRemove = new ArrayList<ShortcutInfo>(1); 1611 final int contentsCount = contents.size(); 1612 boolean removedFromFolder = false; 1613 1614 for (int k = 0; k < contentsCount; k++) { 1615 final ShortcutInfo appInfo = contents.get(k); 1616 final Intent intent = appInfo.intent; 1617 final ComponentName name = intent.getComponent(); 1618 1619 if (Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1620 for (String packageName: packageNames) { 1621 if (packageName.equals(name.getPackageName())) { 1622 toRemove.add(appInfo); 1623 LauncherModel.deleteItemFromDatabase(mLauncher, appInfo); 1624 removedFromFolder = true; 1625 } 1626 } 1627 } 1628 } 1629 1630 contents.removeAll(toRemove); 1631 if (removedFromFolder) { 1632 final Folder folder = getOpenFolder(); 1633 if (folder != null) 1634 folder.notifyDataSetChanged(); 1635 } 1636 } else if (tag instanceof LiveFolderInfo) { 1637 final LiveFolderInfo info = (LiveFolderInfo) tag; 1638 final Uri uri = info.uri; 1639 final ProviderInfo providerInfo = manager.resolveContentProvider( 1640 uri.getAuthority(), 0); 1641 1642 if (providerInfo != null) { 1643 for (String packageName: packageNames) { 1644 if (packageName.equals(providerInfo.packageName)) { 1645 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1646 childrenToRemove.add(view); 1647 } 1648 } 1649 } 1650 } else if (tag instanceof LauncherAppWidgetInfo) { 1651 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag; 1652 final AppWidgetProviderInfo provider = 1653 widgets.getAppWidgetInfo(info.appWidgetId); 1654 if (provider != null) { 1655 for (String packageName: packageNames) { 1656 if (packageName.equals(provider.provider.getPackageName())) { 1657 LauncherModel.deleteItemFromDatabase(mLauncher, info); 1658 childrenToRemove.add(view); 1659 } 1660 } 1661 } 1662 } 1663 } 1664 1665 childCount = childrenToRemove.size(); 1666 for (int j = 0; j < childCount; j++) { 1667 View child = childrenToRemove.get(j); 1668 layout.removeViewInLayout(child); 1669 if (child instanceof DropTarget) { 1670 mDragController.removeDropTarget((DropTarget)child); 1671 } 1672 } 1673 1674 if (childCount > 0) { 1675 layout.requestLayout(); 1676 layout.invalidate(); 1677 } 1678 } 1679 }); 1680 } 1681 } 1682 1683 void updateShortcuts(ArrayList<ApplicationInfo> apps) { 1684 final int screenCount = getChildCount(); 1685 for (int i = 0; i < screenCount; i++) { 1686 final CellLayout layout = (CellLayout) getChildAt(i); 1687 int childCount = layout.getChildCount(); 1688 for (int j = 0; j < childCount; j++) { 1689 final View view = layout.getChildAt(j); 1690 Object tag = view.getTag(); 1691 if (tag instanceof ShortcutInfo) { 1692 ShortcutInfo info = (ShortcutInfo)tag; 1693 // We need to check for ACTION_MAIN otherwise getComponent() might 1694 // return null for some shortcuts (for instance, for shortcuts to 1695 // web pages.) 1696 final Intent intent = info.intent; 1697 final ComponentName name = intent.getComponent(); 1698 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION && 1699 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) { 1700 final int appCount = apps.size(); 1701 for (int k = 0; k < appCount; k++) { 1702 ApplicationInfo app = apps.get(k); 1703 if (app.componentName.equals(name)) { 1704 info.setIcon(mIconCache.getIcon(info.intent)); 1705 ((TextView)view).setCompoundDrawablesWithIntrinsicBounds(null, 1706 new FastBitmapDrawable(info.getIcon(mIconCache)), 1707 null, null); 1708 } 1709 } 1710 } 1711 } 1712 } 1713 } 1714 } 1715 1716 void moveToDefaultScreen(boolean animate) { 1717 if (mIsSmall || mIsInUnshrinkAnimation) { 1718 mLauncher.showWorkspace(animate, (CellLayout)getChildAt(mDefaultPage)); 1719 } else if (animate) { 1720 snapToPage(mDefaultPage); 1721 } else { 1722 setCurrentPage(mDefaultPage); 1723 } 1724 getChildAt(mDefaultPage).requestFocus(); 1725 } 1726 1727 void setIndicators(Drawable previous, Drawable next) { 1728 mPreviousIndicator = previous; 1729 mNextIndicator = next; 1730 previous.setLevel(mCurrentPage); 1731 next.setLevel(mCurrentPage); 1732 } 1733 1734 @Override 1735 public void syncPages() { 1736 } 1737 1738 @Override 1739 public void syncPageItems(int page) { 1740 } 1741 1742} 1743