OSDN Git Service

auto import from //depot/cupcake/@135843
[android-x86/packages-apps-Launcher.git] / src / com / android / launcher / Workspace.java
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
17 package com.android.launcher;
18
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;
38
39 import java.util.ArrayList;
40
41 /**
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.
45  */
46 public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
47     private static final int INVALID_SCREEN = -1;
48
49     /**
50      * The velocity at which a fling gesture will cause us to snap to the next screen
51      */
52     private static final int SNAP_VELOCITY = 1000;
53
54     private int mDefaultScreen;
55
56     private Paint mPaint;
57     private Bitmap mWallpaper;
58
59     private int mWallpaperWidth;
60     private int mWallpaperHeight;
61     private float mWallpaperOffset;
62     private boolean mWallpaperLoaded;
63
64     private boolean mFirstLayout = true;
65
66     private int mCurrentScreen;
67     private int mNextScreen = INVALID_SCREEN;
68     private Scroller mScroller;
69     private VelocityTracker mVelocityTracker;
70
71     /**
72      * CellInfo for the cell that is currently being dragged
73      */
74     private CellLayout.CellInfo mDragInfo;
75
76     private float mLastMotionX;
77     private float mLastMotionY;
78
79     private final static int TOUCH_STATE_REST = 0;
80     private final static int TOUCH_STATE_SCROLLING = 1;
81
82     private int mTouchState = TOUCH_STATE_REST;
83
84     private OnLongClickListener mLongClickListener;
85
86     private Launcher mLauncher;
87     private DragController mDragger;
88
89     private int[] mTempCell = new int[2];
90
91     private boolean mAllowLongPress;
92     private boolean mLocked;
93
94     private int mTouchSlop;
95
96     final Rect mDrawerBounds = new Rect();
97     final Rect mClipBounds = new Rect();
98
99     /**
100      * Used to inflate the Workspace from XML.
101      *
102      * @param context The application's context.
103      * @param attrs The attribtues set containing the Workspace's customization values.
104      */
105     public Workspace(Context context, AttributeSet attrs) {
106         this(context, attrs, 0);
107     }
108
109     /**
110      * Used to inflate the Workspace from XML.
111      *
112      * @param context The application's context.
113      * @param attrs The attribtues set containing the Workspace's customization values.
114      * @param defStyle Unused.
115      */
116     public Workspace(Context context, AttributeSet attrs, int defStyle) {
117         super(context, attrs, defStyle);
118
119         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
120         mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
121         a.recycle();
122
123         initWorkspace();
124     }
125
126     /**
127      * Initializes various states for this workspace.
128      */
129     private void initWorkspace() {
130         mScroller = new Scroller(getContext());
131         mCurrentScreen = mDefaultScreen;
132         Launcher.setScreen(mCurrentScreen);
133
134         mPaint = new Paint();
135         mPaint.setDither(false);
136
137         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
138     }
139
140     /**
141      * Set the background's wallpaper.
142      */
143     void loadWallpaper(Bitmap bitmap) {
144         mWallpaper = bitmap;
145         mWallpaperLoaded = true;
146         requestLayout();
147         invalidate();
148     }
149
150     @Override
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.");
154         }
155         super.addView(child, index, params);
156     }
157
158     @Override
159     public void addView(View child) {
160         if (!(child instanceof CellLayout)) {
161             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
162         }
163         super.addView(child);
164     }
165
166     @Override
167     public void addView(View child, int index) {
168         if (!(child instanceof CellLayout)) {
169             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
170         }
171         super.addView(child, index);
172     }
173
174     @Override
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.");
178         }
179         super.addView(child, width, height);
180     }
181
182     @Override
183     public void addView(View child, LayoutParams params) {
184         if (!(child instanceof CellLayout)) {
185             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
186         }
187         super.addView(child, params);
188     }
189
190     /**
191      * @return The open folder on the current screen, or null if there is none
192      */
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;
201             }
202         }
203         return null;
204     }
205
206     ArrayList<Folder> getOpenFolders() {
207         final int screens = getChildCount();
208         ArrayList<Folder> folders = new ArrayList<Folder>(screens);
209
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);
218                     break;
219                 }
220             }
221         }
222
223         return folders;
224     }
225
226     boolean isDefaultScreenShowing() {
227         return mCurrentScreen == mDefaultScreen;
228     }
229
230     /**
231      * Returns the index of the currently displayed screen.
232      *
233      * @return The index of the currently displayed screen.
234      */
235     int getCurrentScreen() {
236         return mCurrentScreen;
237     }
238
239     /**
240      * Computes a bounding rectangle for a range of cells
241      *
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
247      */
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);
251     }
252
253     /**
254      * Sets the current screen.
255      *
256      * @param currentScreen
257      */
258     void setCurrentScreen(int currentScreen) {
259         mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
260         scrollTo(mCurrentScreen * getWidth(), 0);
261         invalidate();
262     }
263
264     /**
265      * Shows the default screen (defined by the firstScreen attribute in XML.)
266      */
267     void showDefaultScreen() {
268         setCurrentScreen(mDefaultScreen);
269     }
270
271     /**
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.
274      *
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.
280      */
281     void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
282         addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
283     }
284
285     /**
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.
288      *
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.
295      */
296     void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
297         addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
298     }
299
300     /**
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.
303      *
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.
310      */
311     void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
312         addInScreen(child, screen, x, y, spanX, spanY, false);
313     }
314
315     /**
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.
318      *
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.
326      */
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());
330         }
331
332         final CellLayout group = (CellLayout) getChildAt(screen);
333         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
334         if (lp == null) {
335             lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
336         } else {
337             lp.cellX = x;
338             lp.cellY = y;
339             lp.cellHSpan = spanX;
340             lp.cellVSpan = spanY;
341         }
342         group.addView(child, insert ? 0 : -1, lp);
343         if (!(child instanceof Folder)) {
344             child.setOnLongClickListener(mLongClickListener);
345         }
346     }
347
348     void addWidget(View view, Widget widget) {
349         addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
350                 widget.spanY, false);
351     }
352
353     void addWidget(View view, Widget widget, boolean insert) {
354         addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
355                 widget.spanY, insert);
356     }
357
358     CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
359         CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
360         if (group != null) {
361             return group.findAllVacantCells(occupied);
362         }
363         return null;
364     }
365
366     /**
367      * Returns the coordinate of a vacant cell for the current screen.
368      */
369     boolean getVacantCell(int[] vacant, int spanX, int spanY) {
370         CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
371         if (group != null) {
372             return group.getVacantCell(vacant, spanX, spanY);
373         }
374         return false;
375     }
376
377     /**
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.
380      *
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.
384      */
385     void fitInCurrentScreen(View child, int spanX, int spanY) {
386         fitInScreen(child, mCurrentScreen, spanX, spanY);
387     }
388
389     /**
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.
392      *
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.
397      */
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());
401         }
402
403         final CellLayout group = (CellLayout) getChildAt(screen);
404         boolean vacant = group.getVacantCell(mTempCell, spanX, spanY);
405         if (vacant) {
406             group.addView(child,
407                     new CellLayout.LayoutParams(mTempCell[0], mTempCell[1], spanX, spanY));
408             child.setOnLongClickListener(mLongClickListener);
409             if (!(child instanceof Folder)) {
410                 child.setOnLongClickListener(mLongClickListener);
411             }
412         }
413     }
414
415     /**
416      * Registers the specified listener on each screen contained in this workspace.
417      *
418      * @param l The listener used to respond to long clicks.
419      */
420     @Override
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);
426         }
427     }
428
429     @Override
430     public void computeScroll() {
431         if (mScroller.computeScrollOffset()) {
432             mScrollX = mScroller.getCurrX();
433             mScrollY = mScroller.getCurrY();
434             postInvalidate();
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();
440         }
441     }
442
443     @Override
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)) {
453                 return;
454             }
455         }
456
457         float x = mScrollX * mWallpaperOffset;
458         if (x + mWallpaperWidth < mRight - mLeft) {
459             x = mRight - mLeft - mWallpaperWidth;
460         }
461
462         canvas.drawBitmap(mWallpaper, x, (mBottom - mTop - mWallpaperHeight) / 2, mPaint);
463
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.
468
469         boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
470         // If we are not scrolling or flinging, draw only the current screen
471         if (fastDraw) {
472             drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
473         } else {
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);
480             } else {
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);
485                 }
486             }
487         }
488     }
489
490     @Override
491     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
492         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
493
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.");
498         }
499
500         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
501         if (heightMode != MeasureSpec.EXACTLY) {
502             throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
503         }
504
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);
509         }
510
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();
517         }
518
519         final int wallpaperWidth = mWallpaperWidth;
520         mWallpaperOffset = wallpaperWidth > width ? (count * width - wallpaperWidth) /
521                 ((count - 1) * (float) width) : 1.0f;
522
523         if (mFirstLayout) {
524             scrollTo(mCurrentScreen * width, 0);
525             mFirstLayout = false;
526         }
527     }
528
529     @Override
530     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
531         int childLeft = 0;
532
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;
540             }
541         }
542     }
543
544     @Override
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);
550             }
551             return true;
552         }
553         return false;
554     }
555
556     @Override
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);
562             } else {
563                 int focusableScreen;
564                 if (mNextScreen != INVALID_SCREEN) {
565                     focusableScreen = mNextScreen;
566                 } else {
567                     focusableScreen = mCurrentScreen;
568                 }
569                 getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
570             }
571         }
572         return false;
573     }
574
575     @Override
576     public boolean dispatchUnhandledMove(View focused, int direction) {
577         if (direction == View.FOCUS_LEFT) {
578             if (getCurrentScreen() > 0) {
579                 snapToScreen(getCurrentScreen() - 1);
580                 return true;
581             }
582         } else if (direction == View.FOCUS_RIGHT) {
583             if (getCurrentScreen() < getChildCount() - 1) {
584                 snapToScreen(getCurrentScreen() + 1);
585                 return true;
586             }
587         }
588         return super.dispatchUnhandledMove(focused, direction);
589     }
590
591     @Override
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);
600                     }
601                 } else if (direction == View.FOCUS_RIGHT){
602                     if (mCurrentScreen < getChildCount() - 1) {
603                         getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
604                     }
605                 }
606             } else {
607                 openFolder.addFocusables(views, direction);
608             }
609         }
610     }
611
612     @Override
613     public boolean onInterceptTouchEvent(MotionEvent ev) {
614         if (mLocked || !mLauncher.isDrawerDown()) {
615             return true;
616         }
617
618         /*
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
621          * scrolling there.
622          */
623
624         /*
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
627          * motion.
628          */
629         final int action = ev.getAction();
630         if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
631             return true;
632         }
633
634         final float x = ev.getX();
635         final float y = ev.getY();
636
637         switch (action) {
638             case MotionEvent.ACTION_MOVE:
639                 /*
640                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
641                  * whether the user has moved far enough from his original down touch.
642                  */
643
644                 /*
645                  * Locally do absolute value. mLastMotionX is set to the y value
646                  * of the down event.
647                  */
648                 final int xDiff = (int) Math.abs(x - mLastMotionX);
649                 final int yDiff = (int) Math.abs(y - mLastMotionY);
650
651                 final int touchSlop = mTouchSlop;
652                 boolean xMoved = xDiff > touchSlop;
653                 boolean yMoved = yDiff > touchSlop;
654                 
655                 if (xMoved || yMoved) {
656                     
657                     if (xMoved) {
658                         // Scroll if the user moved far enough along the X axis
659                         mTouchState = TOUCH_STATE_SCROLLING;
660                         enableChildrenCache();
661                     }
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
667                         // everything
668                         final View currentScreen = getChildAt(mCurrentScreen);
669                         currentScreen.cancelLongPress();
670                     }
671                 }
672                 break;
673
674             case MotionEvent.ACTION_DOWN:
675                 // Remember location of down touch
676                 mLastMotionX = x;
677                 mLastMotionY = y;
678                 mAllowLongPress = true;
679
680                 /*
681                  * If being flinged and user touches the screen, initiate drag;
682                  * otherwise don't.  mScroller.isFinished should be false when
683                  * being flinged.
684                  */
685                 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
686                 break;
687
688             case MotionEvent.ACTION_CANCEL:
689             case MotionEvent.ACTION_UP:
690                 // Release the drag
691                 clearChildrenCache();
692                 mTouchState = TOUCH_STATE_REST;
693                 mAllowLongPress = false;
694                 break;
695         }
696
697         /*
698          * The only time we want to intercept motion events is if we are in the
699          * drag mode.
700          */
701         return mTouchState != TOUCH_STATE_REST;
702     }
703
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);
710         }
711     }
712
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);
718         }
719     }
720
721     @Override
722     public boolean onTouchEvent(MotionEvent ev) {
723         if (mLocked || !mLauncher.isDrawerDown()) {
724             return true;
725         }
726
727         if (mVelocityTracker == null) {
728             mVelocityTracker = VelocityTracker.obtain();
729         }
730         mVelocityTracker.addMovement(ev);
731
732         final int action = ev.getAction();
733         final float x = ev.getX();
734
735         switch (action) {
736         case MotionEvent.ACTION_DOWN:
737             /*
738              * If being flinged and user touches, stop the fling. isFinished
739              * will be false if being flinged.
740              */
741             if (!mScroller.isFinished()) {
742                 mScroller.abortAnimation();
743             }
744
745             // Remember where the motion event started
746             mLastMotionX = x;
747             break;
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);
752                 mLastMotionX = x;
753
754                 if (deltaX < 0) {
755                     if (mScrollX > 0) {
756                         scrollBy(Math.max(-mScrollX, deltaX), 0);
757                     }
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);
763                     }
764                 }
765             }
766             break;
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();
772
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);
779                 } else {
780                     snapToDestination();
781                 }
782
783                 if (mVelocityTracker != null) {
784                     mVelocityTracker.recycle();
785                     mVelocityTracker = null;
786                 }
787             }
788             mTouchState = TOUCH_STATE_REST;
789             break;
790         case MotionEvent.ACTION_CANCEL:
791             mTouchState = TOUCH_STATE_REST;
792         }
793
794         return true;
795     }
796
797     private void snapToDestination() {
798         final int screenWidth = getWidth();
799         final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
800
801         snapToScreen(whichScreen);
802     }
803
804     void snapToScreen(int whichScreen) {
805         enableChildrenCache();
806
807         whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
808         boolean changingScreens = whichScreen != mCurrentScreen;
809         
810         mNextScreen = whichScreen;
811         
812         View focusedChild = getFocusedChild();
813         if (focusedChild != null && changingScreens && focusedChild == getChildAt(mCurrentScreen)) {
814             focusedChild.clearFocus();
815         }
816         
817         final int newX = whichScreen * getWidth();
818         final int delta = newX - mScrollX;
819         mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
820         invalidate();
821     }
822
823     void startDrag(CellLayout.CellInfo cellInfo) {
824         View child = cellInfo.cell;
825         
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)) {
829             return;
830         }
831         
832         mDragInfo = cellInfo;
833         mDragInfo.screen = mCurrentScreen;
834         
835         CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
836
837         current.onDragChild(child);
838         mDragger.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
839         invalidate();
840     }
841
842     @Override
843     protected Parcelable onSaveInstanceState() {
844         final SavedState state = new SavedState(super.onSaveInstanceState());
845         state.currentScreen = mCurrentScreen;
846         return state;
847     }
848
849     @Override
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);
856         }
857     }
858
859     void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo) {
860         addApplicationShortcut(info, cellInfo, false);
861     }
862
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];
867
868         layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
869         onDropExternal(result[0], result[1], info, layout, insertAtFirst);
870     }
871
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);
876         } else {
877             // Move internally
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);
884                 }
885                 cellLayout.onDropChild(cell, x - xOffset, y - yOffset);
886
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);
891             }
892         }
893     }
894
895     public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
896             Object dragInfo) {
897     }
898
899     public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
900             Object dragInfo) {
901     }
902
903     public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
904             Object dragInfo) {
905     }
906
907     private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
908         onDropExternal(x, y, dragInfo, cellLayout, false);
909     }
910     
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;
915
916         View view;
917
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);
924             }
925             view = mLauncher.createShortcut(R.layout.application, cellLayout,
926                     (ApplicationInfo) info);
927             break;
928         case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
929             view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
930                     (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
931             break;
932         default:
933             throw new IllegalStateException("Unknown item type: " + info.itemType);
934         }
935
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();
940
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);
945     }
946
947     public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
948             Object dragInfo) {
949
950         final CellLayout.CellInfo cellInfo = mDragInfo;
951         int cellHSpan = cellInfo == null ? 1 : cellInfo.spanX;
952         int cellVSpan = cellInfo == null ? 1 : cellInfo.spanY;
953
954         return ((CellLayout) getChildAt(mCurrentScreen)).acceptChildDrop(x - xOffset, y - yOffset,
955                 cellHSpan, cellVSpan, cellInfo == null ? null : cellInfo.cell);
956     }
957
958     void setLauncher(Launcher launcher) {
959         mLauncher = launcher;
960     }
961
962     public void setDragger(DragController dragger) {
963         mDragger = dragger;
964     }
965
966     public void onDropCompleted(View target, boolean success) {
967         if (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);
973             }
974         } else {
975             if (mDragInfo != null) {
976                 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
977                 cellLayout.onDropAborted(mDragInfo.cell);
978             }
979         }
980
981         mDragInfo = null;
982     }
983
984     public void scrollLeft() {
985         if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
986             snapToScreen(mCurrentScreen - 1);
987         }
988     }
989
990     public void scrollRight() {
991         if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
992                 mScroller.isFinished()) {
993             snapToScreen(mCurrentScreen + 1);
994         }
995     }
996
997     public int getScreenForView(View v) {
998         int result = -1;
999         if (v != null) {
1000             ViewParent vp = v.getParent();
1001             int count = getChildCount();
1002             for (int i = 0; i < count; i++) {
1003                 if (vp == getChildAt(i)) {
1004                     return i;
1005                 }
1006             }
1007         }
1008         return result;
1009     }
1010     
1011     /**
1012      * Find a search widget on the given screen
1013      */
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) {
1019                 return (Search) v;
1020             }
1021         }
1022         return null;
1023     }
1024     
1025     /**
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 
1028      */
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
1036             // happens:
1037             //
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
1043             //   clears its focus
1044             //
1045             // Forcing focusable in touch mode ensures the search widget will
1046             // keep the focus no matter what happens.
1047             //
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();
1053
1054             if (isInTouchMode()) {
1055                 searchWidget.requestFocusFromTouch();
1056             } else {
1057                 searchWidget.requestFocus();
1058             }
1059             searchWidget.clearQuery();
1060             return true;
1061         }
1062         return false;
1063     }
1064     
1065     /**
1066      * Snap to the nearest screen with a search widget and give it focus
1067      * 
1068      * @return True if a search widget was found
1069      */
1070     public boolean snapToSearch() {
1071         // The screen we are searching
1072         int current = mCurrentScreen;
1073         
1074         // first position scanned so far
1075         int first = current;
1076
1077         // last position scanned so far
1078         int last = current;
1079
1080         // True if we should move down on the next iteration
1081         boolean next = false;
1082
1083         // True when we have looked at the first item in the data
1084         boolean hitFirst;
1085
1086         // True when we have looked at the last item in the data
1087         boolean hitLast;
1088         
1089         final int count = getChildCount();
1090
1091         while (true) {
1092             if (focusOnSearch(current)) {
1093                 return true;
1094             }
1095
1096             hitLast = last == count - 1;
1097             hitFirst = first == 0;
1098
1099             if (hitLast && hitFirst) {
1100                 // Looked at everything
1101                 break;
1102             }
1103
1104             if (hitFirst || (next && !hitLast)) {
1105                 // Either we hit the top, or we are trying to move down
1106                 last++;
1107                 current = last;
1108                 // Try going up next time
1109                 next = false;
1110             } else {
1111                 // Either we hit the bottom, or we are trying to move up
1112                 first--;
1113                 current = first;
1114                 // Try going down next time
1115                 next = true;
1116             }
1117
1118         }
1119         return false;
1120     }
1121
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) {
1133                         return f;
1134                     }
1135                 }
1136             }
1137         }
1138         return null;
1139     }
1140
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) {
1149                     return child;
1150                 }
1151             }
1152         }
1153         return null;
1154     }
1155
1156     /**
1157      * Unlocks the SlidingDrawer so that touch events are processed.
1158      *
1159      * @see #lock()
1160      */
1161     public void unlock() {
1162         mLocked = false;
1163     }
1164
1165     /**
1166      * Locks the SlidingDrawer so that touch events are ignores.
1167      *
1168      * @see #unlock()
1169      */
1170     public void lock() {
1171         mLocked = true;
1172     }
1173     
1174     /**
1175      * @return True is long presses are still allowed for the current touch
1176      */
1177     public boolean allowLongPress() {
1178         return mAllowLongPress;
1179     }
1180
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
1196                     // web pages.)
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);
1204                     }
1205                 }
1206             }
1207             childCount = childrenToRemove.size();
1208             for (int j = 0; j < childCount; j++) {
1209                 layout.removeViewInLayout(childrenToRemove.get(j));
1210             }
1211             if (childCount > 0) {
1212                 layout.requestLayout();
1213                 layout.invalidate();
1214             }
1215         }
1216     }
1217     
1218     // TODO: remove gadgets when gadgetmanager tells us they're gone
1219 //    void removeGadgetsForProvider() {
1220 //    }
1221
1222     void moveToDefaultScreen() {
1223         snapToScreen(mDefaultScreen);
1224         getChildAt(mDefaultScreen).requestFocus();
1225     }
1226
1227     public static class SavedState extends BaseSavedState {
1228         int currentScreen = -1;
1229
1230         SavedState(Parcelable superState) {
1231             super(superState);
1232         }
1233
1234         private SavedState(Parcel in) {
1235             super(in);
1236             currentScreen = in.readInt();
1237         }
1238
1239         @Override
1240         public void writeToParcel(Parcel out, int flags) {
1241             super.writeToParcel(out, flags);
1242             out.writeInt(currentScreen);
1243         }
1244
1245         public static final Parcelable.Creator<SavedState> CREATOR =
1246                 new Parcelable.Creator<SavedState>() {
1247             public SavedState createFromParcel(Parcel in) {
1248                 return new SavedState(in);
1249             }
1250
1251             public SavedState[] newArray(int size) {
1252                 return new SavedState[size];
1253             }
1254         };
1255     }
1256 }