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