OSDN Git Service

auto import from //depot/cupcake/@132589
[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.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;
36
37 import java.util.ArrayList;
38
39 /**
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.
43  */
44 public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
45     private static final int INVALID_SCREEN = -1;
46
47     /**
48      * The velocity at which a fling gesture will cause us to snap to the next screen
49      */
50     private static final int SNAP_VELOCITY = 1000;
51
52     private int mDefaultScreen;
53
54     private Paint mPaint;
55     private Bitmap mWallpaper;
56
57     private int mWallpaperWidth;
58     private int mWallpaperHeight;
59     private float mWallpaperOffset;
60     private boolean mWallpaperLoaded;
61
62     private boolean mFirstLayout = true;
63
64     private int mCurrentScreen;
65     private int mNextScreen = INVALID_SCREEN;
66     private Scroller mScroller;
67     private VelocityTracker mVelocityTracker;
68
69     /**
70      * CellInfo for the cell that is currently being dragged
71      */
72     private CellLayout.CellInfo mDragInfo;
73
74     private float mLastMotionX;
75     private float mLastMotionY;
76
77     private final static int TOUCH_STATE_REST = 0;
78     private final static int TOUCH_STATE_SCROLLING = 1;
79
80     private int mTouchState = TOUCH_STATE_REST;
81
82     private OnLongClickListener mLongClickListener;
83
84     private Launcher mLauncher;
85     private DragController mDragger;
86
87     private int[] mTempCell = new int[2];
88
89     private boolean mAllowLongPress;
90     private boolean mLocked;
91
92     private int mTouchSlop;
93
94     /**
95      * Used to inflate the Workspace from XML.
96      *
97      * @param context The application's context.
98      * @param attrs The attribtues set containing the Workspace's customization values.
99      */
100     public Workspace(Context context, AttributeSet attrs) {
101         this(context, attrs, 0);
102     }
103
104     /**
105      * Used to inflate the Workspace from XML.
106      *
107      * @param context The application's context.
108      * @param attrs The attribtues set containing the Workspace's customization values.
109      * @param defStyle Unused.
110      */
111     public Workspace(Context context, AttributeSet attrs, int defStyle) {
112         super(context, attrs, defStyle);
113
114         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
115         mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
116         a.recycle();
117
118         initWorkspace();
119     }
120
121     /**
122      * Initializes various states for this workspace.
123      */
124     private void initWorkspace() {
125         mScroller = new Scroller(getContext());
126         mCurrentScreen = mDefaultScreen;
127         Launcher.setScreen(mCurrentScreen);
128
129         mPaint = new Paint();
130         mPaint.setDither(false);
131
132         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
133     }
134
135     /**
136      * Set the background's wallpaper.
137      */
138     void loadWallpaper(Bitmap bitmap) {
139         mWallpaper = bitmap;
140         mWallpaperLoaded = true;
141         requestLayout();
142         invalidate();
143     }
144
145     @Override
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.");
149         }
150         super.addView(child, index, params);
151     }
152
153     @Override
154     public void addView(View child) {
155         if (!(child instanceof CellLayout)) {
156             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
157         }
158         super.addView(child);
159     }
160
161     @Override
162     public void addView(View child, int index) {
163         if (!(child instanceof CellLayout)) {
164             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
165         }
166         super.addView(child, index);
167     }
168
169     @Override
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.");
173         }
174         super.addView(child, width, height);
175     }
176
177     @Override
178     public void addView(View child, LayoutParams params) {
179         if (!(child instanceof CellLayout)) {
180             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
181         }
182         super.addView(child, params);
183     }
184
185     /**
186      * @return The open folder on the current screen, or null if there is none
187      */
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;
196             }
197         }
198         return null;
199     }
200
201     ArrayList<Folder> getOpenFolders() {
202         final int screens = getChildCount();
203         ArrayList<Folder> folders = new ArrayList<Folder>(screens);
204
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);
213                     break;
214                 }
215             }
216         }
217
218         return folders;
219     }
220
221     boolean isDefaultScreenShowing() {
222         return mCurrentScreen == mDefaultScreen;
223     }
224
225     /**
226      * Returns the index of the currently displayed screen.
227      *
228      * @return The index of the currently displayed screen.
229      */
230     int getCurrentScreen() {
231         return mCurrentScreen;
232     }
233
234     /**
235      * Computes a bounding rectangle for a range of cells
236      *
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
242      */
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);
246     }
247
248     /**
249      * Sets the current screen.
250      *
251      * @param currentScreen
252      */
253     void setCurrentScreen(int currentScreen) {
254         mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
255         scrollTo(mCurrentScreen * getWidth(), 0);
256         invalidate();
257     }
258
259     /**
260      * Shows the default screen (defined by the firstScreen attribute in XML.)
261      */
262     void showDefaultScreen() {
263         setCurrentScreen(mDefaultScreen);
264     }
265
266     /**
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.
269      *
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.
275      */
276     void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
277         addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
278     }
279
280     /**
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.
283      *
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.
290      */
291     void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
292         addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
293     }
294
295     /**
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.
298      *
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.
305      */
306     void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
307         addInScreen(child, screen, x, y, spanX, spanY, false);
308     }
309
310     /**
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.
313      *
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.
321      */
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());
325         }
326
327         final CellLayout group = (CellLayout) getChildAt(screen);
328         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
329         if (lp == null) {
330             lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
331         } else {
332             lp.cellX = x;
333             lp.cellY = y;
334             lp.cellHSpan = spanX;
335             lp.cellVSpan = spanY;
336         }
337         group.addView(child, insert ? 0 : -1, lp);
338         if (!(child instanceof Folder)) {
339             child.setOnLongClickListener(mLongClickListener);
340         }
341     }
342
343     void addWidget(View view, Widget widget) {
344         addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
345                 widget.spanY, false);
346     }
347
348     void addWidget(View view, Widget widget, boolean insert) {
349         addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
350                 widget.spanY, insert);
351     }
352
353     CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
354         CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
355         if (group != null) {
356             return group.findAllVacantCells(occupied);
357         }
358         return null;
359     }
360
361     /**
362      * Returns the coordinate of a vacant cell for the current screen.
363      */
364     boolean getVacantCell(int[] vacant, int spanX, int spanY) {
365         CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
366         if (group != null) {
367             return group.getVacantCell(vacant, spanX, spanY);
368         }
369         return false;
370     }
371
372     /**
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.
375      *
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.
379      */
380     void fitInCurrentScreen(View child, int spanX, int spanY) {
381         fitInScreen(child, mCurrentScreen, spanX, spanY);
382     }
383
384     /**
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.
387      *
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.
392      */
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());
396         }
397
398         final CellLayout group = (CellLayout) getChildAt(screen);
399         boolean vacant = group.getVacantCell(mTempCell, spanX, spanY);
400         if (vacant) {
401             group.addView(child,
402                     new CellLayout.LayoutParams(mTempCell[0], mTempCell[1], spanX, spanY));
403             child.setOnLongClickListener(mLongClickListener);
404             if (!(child instanceof Folder)) {
405                 child.setOnLongClickListener(mLongClickListener);
406             }
407         }
408     }
409
410     /**
411      * Registers the specified listener on each screen contained in this workspace.
412      *
413      * @param l The listener used to respond to long clicks.
414      */
415     @Override
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);
421         }
422     }
423
424     @Override
425     public void computeScroll() {
426         if (mScroller.computeScrollOffset()) {
427             mScrollX = mScroller.getCurrX();
428             mScrollY = mScroller.getCurrY();
429             postInvalidate();
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();
435         }
436     }
437
438     @Override
439     protected void dispatchDraw(Canvas canvas) {
440         float x = mScrollX * mWallpaperOffset;
441         if (x + mWallpaperWidth < mRight - mLeft) {
442             x = mRight - mLeft - mWallpaperWidth;
443         }
444
445         canvas.drawBitmap(mWallpaper, x, (mBottom - mTop - mWallpaperHeight) / 2, mPaint);
446
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.
451
452         boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
453         // If we are not scrolling or flinging, draw only the current screen
454         if (fastDraw) {
455             drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
456         } else {
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);
463             } else {
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);
468                 }
469             }
470         }
471     }
472
473     @Override
474     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
475         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
476
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.");
481         }
482
483         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
484         if (heightMode != MeasureSpec.EXACTLY) {
485             throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
486         }
487
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);
492         }
493
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();
500         }
501
502         final int wallpaperWidth = mWallpaperWidth;
503         mWallpaperOffset = wallpaperWidth > width ? (count * width - wallpaperWidth) /
504                 ((count - 1) * (float) width) : 1.0f;
505
506         if (mFirstLayout) {
507             scrollTo(mCurrentScreen * width, 0);
508             mFirstLayout = false;
509         }
510     }
511
512     @Override
513     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
514         int childLeft = 0;
515
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;
523             }
524         }
525     }
526
527     @Override
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);
533             }
534             return true;
535         }
536         return false;
537     }
538
539     @Override
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);
545             } else {
546                 int focusableScreen;
547                 if (mNextScreen != INVALID_SCREEN) {
548                     focusableScreen = mNextScreen;
549                 } else {
550                     focusableScreen = mCurrentScreen;
551                 }
552                 getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
553             }
554         }
555         return false;
556     }
557
558     @Override
559     public boolean dispatchUnhandledMove(View focused, int direction) {
560         if (direction == View.FOCUS_LEFT) {
561             if (getCurrentScreen() > 0) {
562                 snapToScreen(getCurrentScreen() - 1);
563                 return true;
564             }
565         } else if (direction == View.FOCUS_RIGHT) {
566             if (getCurrentScreen() < getChildCount() - 1) {
567                 snapToScreen(getCurrentScreen() + 1);
568                 return true;
569             }
570         }
571         return super.dispatchUnhandledMove(focused, direction);
572     }
573
574     @Override
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);
583                     }
584                 } else if (direction == View.FOCUS_RIGHT){
585                     if (mCurrentScreen < getChildCount() - 1) {
586                         getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
587                     }
588                 }
589             } else {
590                 openFolder.addFocusables(views, direction);
591             }
592         }
593     }
594
595     @Override
596     public boolean onInterceptTouchEvent(MotionEvent ev) {
597         if (mLocked || !mLauncher.isDrawerDown()) {
598             return true;
599         }
600
601         /*
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
604          * scrolling there.
605          */
606
607         /*
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
610          * motion.
611          */
612         final int action = ev.getAction();
613         if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
614             return true;
615         }
616
617         final float x = ev.getX();
618         final float y = ev.getY();
619
620         switch (action) {
621             case MotionEvent.ACTION_MOVE:
622                 /*
623                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
624                  * whether the user has moved far enough from his original down touch.
625                  */
626
627                 /*
628                  * Locally do absolute value. mLastMotionX is set to the y value
629                  * of the down event.
630                  */
631                 final int xDiff = (int) Math.abs(x - mLastMotionX);
632                 final int yDiff = (int) Math.abs(y - mLastMotionY);
633
634                 final int touchSlop = mTouchSlop;
635                 boolean xMoved = xDiff > touchSlop;
636                 boolean yMoved = yDiff > touchSlop;
637                 
638                 if (xMoved || yMoved) {
639                     
640                     if (xMoved) {
641                         // Scroll if the user moved far enough along the X axis
642                         mTouchState = TOUCH_STATE_SCROLLING;
643                         enableChildrenCache();
644                     }
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
650                         // everything
651                         final View currentScreen = getChildAt(mCurrentScreen);
652                         currentScreen.cancelLongPress();
653                     }
654                 }
655                 break;
656
657             case MotionEvent.ACTION_DOWN:
658                 // Remember location of down touch
659                 mLastMotionX = x;
660                 mLastMotionY = y;
661                 mAllowLongPress = true;
662
663                 /*
664                  * If being flinged and user touches the screen, initiate drag;
665                  * otherwise don't.  mScroller.isFinished should be false when
666                  * being flinged.
667                  */
668                 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
669                 break;
670
671             case MotionEvent.ACTION_CANCEL:
672             case MotionEvent.ACTION_UP:
673                 // Release the drag
674                 clearChildrenCache();
675                 mTouchState = TOUCH_STATE_REST;
676                 break;
677         }
678
679         /*
680          * The only time we want to intercept motion events is if we are in the
681          * drag mode.
682          */
683         return mTouchState != TOUCH_STATE_REST;
684     }
685
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);
692         }
693     }
694
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);
700         }
701     }
702
703     @Override
704     public boolean onTouchEvent(MotionEvent ev) {
705         if (mLocked || !mLauncher.isDrawerDown()) {
706             return true;
707         }
708
709         if (mVelocityTracker == null) {
710             mVelocityTracker = VelocityTracker.obtain();
711         }
712         mVelocityTracker.addMovement(ev);
713
714         final int action = ev.getAction();
715         final float x = ev.getX();
716
717         switch (action) {
718         case MotionEvent.ACTION_DOWN:
719             /*
720              * If being flinged and user touches, stop the fling. isFinished
721              * will be false if being flinged.
722              */
723             if (!mScroller.isFinished()) {
724                 mScroller.abortAnimation();
725             }
726
727             // Remember where the motion event started
728             mLastMotionX = x;
729             break;
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);
734                 mLastMotionX = x;
735
736                 if (deltaX < 0) {
737                     if (mScrollX > 0) {
738                         scrollBy(Math.max(-mScrollX, deltaX), 0);
739                     }
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);
745                     }
746                 }
747             }
748             break;
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();
754
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);
761                 } else {
762                     snapToDestination();
763                 }
764
765                 if (mVelocityTracker != null) {
766                     mVelocityTracker.recycle();
767                     mVelocityTracker = null;
768                 }
769             }
770             mTouchState = TOUCH_STATE_REST;
771             break;
772         case MotionEvent.ACTION_CANCEL:
773             mTouchState = TOUCH_STATE_REST;
774         }
775
776         return true;
777     }
778
779     private void snapToDestination() {
780         final int screenWidth = getWidth();
781         final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
782
783         snapToScreen(whichScreen);
784     }
785
786     void snapToScreen(int whichScreen) {
787         enableChildrenCache();
788
789         whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
790         boolean changingScreens = whichScreen != mCurrentScreen;
791         
792         mNextScreen = whichScreen;
793         
794         View focusedChild = getFocusedChild();
795         if (focusedChild != null && changingScreens && focusedChild == getChildAt(mCurrentScreen)) {
796             focusedChild.clearFocus();
797         }
798         
799         final int newX = whichScreen * getWidth();
800         final int delta = newX - mScrollX;
801         mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
802         invalidate();
803     }
804
805     void startDrag(CellLayout.CellInfo cellInfo) {
806         View child = cellInfo.cell;
807         
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)) {
811             return;
812         }
813         
814         mDragInfo = cellInfo;
815         mDragInfo.screen = mCurrentScreen;
816         
817         CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
818
819         current.onDragChild(child);
820         mDragger.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
821         invalidate();
822     }
823
824     @Override
825     protected Parcelable onSaveInstanceState() {
826         final SavedState state = new SavedState(super.onSaveInstanceState());
827         state.currentScreen = mCurrentScreen;
828         return state;
829     }
830
831     @Override
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);
838         }
839     }
840
841     void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo) {
842         addApplicationShortcut(info, cellInfo, false);
843     }
844
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];
849
850         layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
851         onDropExternal(result[0], result[1], info, layout, insertAtFirst);
852     }
853
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);
858         } else {
859             // Move internally
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);
866                 }
867                 cellLayout.onDropChild(cell, x - xOffset, y - yOffset);
868
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);
873             }
874         }
875     }
876
877     public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
878             Object dragInfo) {
879     }
880
881     public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
882             Object dragInfo) {
883     }
884
885     public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
886             Object dragInfo) {
887     }
888
889     private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
890         onDropExternal(x, y, dragInfo, cellLayout, false);
891     }
892     
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;
897
898         View view;
899
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);
906             }
907             view = mLauncher.createShortcut(R.layout.application, cellLayout,
908                     (ApplicationInfo) info);
909             break;
910         case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
911             view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
912                     (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
913             break;
914         default:
915             throw new IllegalStateException("Unknown item type: " + info.itemType);
916         }
917
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();
922
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);
927     }
928
929     public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
930             Object dragInfo) {
931
932         final CellLayout.CellInfo cellInfo = mDragInfo;
933         int cellHSpan = cellInfo == null ? 1 : cellInfo.spanX;
934         int cellVSpan = cellInfo == null ? 1 : cellInfo.spanY;
935
936         return ((CellLayout) getChildAt(mCurrentScreen)).acceptChildDrop(x - xOffset, y - yOffset,
937                 cellHSpan, cellVSpan, cellInfo == null ? null : cellInfo.cell);
938     }
939
940     void setLauncher(Launcher launcher) {
941         mLauncher = launcher;
942     }
943
944     public void setDragger(DragController dragger) {
945         mDragger = dragger;
946     }
947
948     public void onDropCompleted(View target, boolean success) {
949         if (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);
955             }
956         } else {
957             if (mDragInfo != null) {
958                 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
959                 cellLayout.onDropAborted(mDragInfo.cell);
960             }
961         }
962
963         mDragInfo = null;
964     }
965
966     public void scrollLeft() {
967         if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
968             snapToScreen(mCurrentScreen - 1);
969         }
970     }
971
972     public void scrollRight() {
973         if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
974                 mScroller.isFinished()) {
975             snapToScreen(mCurrentScreen + 1);
976         }
977     }
978
979     public int getScreenForView(View v) {
980         int result = -1;
981         if (v != null) {
982             ViewParent vp = v.getParent();
983             int count = getChildCount();
984             for (int i = 0; i < count; i++) {
985                 if (vp == getChildAt(i)) {
986                     return i;
987                 }
988             }
989         }
990         return result;
991     }
992     
993     /**
994      * Find a search widget on the given screen
995      */
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) {
1001                 return v;
1002             }
1003         }
1004         return null;
1005     }
1006     
1007     /**
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 
1010      */
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();
1017             } else {
1018                 searchWidget.requestFocus();
1019             }
1020             searchWidget.clearQuery();
1021             return true;
1022         }
1023         return false;
1024     }
1025     
1026     /**
1027      * Snap to the nearest screen with a search widget and give it focus
1028      * 
1029      * @return True if a search widget was found
1030      */
1031     public boolean snapToSearch() {
1032         // The screen we are searching
1033         int current = mCurrentScreen;
1034         
1035         // first position scanned so far
1036         int first = current;
1037
1038         // last position scanned so far
1039         int last = current;
1040
1041         // True if we should move down on the next iteration
1042         boolean next = false;
1043
1044         // True when we have looked at the first item in the data
1045         boolean hitFirst;
1046
1047         // True when we have looked at the last item in the data
1048         boolean hitLast;
1049         
1050         final int count = getChildCount();
1051
1052         while (true) {
1053             if (focusOnSearch(current)) {
1054                 return true;
1055             }
1056
1057             hitLast = last == count - 1;
1058             hitFirst = first == 0;
1059
1060             if (hitLast && hitFirst) {
1061                 // Looked at everything
1062                 break;
1063             }
1064
1065             if (hitFirst || (next && !hitLast)) {
1066                 // Either we hit the top, or we are trying to move down
1067                 last++;
1068                 current = last;
1069                 // Try going up next time
1070                 next = false;
1071             } else {
1072                 // Either we hit the bottom, or we are trying to move up
1073                 first--;
1074                 current = first;
1075                 // Try going down next time
1076                 next = true;
1077             }
1078
1079         }
1080         return false;
1081     }
1082
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) {
1094                         return f;
1095                     }
1096                 }
1097             }
1098         }
1099         return null;
1100     }
1101
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) {
1110                     return child;
1111                 }
1112             }
1113         }
1114         return null;
1115     }
1116
1117     /**
1118      * Unlocks the SlidingDrawer so that touch events are processed.
1119      *
1120      * @see #lock()
1121      */
1122     public void unlock() {
1123         mLocked = false;
1124     }
1125
1126     /**
1127      * Locks the SlidingDrawer so that touch events are ignores.
1128      *
1129      * @see #unlock()
1130      */
1131     public void lock() {
1132         mLocked = true;
1133     }
1134     
1135     /**
1136      * @return True is long presses are still allowed for the current touch
1137      */
1138     public boolean allowLongPress() {
1139         return mAllowLongPress;
1140     }
1141
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);
1159                     }
1160                 }
1161             }
1162             childCount = childrenToRemove.size();
1163             for (int j = 0; j < childCount; j++) {
1164                 layout.removeViewInLayout(childrenToRemove.get(j));
1165             }
1166             if (childCount > 0) {
1167                 layout.requestLayout();
1168                 layout.invalidate();
1169             }
1170         }
1171     }
1172     
1173     // TODO: remove gadgets when gadgetmanager tells us they're gone
1174 //    void removeGadgetsForProvider() {
1175 //    }
1176
1177     void moveToDefaultScreen() {
1178         snapToScreen(mDefaultScreen);
1179         getChildAt(mDefaultScreen).requestFocus();
1180     }
1181
1182     public static class SavedState extends BaseSavedState {
1183         int currentScreen = -1;
1184
1185         SavedState(Parcelable superState) {
1186             super(superState);
1187         }
1188
1189         private SavedState(Parcel in) {
1190             super(in);
1191             currentScreen = in.readInt();
1192         }
1193
1194         @Override
1195         public void writeToParcel(Parcel out, int flags) {
1196             super.writeToParcel(out, flags);
1197             out.writeInt(currentScreen);
1198         }
1199
1200         public static final Parcelable.Creator<SavedState> CREATOR =
1201                 new Parcelable.Creator<SavedState>() {
1202             public SavedState createFromParcel(Parcel in) {
1203                 return new SavedState(in);
1204             }
1205
1206             public SavedState[] newArray(int size) {
1207                 return new SavedState[size];
1208             }
1209         };
1210     }
1211 }