2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.ComponentName;
22 import android.content.res.TypedArray;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.Paint;
26 import android.graphics.RectF;
27 import android.graphics.Rect;
28 import android.util.AttributeSet;
29 import android.view.MotionEvent;
30 import android.view.VelocityTracker;
31 import android.view.View;
32 import android.view.ViewConfiguration;
33 import android.view.ViewGroup;
34 import android.view.ViewParent;
35 import android.widget.Scroller;
36 import android.os.Parcelable;
37 import android.os.Parcel;
39 import java.util.ArrayList;
42 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
43 * screen contains a number of icons, folders or widgets the user can interact with.
44 * A workspace is meant to be used with a fixed width only.
46 public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
47 private static final int INVALID_SCREEN = -1;
50 * The velocity at which a fling gesture will cause us to snap to the next screen
52 private static final int SNAP_VELOCITY = 1000;
54 private int mDefaultScreen;
57 private Bitmap mWallpaper;
59 private int mWallpaperWidth;
60 private int mWallpaperHeight;
61 private float mWallpaperOffset;
62 private boolean mWallpaperLoaded;
64 private boolean mFirstLayout = true;
66 private int mCurrentScreen;
67 private int mNextScreen = INVALID_SCREEN;
68 private Scroller mScroller;
69 private VelocityTracker mVelocityTracker;
72 * CellInfo for the cell that is currently being dragged
74 private CellLayout.CellInfo mDragInfo;
76 private float mLastMotionX;
77 private float mLastMotionY;
79 private final static int TOUCH_STATE_REST = 0;
80 private final static int TOUCH_STATE_SCROLLING = 1;
82 private int mTouchState = TOUCH_STATE_REST;
84 private OnLongClickListener mLongClickListener;
86 private Launcher mLauncher;
87 private DragController mDragger;
89 private int[] mTempCell = new int[2];
91 private boolean mAllowLongPress;
92 private boolean mLocked;
94 private int mTouchSlop;
96 final Rect mDrawerBounds = new Rect();
97 final Rect mClipBounds = new Rect();
100 * Used to inflate the Workspace from XML.
102 * @param context The application's context.
103 * @param attrs The attribtues set containing the Workspace's customization values.
105 public Workspace(Context context, AttributeSet attrs) {
106 this(context, attrs, 0);
110 * Used to inflate the Workspace from XML.
112 * @param context The application's context.
113 * @param attrs The attribtues set containing the Workspace's customization values.
114 * @param defStyle Unused.
116 public Workspace(Context context, AttributeSet attrs, int defStyle) {
117 super(context, attrs, defStyle);
119 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
120 mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
127 * Initializes various states for this workspace.
129 private void initWorkspace() {
130 mScroller = new Scroller(getContext());
131 mCurrentScreen = mDefaultScreen;
132 Launcher.setScreen(mCurrentScreen);
134 mPaint = new Paint();
135 mPaint.setDither(false);
137 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
141 * Set the background's wallpaper.
143 void loadWallpaper(Bitmap bitmap) {
145 mWallpaperLoaded = true;
151 public void addView(View child, int index, LayoutParams params) {
152 if (!(child instanceof CellLayout)) {
153 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
155 super.addView(child, index, params);
159 public void addView(View child) {
160 if (!(child instanceof CellLayout)) {
161 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
163 super.addView(child);
167 public void addView(View child, int index) {
168 if (!(child instanceof CellLayout)) {
169 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
171 super.addView(child, index);
175 public void addView(View child, int width, int height) {
176 if (!(child instanceof CellLayout)) {
177 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
179 super.addView(child, width, height);
183 public void addView(View child, LayoutParams params) {
184 if (!(child instanceof CellLayout)) {
185 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
187 super.addView(child, params);
191 * @return The open folder on the current screen, or null if there is none
193 Folder getOpenFolder() {
194 CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
195 int count = currentScreen.getChildCount();
196 for (int i = 0; i < count; i++) {
197 View child = currentScreen.getChildAt(i);
198 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
199 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
200 return (Folder) child;
206 ArrayList<Folder> getOpenFolders() {
207 final int screens = getChildCount();
208 ArrayList<Folder> folders = new ArrayList<Folder>(screens);
210 for (int screen = 0; screen < screens; screen++) {
211 CellLayout currentScreen = (CellLayout) getChildAt(screen);
212 int count = currentScreen.getChildCount();
213 for (int i = 0; i < count; i++) {
214 View child = currentScreen.getChildAt(i);
215 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
216 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
217 folders.add((Folder) child);
226 boolean isDefaultScreenShowing() {
227 return mCurrentScreen == mDefaultScreen;
231 * Returns the index of the currently displayed screen.
233 * @return The index of the currently displayed screen.
235 int getCurrentScreen() {
236 return mCurrentScreen;
240 * Computes a bounding rectangle for a range of cells
242 * @param cellX X coordinate of upper left corner expressed as a cell position
243 * @param cellY Y coordinate of upper left corner expressed as a cell position
244 * @param cellHSpan Width in cells
245 * @param cellVSpan Height in cells
246 * @param rect Rectnagle into which to put the results
248 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF rect) {
249 ((CellLayout)getChildAt(mCurrentScreen)).cellToRect(cellX, cellY,
250 cellHSpan, cellVSpan, rect);
254 * Sets the current screen.
256 * @param currentScreen
258 void setCurrentScreen(int currentScreen) {
259 mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
260 scrollTo(mCurrentScreen * getWidth(), 0);
265 * Shows the default screen (defined by the firstScreen attribute in XML.)
267 void showDefaultScreen() {
268 setCurrentScreen(mDefaultScreen);
272 * Adds the specified child in the current screen. The position and dimension of
273 * the child are defined by x, y, spanX and spanY.
275 * @param child The child to add in one of the workspace's screens.
276 * @param x The X position of the child in the screen's grid.
277 * @param y The Y position of the child in the screen's grid.
278 * @param spanX The number of cells spanned horizontally by the child.
279 * @param spanY The number of cells spanned vertically by the child.
281 void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
282 addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
286 * Adds the specified child in the current screen. The position and dimension of
287 * the child are defined by x, y, spanX and spanY.
289 * @param child The child to add in one of the workspace's screens.
290 * @param x The X position of the child in the screen's grid.
291 * @param y The Y position of the child in the screen's grid.
292 * @param spanX The number of cells spanned horizontally by the child.
293 * @param spanY The number of cells spanned vertically by the child.
294 * @param insert When true, the child is inserted at the beginning of the children list.
296 void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
297 addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
301 * Adds the specified child in the specified screen. The position and dimension of
302 * the child are defined by x, y, spanX and spanY.
304 * @param child The child to add in one of the workspace's screens.
305 * @param screen The screen in which to add the child.
306 * @param x The X position of the child in the screen's grid.
307 * @param y The Y position of the child in the screen's grid.
308 * @param spanX The number of cells spanned horizontally by the child.
309 * @param spanY The number of cells spanned vertically by the child.
311 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
312 addInScreen(child, screen, x, y, spanX, spanY, false);
316 * Adds the specified child in the specified screen. The position and dimension of
317 * the child are defined by x, y, spanX and spanY.
319 * @param child The child to add in one of the workspace's screens.
320 * @param screen The screen in which to add the child.
321 * @param x The X position of the child in the screen's grid.
322 * @param y The Y position of the child in the screen's grid.
323 * @param spanX The number of cells spanned horizontally by the child.
324 * @param spanY The number of cells spanned vertically by the child.
325 * @param insert When true, the child is inserted at the beginning of the children list.
327 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
328 if (screen < 0 || screen >= getChildCount()) {
329 throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
332 final CellLayout group = (CellLayout) getChildAt(screen);
333 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
335 lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
339 lp.cellHSpan = spanX;
340 lp.cellVSpan = spanY;
342 group.addView(child, insert ? 0 : -1, lp);
343 if (!(child instanceof Folder)) {
344 child.setOnLongClickListener(mLongClickListener);
348 void addWidget(View view, Widget widget) {
349 addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
350 widget.spanY, false);
353 void addWidget(View view, Widget widget, boolean insert) {
354 addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
355 widget.spanY, insert);
358 CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
359 CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
361 return group.findAllVacantCells(occupied);
367 * Returns the coordinate of a vacant cell for the current screen.
369 boolean getVacantCell(int[] vacant, int spanX, int spanY) {
370 CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
372 return group.getVacantCell(vacant, spanX, spanY);
378 * Adds the specified child in the current screen. The position and dimension of
379 * the child are defined by x, y, spanX and spanY.
381 * @param child The child to add in one of the workspace's screens.
382 * @param spanX The number of cells spanned horizontally by the child.
383 * @param spanY The number of cells spanned vertically by the child.
385 void fitInCurrentScreen(View child, int spanX, int spanY) {
386 fitInScreen(child, mCurrentScreen, spanX, spanY);
390 * Adds the specified child in the specified screen. The position and dimension of
391 * the child are defined by x, y, spanX and spanY.
393 * @param child The child to add in one of the workspace's screens.
394 * @param screen The screen in which to add the child.
395 * @param spanX The number of cells spanned horizontally by the child.
396 * @param spanY The number of cells spanned vertically by the child.
398 void fitInScreen(View child, int screen, int spanX, int spanY) {
399 if (screen < 0 || screen >= getChildCount()) {
400 throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
403 final CellLayout group = (CellLayout) getChildAt(screen);
404 boolean vacant = group.getVacantCell(mTempCell, spanX, spanY);
407 new CellLayout.LayoutParams(mTempCell[0], mTempCell[1], spanX, spanY));
408 child.setOnLongClickListener(mLongClickListener);
409 if (!(child instanceof Folder)) {
410 child.setOnLongClickListener(mLongClickListener);
416 * Registers the specified listener on each screen contained in this workspace.
418 * @param l The listener used to respond to long clicks.
421 public void setOnLongClickListener(OnLongClickListener l) {
422 mLongClickListener = l;
423 final int count = getChildCount();
424 for (int i = 0; i < count; i++) {
425 getChildAt(i).setOnLongClickListener(l);
430 public void computeScroll() {
431 if (mScroller.computeScrollOffset()) {
432 mScrollX = mScroller.getCurrX();
433 mScrollY = mScroller.getCurrY();
435 } else if (mNextScreen != INVALID_SCREEN) {
436 mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
437 Launcher.setScreen(mCurrentScreen);
438 mNextScreen = INVALID_SCREEN;
439 clearChildrenCache();
444 protected void dispatchDraw(Canvas canvas) {
445 // If the all apps drawer is open and the drawing region for the workspace
446 // is contained within the drawer's bounds, we skip the drawing. This requires
447 // the drawer to be fully opaque.
448 if (mLauncher.isDrawerUp()) {
449 final Rect clipBounds = mClipBounds;
450 canvas.getClipBounds(clipBounds);
451 clipBounds.offset(-mScrollX, -mScrollY);
452 if (mDrawerBounds.contains(clipBounds)) {
457 float x = mScrollX * mWallpaperOffset;
458 if (x + mWallpaperWidth < mRight - mLeft) {
459 x = mRight - mLeft - mWallpaperWidth;
462 canvas.drawBitmap(mWallpaper, x, (mBottom - mTop - mWallpaperHeight) / 2, mPaint);
464 // ViewGroup.dispatchDraw() supports many features we don't need:
465 // clip to padding, layout animation, animation listener, disappearing
466 // children, etc. The following implementation attempts to fast-track
467 // the drawing dispatch by drawing only what we know needs to be drawn.
469 boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
470 // If we are not scrolling or flinging, draw only the current screen
472 drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
474 final long drawingTime = getDrawingTime();
475 // If we are flinging, draw only the current screen and the target screen
476 if (mNextScreen >= 0 && mNextScreen < getChildCount() &&
477 Math.abs(mCurrentScreen - mNextScreen) == 1) {
478 drawChild(canvas, getChildAt(mCurrentScreen), drawingTime);
479 drawChild(canvas, getChildAt(mNextScreen), drawingTime);
481 // If we are scrolling, draw all of our children
482 final int count = getChildCount();
483 for (int i = 0; i < count; i++) {
484 drawChild(canvas, getChildAt(i), drawingTime);
491 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
492 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
494 final int width = MeasureSpec.getSize(widthMeasureSpec);
495 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
496 if (widthMode != MeasureSpec.EXACTLY) {
497 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
500 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
501 if (heightMode != MeasureSpec.EXACTLY) {
502 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
505 // The children are given the same width and height as the workspace
506 final int count = getChildCount();
507 for (int i = 0; i < count; i++) {
508 getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
511 if (mWallpaperLoaded) {
512 mWallpaperLoaded = false;
513 mWallpaper = Utilities.centerToFit(mWallpaper, width,
514 MeasureSpec.getSize(heightMeasureSpec), getContext());
515 mWallpaperWidth = mWallpaper.getWidth();
516 mWallpaperHeight = mWallpaper.getHeight();
519 final int wallpaperWidth = mWallpaperWidth;
520 mWallpaperOffset = wallpaperWidth > width ? (count * width - wallpaperWidth) /
521 ((count - 1) * (float) width) : 1.0f;
524 scrollTo(mCurrentScreen * width, 0);
525 mFirstLayout = false;
530 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
533 final int count = getChildCount();
534 for (int i = 0; i < count; i++) {
535 final View child = getChildAt(i);
536 if (child.getVisibility() != View.GONE) {
537 final int childWidth = child.getMeasuredWidth();
538 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
539 childLeft += childWidth;
545 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
546 int screen = indexOfChild(child);
547 if (screen != mCurrentScreen || !mScroller.isFinished()) {
548 if (!mLauncher.isWorkspaceLocked()) {
549 snapToScreen(screen);
557 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
558 if (mLauncher.isDrawerDown()) {
559 final Folder openFolder = getOpenFolder();
560 if (openFolder != null) {
561 return openFolder.requestFocus(direction, previouslyFocusedRect);
564 if (mNextScreen != INVALID_SCREEN) {
565 focusableScreen = mNextScreen;
567 focusableScreen = mCurrentScreen;
569 getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
576 public boolean dispatchUnhandledMove(View focused, int direction) {
577 if (direction == View.FOCUS_LEFT) {
578 if (getCurrentScreen() > 0) {
579 snapToScreen(getCurrentScreen() - 1);
582 } else if (direction == View.FOCUS_RIGHT) {
583 if (getCurrentScreen() < getChildCount() - 1) {
584 snapToScreen(getCurrentScreen() + 1);
588 return super.dispatchUnhandledMove(focused, direction);
592 public void addFocusables(ArrayList<View> views, int direction) {
593 if (mLauncher.isDrawerDown()) {
594 final Folder openFolder = getOpenFolder();
595 if (openFolder == null) {
596 getChildAt(mCurrentScreen).addFocusables(views, direction);
597 if (direction == View.FOCUS_LEFT) {
598 if (mCurrentScreen > 0) {
599 getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
601 } else if (direction == View.FOCUS_RIGHT){
602 if (mCurrentScreen < getChildCount() - 1) {
603 getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
607 openFolder.addFocusables(views, direction);
613 public boolean onInterceptTouchEvent(MotionEvent ev) {
614 if (mLocked || !mLauncher.isDrawerDown()) {
619 * This method JUST determines whether we want to intercept the motion.
620 * If we return true, onTouchEvent will be called and we do the actual
625 * Shortcut the most recurring case: the user is in the dragging
626 * state and he is moving his finger. We want to intercept this
629 final int action = ev.getAction();
630 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
634 final float x = ev.getX();
635 final float y = ev.getY();
638 case MotionEvent.ACTION_MOVE:
640 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
641 * whether the user has moved far enough from his original down touch.
645 * Locally do absolute value. mLastMotionX is set to the y value
648 final int xDiff = (int) Math.abs(x - mLastMotionX);
649 final int yDiff = (int) Math.abs(y - mLastMotionY);
651 final int touchSlop = mTouchSlop;
652 boolean xMoved = xDiff > touchSlop;
653 boolean yMoved = yDiff > touchSlop;
655 if (xMoved || yMoved) {
658 // Scroll if the user moved far enough along the X axis
659 mTouchState = TOUCH_STATE_SCROLLING;
660 enableChildrenCache();
662 // Either way, cancel any pending longpress
663 if (mAllowLongPress) {
664 mAllowLongPress = false;
665 // Try canceling the long press. It could also have been scheduled
666 // by a distant descendant, so use the mAllowLongPress flag to block
668 final View currentScreen = getChildAt(mCurrentScreen);
669 currentScreen.cancelLongPress();
674 case MotionEvent.ACTION_DOWN:
675 // Remember location of down touch
678 mAllowLongPress = true;
681 * If being flinged and user touches the screen, initiate drag;
682 * otherwise don't. mScroller.isFinished should be false when
685 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
688 case MotionEvent.ACTION_CANCEL:
689 case MotionEvent.ACTION_UP:
691 clearChildrenCache();
692 mTouchState = TOUCH_STATE_REST;
693 mAllowLongPress = false;
698 * The only time we want to intercept motion events is if we are in the
701 return mTouchState != TOUCH_STATE_REST;
704 void enableChildrenCache() {
705 final int count = getChildCount();
706 for (int i = 0; i < count; i++) {
707 final CellLayout layout = (CellLayout) getChildAt(i);
708 layout.setChildrenDrawnWithCacheEnabled(true);
709 layout.setChildrenDrawingCacheEnabled(true);
713 void clearChildrenCache() {
714 final int count = getChildCount();
715 for (int i = 0; i < count; i++) {
716 final CellLayout layout = (CellLayout) getChildAt(i);
717 layout.setChildrenDrawnWithCacheEnabled(false);
722 public boolean onTouchEvent(MotionEvent ev) {
723 if (mLocked || !mLauncher.isDrawerDown()) {
727 if (mVelocityTracker == null) {
728 mVelocityTracker = VelocityTracker.obtain();
730 mVelocityTracker.addMovement(ev);
732 final int action = ev.getAction();
733 final float x = ev.getX();
736 case MotionEvent.ACTION_DOWN:
738 * If being flinged and user touches, stop the fling. isFinished
739 * will be false if being flinged.
741 if (!mScroller.isFinished()) {
742 mScroller.abortAnimation();
745 // Remember where the motion event started
748 case MotionEvent.ACTION_MOVE:
749 if (mTouchState == TOUCH_STATE_SCROLLING) {
750 // Scroll to follow the motion event
751 final int deltaX = (int) (mLastMotionX - x);
756 scrollBy(Math.max(-mScrollX, deltaX), 0);
758 } else if (deltaX > 0) {
759 final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -
760 mScrollX - getWidth();
761 if (availableToScroll > 0) {
762 scrollBy(Math.min(availableToScroll, deltaX), 0);
767 case MotionEvent.ACTION_UP:
768 if (mTouchState == TOUCH_STATE_SCROLLING) {
769 final VelocityTracker velocityTracker = mVelocityTracker;
770 velocityTracker.computeCurrentVelocity(1000);
771 int velocityX = (int) velocityTracker.getXVelocity();
773 if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
774 // Fling hard enough to move left
775 snapToScreen(mCurrentScreen - 1);
776 } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
777 // Fling hard enough to move right
778 snapToScreen(mCurrentScreen + 1);
783 if (mVelocityTracker != null) {
784 mVelocityTracker.recycle();
785 mVelocityTracker = null;
788 mTouchState = TOUCH_STATE_REST;
790 case MotionEvent.ACTION_CANCEL:
791 mTouchState = TOUCH_STATE_REST;
797 private void snapToDestination() {
798 final int screenWidth = getWidth();
799 final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
801 snapToScreen(whichScreen);
804 void snapToScreen(int whichScreen) {
805 enableChildrenCache();
807 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
808 boolean changingScreens = whichScreen != mCurrentScreen;
810 mNextScreen = whichScreen;
812 View focusedChild = getFocusedChild();
813 if (focusedChild != null && changingScreens && focusedChild == getChildAt(mCurrentScreen)) {
814 focusedChild.clearFocus();
817 final int newX = whichScreen * getWidth();
818 final int delta = newX - mScrollX;
819 mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
823 void startDrag(CellLayout.CellInfo cellInfo) {
824 View child = cellInfo.cell;
826 // Make sure the drag was started by a long press as opposed to a long click.
827 // Note that Search takes focus when clicked rather than entering touch mode
828 if (!child.isInTouchMode() && !(child instanceof Search)) {
832 mDragInfo = cellInfo;
833 mDragInfo.screen = mCurrentScreen;
835 CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
837 current.onDragChild(child);
838 mDragger.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
843 protected Parcelable onSaveInstanceState() {
844 final SavedState state = new SavedState(super.onSaveInstanceState());
845 state.currentScreen = mCurrentScreen;
850 protected void onRestoreInstanceState(Parcelable state) {
851 SavedState savedState = (SavedState) state;
852 super.onRestoreInstanceState(savedState.getSuperState());
853 if (savedState.currentScreen != -1) {
854 mCurrentScreen = savedState.currentScreen;
855 Launcher.setScreen(mCurrentScreen);
859 void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo) {
860 addApplicationShortcut(info, cellInfo, false);
863 void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo,
864 boolean insertAtFirst) {
865 final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
866 final int[] result = new int[2];
868 layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
869 onDropExternal(result[0], result[1], info, layout, insertAtFirst);
872 public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
873 final CellLayout cellLayout = (CellLayout) getChildAt(mCurrentScreen);
874 if (source != this) {
875 onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
878 if (mDragInfo != null) {
879 final View cell = mDragInfo.cell;
880 if (mCurrentScreen != mDragInfo.screen) {
881 final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
882 originalCellLayout.removeView(cell);
883 cellLayout.addView(cell);
885 cellLayout.onDropChild(cell, x - xOffset, y - yOffset);
887 final ItemInfo info = (ItemInfo)cell.getTag();
888 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
889 LauncherModel.moveItemInDatabase(mLauncher, info,
890 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
895 public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
899 public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
903 public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
907 private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
908 onDropExternal(x, y, dragInfo, cellLayout, false);
911 private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout,
912 boolean insertAtFirst) {
913 // Drag from somewhere else
914 ItemInfo info = (ItemInfo) dragInfo;
918 switch (info.itemType) {
919 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
920 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
921 if (info.container == NO_ID) {
922 // Came from all apps -- make a copy
923 info = new ApplicationInfo((ApplicationInfo) info);
925 view = mLauncher.createShortcut(R.layout.application, cellLayout,
926 (ApplicationInfo) info);
928 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
929 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
930 (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
933 throw new IllegalStateException("Unknown item type: " + info.itemType);
936 cellLayout.addView(view, insertAtFirst ? 0 : -1);
937 view.setOnLongClickListener(mLongClickListener);
938 cellLayout.onDropChild(view, x, y);
939 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
941 final LauncherModel model = Launcher.getModel();
942 model.addDesktopItem(info);
943 LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
944 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
947 public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
950 final CellLayout.CellInfo cellInfo = mDragInfo;
951 int cellHSpan = cellInfo == null ? 1 : cellInfo.spanX;
952 int cellVSpan = cellInfo == null ? 1 : cellInfo.spanY;
954 return ((CellLayout) getChildAt(mCurrentScreen)).acceptChildDrop(x - xOffset, y - yOffset,
955 cellHSpan, cellVSpan, cellInfo == null ? null : cellInfo.cell);
958 void setLauncher(Launcher launcher) {
959 mLauncher = launcher;
962 public void setDragger(DragController dragger) {
966 public void onDropCompleted(View target, boolean success) {
968 if (target != this && mDragInfo != null) {
969 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
970 cellLayout.removeView(mDragInfo.cell);
971 final Object tag = mDragInfo.cell.getTag();
972 Launcher.getModel().removeDesktopItem((ItemInfo) tag);
975 if (mDragInfo != null) {
976 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
977 cellLayout.onDropAborted(mDragInfo.cell);
984 public void scrollLeft() {
985 if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
986 snapToScreen(mCurrentScreen - 1);
990 public void scrollRight() {
991 if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
992 mScroller.isFinished()) {
993 snapToScreen(mCurrentScreen + 1);
997 public int getScreenForView(View v) {
1000 ViewParent vp = v.getParent();
1001 int count = getChildCount();
1002 for (int i = 0; i < count; i++) {
1003 if (vp == getChildAt(i)) {
1012 * Find a search widget on the given screen
1014 private Search findSearchWidget(CellLayout screen) {
1015 final int count = screen.getChildCount();
1016 for (int i = 0; i < count; i++) {
1017 View v = screen.getChildAt(i);
1018 if (v instanceof Search) {
1026 * Focuses on the search widget on the specified screen,
1027 * if there is one. Also clears the current search selection so we don't
1029 private boolean focusOnSearch(int screen) {
1030 CellLayout currentScreen = (CellLayout) getChildAt(screen);
1031 final Search searchWidget = findSearchWidget(currentScreen);
1032 if (searchWidget != null) {
1033 // This is necessary when focus on search is requested from the menu
1034 // If the workspace was not in touch mode before the menu is invoked
1035 // and the user clicks "Search" by touching the menu item, the following
1038 // - We request focus from touch on the search widget
1039 // - The search widget gains focus
1040 // - The window focus comes back to Home's window
1041 // - The touch mode change is propagated to Home's window
1042 // - The search widget is not focusable in touch mode and ViewRoot
1045 // Forcing focusable in touch mode ensures the search widget will
1046 // keep the focus no matter what happens.
1048 // Note: the search input field disables focusable in touch mode
1049 // after the window gets the focus back, see SearchAutoCompleteTextView
1050 final SearchAutoCompleteTextView input = searchWidget.getSearchInputField();
1051 input.setFocusableInTouchMode(true);
1052 input.showKeyboardOnNextFocus();
1054 if (isInTouchMode()) {
1055 searchWidget.requestFocusFromTouch();
1057 searchWidget.requestFocus();
1059 searchWidget.clearQuery();
1066 * Snap to the nearest screen with a search widget and give it focus
1068 * @return True if a search widget was found
1070 public boolean snapToSearch() {
1071 // The screen we are searching
1072 int current = mCurrentScreen;
1074 // first position scanned so far
1075 int first = current;
1077 // last position scanned so far
1080 // True if we should move down on the next iteration
1081 boolean next = false;
1083 // True when we have looked at the first item in the data
1086 // True when we have looked at the last item in the data
1089 final int count = getChildCount();
1092 if (focusOnSearch(current)) {
1096 hitLast = last == count - 1;
1097 hitFirst = first == 0;
1099 if (hitLast && hitFirst) {
1100 // Looked at everything
1104 if (hitFirst || (next && !hitLast)) {
1105 // Either we hit the top, or we are trying to move down
1108 // Try going up next time
1111 // Either we hit the bottom, or we are trying to move up
1114 // Try going down next time
1122 public Folder getFolderForTag(Object tag) {
1123 int screenCount = getChildCount();
1124 for (int screen = 0; screen < screenCount; screen++) {
1125 CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1126 int count = currentScreen.getChildCount();
1127 for (int i = 0; i < count; i++) {
1128 View child = currentScreen.getChildAt(i);
1129 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1130 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1131 Folder f = (Folder) child;
1132 if (f.getInfo() == tag) {
1141 public View getViewForTag(Object tag) {
1142 int screenCount = getChildCount();
1143 for (int screen = 0; screen < screenCount; screen++) {
1144 CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1145 int count = currentScreen.getChildCount();
1146 for (int i = 0; i < count; i++) {
1147 View child = currentScreen.getChildAt(i);
1148 if (child.getTag() == tag) {
1157 * Unlocks the SlidingDrawer so that touch events are processed.
1161 public void unlock() {
1166 * Locks the SlidingDrawer so that touch events are ignores.
1170 public void lock() {
1175 * @return True is long presses are still allowed for the current touch
1177 public boolean allowLongPress() {
1178 return mAllowLongPress;
1181 void removeShortcutsForPackage(String packageName) {
1182 final ArrayList<View> childrenToRemove = new ArrayList<View>();
1183 final LauncherModel model = Launcher.getModel();
1184 final int count = getChildCount();
1185 for (int i = 0; i < count; i++) {
1186 final CellLayout layout = (CellLayout) getChildAt(i);
1187 int childCount = layout.getChildCount();
1188 childrenToRemove.clear();
1189 for (int j = 0; j < childCount; j++) {
1190 final View view = layout.getChildAt(j);
1191 Object tag = view.getTag();
1192 if (tag instanceof ApplicationInfo) {
1193 ApplicationInfo info = (ApplicationInfo) tag;
1194 // We need to check for ACTION_MAIN otherwise getComponent() might
1195 // return null for some shortcuts (for instance, for shortcuts to
1197 final Intent intent = info.intent;
1198 final ComponentName name = intent.getComponent();
1199 if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1200 name != null && packageName.equals(name.getPackageName())) {
1201 model.removeDesktopItem(info);
1202 LauncherModel.deleteItemFromDatabase(mLauncher, info);
1203 childrenToRemove.add(view);
1207 childCount = childrenToRemove.size();
1208 for (int j = 0; j < childCount; j++) {
1209 layout.removeViewInLayout(childrenToRemove.get(j));
1211 if (childCount > 0) {
1212 layout.requestLayout();
1213 layout.invalidate();
1218 // TODO: remove gadgets when gadgetmanager tells us they're gone
1219 // void removeGadgetsForProvider() {
1222 void moveToDefaultScreen() {
1223 snapToScreen(mDefaultScreen);
1224 getChildAt(mDefaultScreen).requestFocus();
1227 public static class SavedState extends BaseSavedState {
1228 int currentScreen = -1;
1230 SavedState(Parcelable superState) {
1234 private SavedState(Parcel in) {
1236 currentScreen = in.readInt();
1240 public void writeToParcel(Parcel out, int flags) {
1241 super.writeToParcel(out, flags);
1242 out.writeInt(currentScreen);
1245 public static final Parcelable.Creator<SavedState> CREATOR =
1246 new Parcelable.Creator<SavedState>() {
1247 public SavedState createFromParcel(Parcel in) {
1248 return new SavedState(in);
1251 public SavedState[] newArray(int size) {
1252 return new SavedState[size];