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