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.res.TypedArray;
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.Paint;
24 import android.graphics.RectF;
25 import android.graphics.Rect;
26 import android.util.AttributeSet;
27 import android.view.MotionEvent;
28 import android.view.VelocityTracker;
29 import android.view.View;
30 import android.view.ViewConfiguration;
31 import android.view.ViewGroup;
32 import android.view.ViewParent;
33 import android.widget.Scroller;
34 import android.os.Parcelable;
35 import android.os.Parcel;
37 import java.util.ArrayList;
40 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
41 * screen contains a number of icons, folders or widgets the user can interact with.
42 * A workspace is meant to be used with a fixed width only.
44 public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
45 private static final int INVALID_SCREEN = -1;
48 * The velocity at which a fling gesture will cause us to snap to the next screen
50 private static final int SNAP_VELOCITY = 1000;
52 private int mDefaultScreen;
55 private Bitmap mWallpaper;
57 private int mWallpaperWidth;
58 private int mWallpaperHeight;
59 private float mWallpaperOffset;
60 private boolean mWallpaperLoaded;
62 private boolean mFirstLayout = true;
64 private int mCurrentScreen;
65 private int mNextScreen = INVALID_SCREEN;
66 private Scroller mScroller;
67 private VelocityTracker mVelocityTracker;
70 * CellInfo for the cell that is currently being dragged
72 private CellLayout.CellInfo mDragInfo;
74 private float mLastMotionX;
75 private float mLastMotionY;
77 private final static int TOUCH_STATE_REST = 0;
78 private final static int TOUCH_STATE_SCROLLING = 1;
80 private int mTouchState = TOUCH_STATE_REST;
82 private OnLongClickListener mLongClickListener;
84 private Launcher mLauncher;
85 private DragController mDragger;
87 private int[] mTempCell = new int[2];
89 private boolean mAllowLongPress;
90 private boolean mLocked;
92 private int mTouchSlop;
95 * Used to inflate the Workspace from XML.
97 * @param context The application's context.
98 * @param attrs The attribtues set containing the Workspace's customization values.
100 public Workspace(Context context, AttributeSet attrs) {
101 this(context, attrs, 0);
105 * Used to inflate the Workspace from XML.
107 * @param context The application's context.
108 * @param attrs The attribtues set containing the Workspace's customization values.
109 * @param defStyle Unused.
111 public Workspace(Context context, AttributeSet attrs, int defStyle) {
112 super(context, attrs, defStyle);
114 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
115 mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
122 * Initializes various states for this workspace.
124 private void initWorkspace() {
125 mScroller = new Scroller(getContext());
126 mCurrentScreen = mDefaultScreen;
127 Launcher.setScreen(mCurrentScreen);
129 mPaint = new Paint();
130 mPaint.setDither(false);
132 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
136 * Set the background's wallpaper.
138 void loadWallpaper(Bitmap bitmap) {
140 mWallpaperLoaded = true;
146 public void addView(View child, int index, LayoutParams params) {
147 if (!(child instanceof CellLayout)) {
148 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
150 super.addView(child, index, params);
154 public void addView(View child) {
155 if (!(child instanceof CellLayout)) {
156 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
158 super.addView(child);
162 public void addView(View child, int index) {
163 if (!(child instanceof CellLayout)) {
164 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
166 super.addView(child, index);
170 public void addView(View child, int width, int height) {
171 if (!(child instanceof CellLayout)) {
172 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
174 super.addView(child, width, height);
178 public void addView(View child, LayoutParams params) {
179 if (!(child instanceof CellLayout)) {
180 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
182 super.addView(child, params);
186 * @return The open folder on the current screen, or null if there is none
188 Folder getOpenFolder() {
189 CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
190 int count = currentScreen.getChildCount();
191 for (int i = 0; i < count; i++) {
192 View child = currentScreen.getChildAt(i);
193 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
194 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
195 return (Folder) child;
201 ArrayList<Folder> getOpenFolders() {
202 final int screens = getChildCount();
203 ArrayList<Folder> folders = new ArrayList<Folder>(screens);
205 for (int screen = 0; screen < screens; screen++) {
206 CellLayout currentScreen = (CellLayout) getChildAt(screen);
207 int count = currentScreen.getChildCount();
208 for (int i = 0; i < count; i++) {
209 View child = currentScreen.getChildAt(i);
210 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
211 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
212 folders.add((Folder) child);
221 boolean isDefaultScreenShowing() {
222 return mCurrentScreen == mDefaultScreen;
226 * Returns the index of the currently displayed screen.
228 * @return The index of the currently displayed screen.
230 int getCurrentScreen() {
231 return mCurrentScreen;
235 * Computes a bounding rectangle for a range of cells
237 * @param cellX X coordinate of upper left corner expressed as a cell position
238 * @param cellY Y coordinate of upper left corner expressed as a cell position
239 * @param cellHSpan Width in cells
240 * @param cellVSpan Height in cells
241 * @param rect Rectnagle into which to put the results
243 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF rect) {
244 ((CellLayout)getChildAt(mCurrentScreen)).cellToRect(cellX, cellY,
245 cellHSpan, cellVSpan, rect);
249 * Sets the current screen.
251 * @param currentScreen
253 void setCurrentScreen(int currentScreen) {
254 mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
255 scrollTo(mCurrentScreen * getWidth(), 0);
260 * Shows the default screen (defined by the firstScreen attribute in XML.)
262 void showDefaultScreen() {
263 setCurrentScreen(mDefaultScreen);
267 * Adds the specified child in the current screen. The position and dimension of
268 * the child are defined by x, y, spanX and spanY.
270 * @param child The child to add in one of the workspace's screens.
271 * @param x The X position of the child in the screen's grid.
272 * @param y The Y position of the child in the screen's grid.
273 * @param spanX The number of cells spanned horizontally by the child.
274 * @param spanY The number of cells spanned vertically by the child.
276 void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
277 addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
281 * Adds the specified child in the current screen. The position and dimension of
282 * the child are defined by x, y, spanX and spanY.
284 * @param child The child to add in one of the workspace's screens.
285 * @param x The X position of the child in the screen's grid.
286 * @param y The Y position of the child in the screen's grid.
287 * @param spanX The number of cells spanned horizontally by the child.
288 * @param spanY The number of cells spanned vertically by the child.
289 * @param insert When true, the child is inserted at the beginning of the children list.
291 void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
292 addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
296 * Adds the specified child in the specified screen. The position and dimension of
297 * the child are defined by x, y, spanX and spanY.
299 * @param child The child to add in one of the workspace's screens.
300 * @param screen The screen in which to add the child.
301 * @param x The X position of the child in the screen's grid.
302 * @param y The Y position of the child in the screen's grid.
303 * @param spanX The number of cells spanned horizontally by the child.
304 * @param spanY The number of cells spanned vertically by the child.
306 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
307 addInScreen(child, screen, x, y, spanX, spanY, false);
311 * Adds the specified child in the specified screen. The position and dimension of
312 * the child are defined by x, y, spanX and spanY.
314 * @param child The child to add in one of the workspace's screens.
315 * @param screen The screen in which to add the child.
316 * @param x The X position of the child in the screen's grid.
317 * @param y The Y position of the child in the screen's grid.
318 * @param spanX The number of cells spanned horizontally by the child.
319 * @param spanY The number of cells spanned vertically by the child.
320 * @param insert When true, the child is inserted at the beginning of the children list.
322 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
323 if (screen < 0 || screen >= getChildCount()) {
324 throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
327 final CellLayout group = (CellLayout) getChildAt(screen);
328 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
330 lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
334 lp.cellHSpan = spanX;
335 lp.cellVSpan = spanY;
337 group.addView(child, insert ? 0 : -1, lp);
338 if (!(child instanceof Folder)) {
339 child.setOnLongClickListener(mLongClickListener);
343 void addWidget(View view, Widget widget) {
344 addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
345 widget.spanY, false);
348 void addWidget(View view, Widget widget, boolean insert) {
349 addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
350 widget.spanY, insert);
353 CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
354 CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
356 return group.findAllVacantCells(occupied);
362 * Returns the coordinate of a vacant cell for the current screen.
364 boolean getVacantCell(int[] vacant, int spanX, int spanY) {
365 CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
367 return group.getVacantCell(vacant, spanX, spanY);
373 * Adds the specified child in the current screen. The position and dimension of
374 * the child are defined by x, y, spanX and spanY.
376 * @param child The child to add in one of the workspace's screens.
377 * @param spanX The number of cells spanned horizontally by the child.
378 * @param spanY The number of cells spanned vertically by the child.
380 void fitInCurrentScreen(View child, int spanX, int spanY) {
381 fitInScreen(child, mCurrentScreen, spanX, spanY);
385 * Adds the specified child in the specified screen. The position and dimension of
386 * the child are defined by x, y, spanX and spanY.
388 * @param child The child to add in one of the workspace's screens.
389 * @param screen The screen in which to add the child.
390 * @param spanX The number of cells spanned horizontally by the child.
391 * @param spanY The number of cells spanned vertically by the child.
393 void fitInScreen(View child, int screen, int spanX, int spanY) {
394 if (screen < 0 || screen >= getChildCount()) {
395 throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
398 final CellLayout group = (CellLayout) getChildAt(screen);
399 boolean vacant = group.getVacantCell(mTempCell, spanX, spanY);
402 new CellLayout.LayoutParams(mTempCell[0], mTempCell[1], spanX, spanY));
403 child.setOnLongClickListener(mLongClickListener);
404 if (!(child instanceof Folder)) {
405 child.setOnLongClickListener(mLongClickListener);
411 * Registers the specified listener on each screen contained in this workspace.
413 * @param l The listener used to respond to long clicks.
416 public void setOnLongClickListener(OnLongClickListener l) {
417 mLongClickListener = l;
418 final int count = getChildCount();
419 for (int i = 0; i < count; i++) {
420 getChildAt(i).setOnLongClickListener(l);
425 public void computeScroll() {
426 if (mScroller.computeScrollOffset()) {
427 mScrollX = mScroller.getCurrX();
428 mScrollY = mScroller.getCurrY();
430 } else if (mNextScreen != INVALID_SCREEN) {
431 mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
432 Launcher.setScreen(mCurrentScreen);
433 mNextScreen = INVALID_SCREEN;
434 clearChildrenCache();
439 protected void dispatchDraw(Canvas canvas) {
440 float x = mScrollX * mWallpaperOffset;
441 if (x + mWallpaperWidth < mRight - mLeft) {
442 x = mRight - mLeft - mWallpaperWidth;
445 canvas.drawBitmap(mWallpaper, x, (mBottom - mTop - mWallpaperHeight) / 2, mPaint);
447 // ViewGroup.dispatchDraw() supports many features we don't need:
448 // clip to padding, layout animation, animation listener, disappearing
449 // children, etc. The following implementation attempts to fast-track
450 // the drawing dispatch by drawing only what we know needs to be drawn.
452 boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
453 // If we are not scrolling or flinging, draw only the current screen
455 drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
457 final long drawingTime = getDrawingTime();
458 // If we are flinging, draw only the current screen and the target screen
459 if (mNextScreen >= 0 && mNextScreen < getChildCount() &&
460 Math.abs(mCurrentScreen - mNextScreen) == 1) {
461 drawChild(canvas, getChildAt(mCurrentScreen), drawingTime);
462 drawChild(canvas, getChildAt(mNextScreen), drawingTime);
464 // If we are scrolling, draw all of our children
465 final int count = getChildCount();
466 for (int i = 0; i < count; i++) {
467 drawChild(canvas, getChildAt(i), drawingTime);
474 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
475 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
477 final int width = MeasureSpec.getSize(widthMeasureSpec);
478 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
479 if (widthMode != MeasureSpec.EXACTLY) {
480 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
483 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
484 if (heightMode != MeasureSpec.EXACTLY) {
485 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
488 // The children are given the same width and height as the workspace
489 final int count = getChildCount();
490 for (int i = 0; i < count; i++) {
491 getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
494 if (mWallpaperLoaded) {
495 mWallpaperLoaded = false;
496 mWallpaper = Utilities.centerToFit(mWallpaper, width,
497 MeasureSpec.getSize(heightMeasureSpec), getContext());
498 mWallpaperWidth = mWallpaper.getWidth();
499 mWallpaperHeight = mWallpaper.getHeight();
502 final int wallpaperWidth = mWallpaperWidth;
503 mWallpaperOffset = wallpaperWidth > width ? (count * width - wallpaperWidth) /
504 ((count - 1) * (float) width) : 1.0f;
507 scrollTo(mCurrentScreen * width, 0);
508 mFirstLayout = false;
513 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
516 final int count = getChildCount();
517 for (int i = 0; i < count; i++) {
518 final View child = getChildAt(i);
519 if (child.getVisibility() != View.GONE) {
520 final int childWidth = child.getMeasuredWidth();
521 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
522 childLeft += childWidth;
528 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
529 int screen = indexOfChild(child);
530 if (screen != mCurrentScreen || !mScroller.isFinished()) {
531 if (!mLauncher.isWorkspaceLocked()) {
532 snapToScreen(screen);
540 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
541 if (mLauncher.isDrawerDown()) {
542 final Folder openFolder = getOpenFolder();
543 if (openFolder != null) {
544 return openFolder.requestFocus(direction, previouslyFocusedRect);
547 if (mNextScreen != INVALID_SCREEN) {
548 focusableScreen = mNextScreen;
550 focusableScreen = mCurrentScreen;
552 getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
559 public boolean dispatchUnhandledMove(View focused, int direction) {
560 if (direction == View.FOCUS_LEFT) {
561 if (getCurrentScreen() > 0) {
562 snapToScreen(getCurrentScreen() - 1);
565 } else if (direction == View.FOCUS_RIGHT) {
566 if (getCurrentScreen() < getChildCount() - 1) {
567 snapToScreen(getCurrentScreen() + 1);
571 return super.dispatchUnhandledMove(focused, direction);
575 public void addFocusables(ArrayList<View> views, int direction) {
576 if (mLauncher.isDrawerDown()) {
577 final Folder openFolder = getOpenFolder();
578 if (openFolder == null) {
579 getChildAt(mCurrentScreen).addFocusables(views, direction);
580 if (direction == View.FOCUS_LEFT) {
581 if (mCurrentScreen > 0) {
582 getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
584 } else if (direction == View.FOCUS_RIGHT){
585 if (mCurrentScreen < getChildCount() - 1) {
586 getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
590 openFolder.addFocusables(views, direction);
596 public boolean onInterceptTouchEvent(MotionEvent ev) {
597 if (mLocked || !mLauncher.isDrawerDown()) {
602 * This method JUST determines whether we want to intercept the motion.
603 * If we return true, onTouchEvent will be called and we do the actual
608 * Shortcut the most recurring case: the user is in the dragging
609 * state and he is moving his finger. We want to intercept this
612 final int action = ev.getAction();
613 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
617 final float x = ev.getX();
618 final float y = ev.getY();
621 case MotionEvent.ACTION_MOVE:
623 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
624 * whether the user has moved far enough from his original down touch.
628 * Locally do absolute value. mLastMotionX is set to the y value
631 final int xDiff = (int) Math.abs(x - mLastMotionX);
632 final int yDiff = (int) Math.abs(y - mLastMotionY);
634 final int touchSlop = mTouchSlop;
635 boolean xMoved = xDiff > touchSlop;
636 boolean yMoved = yDiff > touchSlop;
638 if (xMoved || yMoved) {
641 // Scroll if the user moved far enough along the X axis
642 mTouchState = TOUCH_STATE_SCROLLING;
643 enableChildrenCache();
645 // Either way, cancel any pending longpress
646 if (mAllowLongPress) {
647 mAllowLongPress = false;
648 // Try canceling the long press. It could also have been scheduled
649 // by a distant descendant, so use the mAllowLongPress flag to block
651 final View currentScreen = getChildAt(mCurrentScreen);
652 currentScreen.cancelLongPress();
657 case MotionEvent.ACTION_DOWN:
658 // Remember location of down touch
661 mAllowLongPress = true;
664 * If being flinged and user touches the screen, initiate drag;
665 * otherwise don't. mScroller.isFinished should be false when
668 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
671 case MotionEvent.ACTION_CANCEL:
672 case MotionEvent.ACTION_UP:
674 clearChildrenCache();
675 mTouchState = TOUCH_STATE_REST;
680 * The only time we want to intercept motion events is if we are in the
683 return mTouchState != TOUCH_STATE_REST;
686 void enableChildrenCache() {
687 final int count = getChildCount();
688 for (int i = 0; i < count; i++) {
689 final CellLayout layout = (CellLayout) getChildAt(i);
690 layout.setChildrenDrawnWithCacheEnabled(true);
691 layout.setChildrenDrawingCacheEnabled(true);
695 void clearChildrenCache() {
696 final int count = getChildCount();
697 for (int i = 0; i < count; i++) {
698 final CellLayout layout = (CellLayout) getChildAt(i);
699 layout.setChildrenDrawnWithCacheEnabled(false);
704 public boolean onTouchEvent(MotionEvent ev) {
705 if (mLocked || !mLauncher.isDrawerDown()) {
709 if (mVelocityTracker == null) {
710 mVelocityTracker = VelocityTracker.obtain();
712 mVelocityTracker.addMovement(ev);
714 final int action = ev.getAction();
715 final float x = ev.getX();
718 case MotionEvent.ACTION_DOWN:
720 * If being flinged and user touches, stop the fling. isFinished
721 * will be false if being flinged.
723 if (!mScroller.isFinished()) {
724 mScroller.abortAnimation();
727 // Remember where the motion event started
730 case MotionEvent.ACTION_MOVE:
731 if (mTouchState == TOUCH_STATE_SCROLLING) {
732 // Scroll to follow the motion event
733 final int deltaX = (int) (mLastMotionX - x);
738 scrollBy(Math.max(-mScrollX, deltaX), 0);
740 } else if (deltaX > 0) {
741 final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -
742 mScrollX - getWidth();
743 if (availableToScroll > 0) {
744 scrollBy(Math.min(availableToScroll, deltaX), 0);
749 case MotionEvent.ACTION_UP:
750 if (mTouchState == TOUCH_STATE_SCROLLING) {
751 final VelocityTracker velocityTracker = mVelocityTracker;
752 velocityTracker.computeCurrentVelocity(1000);
753 int velocityX = (int) velocityTracker.getXVelocity();
755 if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
756 // Fling hard enough to move left
757 snapToScreen(mCurrentScreen - 1);
758 } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
759 // Fling hard enough to move right
760 snapToScreen(mCurrentScreen + 1);
765 if (mVelocityTracker != null) {
766 mVelocityTracker.recycle();
767 mVelocityTracker = null;
770 mTouchState = TOUCH_STATE_REST;
772 case MotionEvent.ACTION_CANCEL:
773 mTouchState = TOUCH_STATE_REST;
779 private void snapToDestination() {
780 final int screenWidth = getWidth();
781 final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
783 snapToScreen(whichScreen);
786 void snapToScreen(int whichScreen) {
787 enableChildrenCache();
789 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
790 boolean changingScreens = whichScreen != mCurrentScreen;
792 mNextScreen = whichScreen;
794 View focusedChild = getFocusedChild();
795 if (focusedChild != null && changingScreens && focusedChild == getChildAt(mCurrentScreen)) {
796 focusedChild.clearFocus();
799 final int newX = whichScreen * getWidth();
800 final int delta = newX - mScrollX;
801 mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
805 void startDrag(CellLayout.CellInfo cellInfo) {
806 View child = cellInfo.cell;
808 // Make sure the drag was started by a long press as opposed to a long click.
809 // Note that Search takes focus when clicked rather than entering touch mode
810 if (!child.isInTouchMode() && !(child instanceof Search)) {
814 mDragInfo = cellInfo;
815 mDragInfo.screen = mCurrentScreen;
817 CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
819 current.onDragChild(child);
820 mDragger.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
825 protected Parcelable onSaveInstanceState() {
826 final SavedState state = new SavedState(super.onSaveInstanceState());
827 state.currentScreen = mCurrentScreen;
832 protected void onRestoreInstanceState(Parcelable state) {
833 SavedState savedState = (SavedState) state;
834 super.onRestoreInstanceState(savedState.getSuperState());
835 if (savedState.currentScreen != -1) {
836 mCurrentScreen = savedState.currentScreen;
837 Launcher.setScreen(mCurrentScreen);
841 void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo) {
842 addApplicationShortcut(info, cellInfo, false);
845 void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo,
846 boolean insertAtFirst) {
847 final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
848 final int[] result = new int[2];
850 layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
851 onDropExternal(result[0], result[1], info, layout, insertAtFirst);
854 public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
855 final CellLayout cellLayout = (CellLayout) getChildAt(mCurrentScreen);
856 if (source != this) {
857 onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
860 if (mDragInfo != null) {
861 final View cell = mDragInfo.cell;
862 if (mCurrentScreen != mDragInfo.screen) {
863 final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
864 originalCellLayout.removeView(cell);
865 cellLayout.addView(cell);
867 cellLayout.onDropChild(cell, x - xOffset, y - yOffset);
869 final ItemInfo info = (ItemInfo)cell.getTag();
870 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
871 LauncherModel.moveItemInDatabase(mLauncher, info,
872 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
877 public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
881 public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
885 public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
889 private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
890 onDropExternal(x, y, dragInfo, cellLayout, false);
893 private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout,
894 boolean insertAtFirst) {
895 // Drag from somewhere else
896 ItemInfo info = (ItemInfo) dragInfo;
900 switch (info.itemType) {
901 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
902 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
903 if (info.container == NO_ID) {
904 // Came from all apps -- make a copy
905 info = new ApplicationInfo((ApplicationInfo) info);
907 view = mLauncher.createShortcut(R.layout.application, cellLayout,
908 (ApplicationInfo) info);
910 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
911 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
912 (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
915 throw new IllegalStateException("Unknown item type: " + info.itemType);
918 cellLayout.addView(view, insertAtFirst ? 0 : -1);
919 view.setOnLongClickListener(mLongClickListener);
920 cellLayout.onDropChild(view, x, y);
921 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
923 final LauncherModel model = Launcher.getModel();
924 model.addDesktopItem(info);
925 LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
926 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
929 public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
932 final CellLayout.CellInfo cellInfo = mDragInfo;
933 int cellHSpan = cellInfo == null ? 1 : cellInfo.spanX;
934 int cellVSpan = cellInfo == null ? 1 : cellInfo.spanY;
936 return ((CellLayout) getChildAt(mCurrentScreen)).acceptChildDrop(x - xOffset, y - yOffset,
937 cellHSpan, cellVSpan, cellInfo == null ? null : cellInfo.cell);
940 void setLauncher(Launcher launcher) {
941 mLauncher = launcher;
944 public void setDragger(DragController dragger) {
948 public void onDropCompleted(View target, boolean success) {
950 if (target != this && mDragInfo != null) {
951 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
952 cellLayout.removeView(mDragInfo.cell);
953 final Object tag = mDragInfo.cell.getTag();
954 Launcher.getModel().removeDesktopItem((ItemInfo) tag);
957 if (mDragInfo != null) {
958 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
959 cellLayout.onDropAborted(mDragInfo.cell);
966 public void scrollLeft() {
967 if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
968 snapToScreen(mCurrentScreen - 1);
972 public void scrollRight() {
973 if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
974 mScroller.isFinished()) {
975 snapToScreen(mCurrentScreen + 1);
979 public int getScreenForView(View v) {
982 ViewParent vp = v.getParent();
983 int count = getChildCount();
984 for (int i = 0; i < count; i++) {
985 if (vp == getChildAt(i)) {
994 * Find a search widget on the given screen
996 private View findSearchWidget(CellLayout screen) {
997 final int count = screen.getChildCount();
998 for (int i = 0; i < count; i++) {
999 View v = screen.getChildAt(i);
1000 if (v instanceof Search) {
1008 * Focuses on the search widget on the specified screen,
1009 * if there is one. Also clears the current search selection so we don't
1011 private boolean focusOnSearch(int screen) {
1012 CellLayout currentScreen = (CellLayout)getChildAt(screen);
1013 Search searchWidget = (Search)findSearchWidget(currentScreen);
1014 if (searchWidget != null) {
1015 if (isInTouchMode()) {
1016 searchWidget.requestFocusFromTouch();
1018 searchWidget.requestFocus();
1020 searchWidget.clearQuery();
1027 * Snap to the nearest screen with a search widget and give it focus
1029 * @return True if a search widget was found
1031 public boolean snapToSearch() {
1032 // The screen we are searching
1033 int current = mCurrentScreen;
1035 // first position scanned so far
1036 int first = current;
1038 // last position scanned so far
1041 // True if we should move down on the next iteration
1042 boolean next = false;
1044 // True when we have looked at the first item in the data
1047 // True when we have looked at the last item in the data
1050 final int count = getChildCount();
1053 if (focusOnSearch(current)) {
1057 hitLast = last == count - 1;
1058 hitFirst = first == 0;
1060 if (hitLast && hitFirst) {
1061 // Looked at everything
1065 if (hitFirst || (next && !hitLast)) {
1066 // Either we hit the top, or we are trying to move down
1069 // Try going up next time
1072 // Either we hit the bottom, or we are trying to move up
1075 // Try going down next time
1083 public Folder getFolderForTag(Object tag) {
1084 int screenCount = getChildCount();
1085 for (int screen = 0; screen < screenCount; screen++) {
1086 CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1087 int count = currentScreen.getChildCount();
1088 for (int i = 0; i < count; i++) {
1089 View child = currentScreen.getChildAt(i);
1090 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1091 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1092 Folder f = (Folder) child;
1093 if (f.getInfo() == tag) {
1102 public View getViewForTag(Object tag) {
1103 int screenCount = getChildCount();
1104 for (int screen = 0; screen < screenCount; screen++) {
1105 CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1106 int count = currentScreen.getChildCount();
1107 for (int i = 0; i < count; i++) {
1108 View child = currentScreen.getChildAt(i);
1109 if (child.getTag() == tag) {
1118 * Unlocks the SlidingDrawer so that touch events are processed.
1122 public void unlock() {
1127 * Locks the SlidingDrawer so that touch events are ignores.
1131 public void lock() {
1136 * @return True is long presses are still allowed for the current touch
1138 public boolean allowLongPress() {
1139 return mAllowLongPress;
1142 void removeShortcutsForPackage(String packageName) {
1143 final ArrayList<View> childrenToRemove = new ArrayList<View>();
1144 final LauncherModel model = Launcher.getModel();
1145 final int count = getChildCount();
1146 for (int i = 0; i < count; i++) {
1147 final CellLayout layout = (CellLayout) getChildAt(i);
1148 int childCount = layout.getChildCount();
1149 childrenToRemove.clear();
1150 for (int j = 0; j < childCount; j++) {
1151 final View view = layout.getChildAt(j);
1152 Object tag = view.getTag();
1153 if (tag instanceof ApplicationInfo) {
1154 ApplicationInfo info = (ApplicationInfo) tag;
1155 if (packageName.equals(info.intent.getComponent().getPackageName())) {
1156 model.removeDesktopItem(info);
1157 LauncherModel.deleteItemFromDatabase(mLauncher, info);
1158 childrenToRemove.add(view);
1162 childCount = childrenToRemove.size();
1163 for (int j = 0; j < childCount; j++) {
1164 layout.removeViewInLayout(childrenToRemove.get(j));
1166 if (childCount > 0) {
1167 layout.requestLayout();
1168 layout.invalidate();
1173 // TODO: remove gadgets when gadgetmanager tells us they're gone
1174 // void removeGadgetsForProvider() {
1177 void moveToDefaultScreen() {
1178 snapToScreen(mDefaultScreen);
1179 getChildAt(mDefaultScreen).requestFocus();
1182 public static class SavedState extends BaseSavedState {
1183 int currentScreen = -1;
1185 SavedState(Parcelable superState) {
1189 private SavedState(Parcel in) {
1191 currentScreen = in.readInt();
1195 public void writeToParcel(Parcel out, int flags) {
1196 super.writeToParcel(out, flags);
1197 out.writeInt(currentScreen);
1200 public static final Parcelable.Creator<SavedState> CREATOR =
1201 new Parcelable.Creator<SavedState>() {
1202 public SavedState createFromParcel(Parcel in) {
1203 return new SavedState(in);
1206 public SavedState[] newArray(int size) {
1207 return new SavedState[size];