• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.launcher2;
18 
19 import android.app.WallpaperManager;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ComponentName;
23 import android.content.res.TypedArray;
24 import android.content.pm.PackageManager;
25 import android.graphics.Canvas;
26 import android.graphics.RectF;
27 import android.graphics.Rect;
28 import android.graphics.drawable.Drawable;
29 import android.os.Parcelable;
30 import android.os.Parcel;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.view.MotionEvent;
34 import android.view.VelocityTracker;
35 import android.view.View;
36 import android.view.ViewConfiguration;
37 import android.view.ViewGroup;
38 import android.view.ViewParent;
39 import android.widget.Scroller;
40 import android.widget.TextView;
41 
42 import java.util.ArrayList;
43 
44 /**
45  * The workspace is a wide area with a wallpaper and a finite number of screens. Each
46  * screen contains a number of icons, folders or widgets the user can interact with.
47  * A workspace is meant to be used with a fixed width only.
48  */
49 public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
50     @SuppressWarnings({"UnusedDeclaration"})
51     private static final String TAG = "Launcher.Workspace";
52     private static final int INVALID_SCREEN = -1;
53 
54     /**
55      * The velocity at which a fling gesture will cause us to snap to the next screen
56      */
57     private static final int SNAP_VELOCITY = 1000;
58 
59     private final WallpaperManager mWallpaperManager;
60 
61     private int mDefaultScreen;
62 
63     private boolean mFirstLayout = true;
64 
65     private int mCurrentScreen;
66     private int mNextScreen = INVALID_SCREEN;
67     private Scroller mScroller;
68     private VelocityTracker mVelocityTracker;
69 
70     /**
71      * CellInfo for the cell that is currently being dragged
72      */
73     private CellLayout.CellInfo mDragInfo;
74 
75     /**
76      * Target drop area calculated during last acceptDrop call.
77      */
78     private int[] mTargetCell = null;
79 
80     private float mLastMotionX;
81     private float mLastMotionY;
82 
83     private final static int TOUCH_STATE_REST = 0;
84     private final static int TOUCH_STATE_SCROLLING = 1;
85 
86     private int mTouchState = TOUCH_STATE_REST;
87 
88     private OnLongClickListener mLongClickListener;
89 
90     private Launcher mLauncher;
91     private DragController mDragController;
92 
93     /**
94      * Cache of vacant cells, used during drag events and invalidated as needed.
95      */
96     private CellLayout.CellInfo mVacantCache = null;
97 
98     private int[] mTempCell = new int[2];
99     private int[] mTempEstimate = new int[2];
100 
101     private boolean mAllowLongPress = true;
102 
103     private int mTouchSlop;
104     private int mMaximumVelocity;
105 
106     final Rect mDrawerBounds = new Rect();
107     final Rect mClipBounds = new Rect();
108     int mDrawerContentHeight;
109     int mDrawerContentWidth;
110 
111     private Drawable mPreviousIndicator;
112     private Drawable mNextIndicator;
113 
114     private boolean mFading = true;
115 
116     /**
117      * Used to inflate the Workspace from XML.
118      *
119      * @param context The application's context.
120      * @param attrs The attribtues set containing the Workspace's customization values.
121      */
Workspace(Context context, AttributeSet attrs)122     public Workspace(Context context, AttributeSet attrs) {
123         this(context, attrs, 0);
124     }
125 
126     /**
127      * Used to inflate the Workspace from XML.
128      *
129      * @param context The application's context.
130      * @param attrs The attribtues set containing the Workspace's customization values.
131      * @param defStyle Unused.
132      */
Workspace(Context context, AttributeSet attrs, int defStyle)133     public Workspace(Context context, AttributeSet attrs, int defStyle) {
134         super(context, attrs, defStyle);
135 
136         mWallpaperManager = WallpaperManager.getInstance(context);
137 
138         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
139         mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
140         a.recycle();
141 
142         initWorkspace();
143     }
144 
145     /**
146      * Initializes various states for this workspace.
147      */
initWorkspace()148     private void initWorkspace() {
149         mScroller = new Scroller(getContext());
150         mCurrentScreen = mDefaultScreen;
151         Launcher.setScreen(mCurrentScreen);
152 
153         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
154         mTouchSlop = configuration.getScaledTouchSlop();
155         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
156     }
157 
158     @Override
addView(View child, int index, LayoutParams params)159     public void addView(View child, int index, LayoutParams params) {
160         if (!(child instanceof CellLayout)) {
161             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
162         }
163         super.addView(child, index, params);
164     }
165 
166     @Override
addView(View child)167     public void addView(View child) {
168         if (!(child instanceof CellLayout)) {
169             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
170         }
171         super.addView(child);
172     }
173 
174     @Override
addView(View child, int index)175     public void addView(View child, int index) {
176         if (!(child instanceof CellLayout)) {
177             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
178         }
179         super.addView(child, index);
180     }
181 
182     @Override
addView(View child, int width, int height)183     public void addView(View child, int width, int height) {
184         if (!(child instanceof CellLayout)) {
185             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
186         }
187         super.addView(child, width, height);
188     }
189 
190     @Override
addView(View child, LayoutParams params)191     public void addView(View child, LayoutParams params) {
192         if (!(child instanceof CellLayout)) {
193             throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
194         }
195         super.addView(child, params);
196     }
197 
198     /**
199      * @return The open folder on the current screen, or null if there is none
200      */
getOpenFolder()201     Folder getOpenFolder() {
202         CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
203         int count = currentScreen.getChildCount();
204         for (int i = 0; i < count; i++) {
205             View child = currentScreen.getChildAt(i);
206             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
207             if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
208                 return (Folder) child;
209             }
210         }
211         return null;
212     }
213 
getOpenFolders()214     ArrayList<Folder> getOpenFolders() {
215         final int screens = getChildCount();
216         ArrayList<Folder> folders = new ArrayList<Folder>(screens);
217 
218         for (int screen = 0; screen < screens; screen++) {
219             CellLayout currentScreen = (CellLayout) getChildAt(screen);
220             int count = currentScreen.getChildCount();
221             for (int i = 0; i < count; i++) {
222                 View child = currentScreen.getChildAt(i);
223                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
224                 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
225                     folders.add((Folder) child);
226                     break;
227                 }
228             }
229         }
230 
231         return folders;
232     }
233 
isDefaultScreenShowing()234     boolean isDefaultScreenShowing() {
235         return mCurrentScreen == mDefaultScreen;
236     }
237 
238     /**
239      * Returns the index of the currently displayed screen.
240      *
241      * @return The index of the currently displayed screen.
242      */
getCurrentScreen()243     int getCurrentScreen() {
244         return mCurrentScreen;
245     }
246 
247     /**
248      * Returns how many screens there are.
249      */
getScreenCount()250     int getScreenCount() {
251         return getChildCount();
252     }
253 
254     /**
255      * Computes a bounding rectangle for a range of cells
256      *
257      * @param cellX X coordinate of upper left corner expressed as a cell position
258      * @param cellY Y coordinate of upper left corner expressed as a cell position
259      * @param cellHSpan Width in cells
260      * @param cellVSpan Height in cells
261      * @param rect Rectnagle into which to put the results
262      */
cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF rect)263     public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF rect) {
264         ((CellLayout)getChildAt(mCurrentScreen)).cellToRect(cellX, cellY,
265                 cellHSpan, cellVSpan, rect);
266     }
267 
268     /**
269      * Sets the current screen.
270      *
271      * @param currentScreen
272      */
setCurrentScreen(int currentScreen)273     void setCurrentScreen(int currentScreen) {
274         clearVacantCache();
275         mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
276         scrollTo(mCurrentScreen * getWidth(), 0);
277         invalidate();
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      */
addInCurrentScreen(View child, int x, int y, int spanX, int spanY)290     void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
291         addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
292     }
293 
294     /**
295      * Adds the specified child in the current screen. The position and dimension of
296      * the child are defined by x, y, spanX and spanY.
297      *
298      * @param child The child to add in one of the workspace's screens.
299      * @param x The X position of the child in the screen's grid.
300      * @param y The Y position of the child in the screen's grid.
301      * @param spanX The number of cells spanned horizontally by the child.
302      * @param spanY The number of cells spanned vertically by the child.
303      * @param insert When true, the child is inserted at the beginning of the children list.
304      */
addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert)305     void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
306         addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
307     }
308 
309     /**
310      * Adds the specified child in the specified screen. The position and dimension of
311      * the child are defined by x, y, spanX and spanY.
312      *
313      * @param child The child to add in one of the workspace's screens.
314      * @param screen The screen in which to add the child.
315      * @param x The X position of the child in the screen's grid.
316      * @param y The Y position of the child in the screen's grid.
317      * @param spanX The number of cells spanned horizontally by the child.
318      * @param spanY The number of cells spanned vertically by the child.
319      */
addInScreen(View child, int screen, int x, int y, int spanX, int spanY)320     void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
321         addInScreen(child, screen, x, y, spanX, spanY, false);
322     }
323 
324     /**
325      * Adds the specified child in the specified screen. The position and dimension of
326      * the child are defined by x, y, spanX and spanY.
327      *
328      * @param child The child to add in one of the workspace's screens.
329      * @param screen The screen in which to add the child.
330      * @param x The X position of the child in the screen's grid.
331      * @param y The Y position of the child in the screen's grid.
332      * @param spanX The number of cells spanned horizontally by the child.
333      * @param spanY The number of cells spanned vertically by the child.
334      * @param insert When true, the child is inserted at the beginning of the children list.
335      */
addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert)336     void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
337         if (screen < 0 || screen >= getChildCount()) {
338             throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
339         }
340 
341         clearVacantCache();
342 
343         final CellLayout group = (CellLayout) getChildAt(screen);
344         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
345         if (lp == null) {
346             lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
347         } else {
348             lp.cellX = x;
349             lp.cellY = y;
350             lp.cellHSpan = spanX;
351             lp.cellVSpan = spanY;
352         }
353         group.addView(child, insert ? 0 : -1, lp);
354         if (!(child instanceof Folder)) {
355             child.setOnLongClickListener(mLongClickListener);
356         }
357         if (child instanceof DropTarget) {
358             mDragController.addDropTarget((DropTarget)child);
359         }
360     }
361 
addWidget(View view, Widget widget)362     void addWidget(View view, Widget widget) {
363         addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
364                 widget.spanY, false);
365     }
366 
addWidget(View view, Widget widget, boolean insert)367     void addWidget(View view, Widget widget, boolean insert) {
368         addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
369                 widget.spanY, insert);
370     }
371 
findAllVacantCells(boolean[] occupied)372     CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
373         CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
374         if (group != null) {
375             return group.findAllVacantCells(occupied, null);
376         }
377         return null;
378     }
379 
clearVacantCache()380     private void clearVacantCache() {
381         if (mVacantCache != null) {
382             mVacantCache.clearVacantCells();
383             mVacantCache = null;
384         }
385     }
386 
387     /**
388      * Returns the coordinate of a vacant cell for the current screen.
389      */
getVacantCell(int[] vacant, int spanX, int spanY)390     boolean getVacantCell(int[] vacant, int spanX, int spanY) {
391         CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
392         if (group != null) {
393             return group.getVacantCell(vacant, spanX, spanY);
394         }
395         return false;
396     }
397 
398     /**
399      * Adds the specified child in the current screen. The position and dimension of
400      * the child are defined by x, y, spanX and spanY.
401      *
402      * @param child The child to add in one of the workspace's screens.
403      * @param spanX The number of cells spanned horizontally by the child.
404      * @param spanY The number of cells spanned vertically by the child.
405      */
fitInCurrentScreen(View child, int spanX, int spanY)406     void fitInCurrentScreen(View child, int spanX, int spanY) {
407         fitInScreen(child, mCurrentScreen, spanX, spanY);
408     }
409 
410     /**
411      * Adds the specified child in the specified screen. The position and dimension of
412      * the child are defined by x, y, spanX and spanY.
413      *
414      * @param child The child to add in one of the workspace's screens.
415      * @param screen The screen in which to add the child.
416      * @param spanX The number of cells spanned horizontally by the child.
417      * @param spanY The number of cells spanned vertically by the child.
418      */
fitInScreen(View child, int screen, int spanX, int spanY)419     void fitInScreen(View child, int screen, int spanX, int spanY) {
420         if (screen < 0 || screen >= getChildCount()) {
421             throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
422         }
423 
424         final CellLayout group = (CellLayout) getChildAt(screen);
425         boolean vacant = group.getVacantCell(mTempCell, spanX, spanY);
426         if (vacant) {
427             group.addView(child,
428                     new CellLayout.LayoutParams(mTempCell[0], mTempCell[1], spanX, spanY));
429             child.setOnLongClickListener(mLongClickListener);
430             if (!(child instanceof Folder)) {
431                 child.setOnLongClickListener(mLongClickListener);
432             }
433             if (child instanceof DropTarget) {
434                 mDragController.addDropTarget((DropTarget)child);
435             }
436         }
437     }
438 
439     /**
440      * Registers the specified listener on each screen contained in this workspace.
441      *
442      * @param l The listener used to respond to long clicks.
443      */
444     @Override
setOnLongClickListener(OnLongClickListener l)445     public void setOnLongClickListener(OnLongClickListener l) {
446         mLongClickListener = l;
447         final int count = getChildCount();
448         for (int i = 0; i < count; i++) {
449             getChildAt(i).setOnLongClickListener(l);
450         }
451     }
452 
updateWallpaperOffset()453     private void updateWallpaperOffset() {
454         updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
455     }
456 
updateWallpaperOffset(int scrollRange)457     private void updateWallpaperOffset(int scrollRange) {
458         mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 0 );
459         mWallpaperManager.setWallpaperOffsets(getWindowToken(), mScrollX / (float) scrollRange, 0);
460     }
461 
462     @Override
computeScroll()463     public void computeScroll() {
464         if (mScroller.computeScrollOffset()) {
465             mScrollX = mScroller.getCurrX();
466             mScrollY = mScroller.getCurrY();
467             updateWallpaperOffset();
468             postInvalidate();
469         } else if (mNextScreen != INVALID_SCREEN) {
470             mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
471             mPreviousIndicator.setLevel(mCurrentScreen);
472             mNextIndicator.setLevel(mCurrentScreen);
473             Launcher.setScreen(mCurrentScreen);
474             mNextScreen = INVALID_SCREEN;
475             clearChildrenCache();
476         }
477     }
478 
startFading(boolean dest)479     public void startFading(boolean dest) {
480         mFading = dest;
481         invalidate();
482     }
483 
484     @Override
dispatchDraw(Canvas canvas)485     protected void dispatchDraw(Canvas canvas) {
486         /*
487         final boolean allAppsOpaque = mLauncher.isAllAppsOpaque();
488         if (mFading == allAppsOpaque) {
489             invalidate();
490         } else {
491             mFading = !allAppsOpaque;
492         }
493         if (allAppsOpaque) {
494             // If the launcher is up, draw black.
495             canvas.drawARGB(0xff, 0, 0, 0);
496             return;
497         }
498         */
499 
500         boolean restore = false;
501         int restoreCount = 0;
502 
503         // For the fade.  If view gets setAlpha(), use that instead.
504         float scale = mScale;
505         if (scale < 0.999f) {
506             int sx = mScrollX;
507 
508             int alpha = (scale < 0.5f) ? (int)(255 * 2 * scale) : 255;
509 
510             restoreCount = canvas.saveLayerAlpha(sx, 0, sx+getWidth(), getHeight(), alpha,
511                     Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
512             restore = true;
513 
514             if (scale < 0.999f) {
515                 int w = getWidth();
516                 w += 2 * mCurrentScreen * w;
517                 int dx = w/2;
518                 int h = getHeight();
519                 int dy = (h/2) - (h/4);
520                 canvas.translate(dx, dy);
521                 canvas.scale(scale, scale);
522                 canvas.translate(-dx, -dy);
523             }
524         }
525 
526         // ViewGroup.dispatchDraw() supports many features we don't need:
527         // clip to padding, layout animation, animation listener, disappearing
528         // children, etc. The following implementation attempts to fast-track
529         // the drawing dispatch by drawing only what we know needs to be drawn.
530 
531         boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN
532                 && scale > 0.999f;
533         // If we are not scrolling or flinging, draw only the current screen
534         if (fastDraw) {
535             drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
536         } else {
537             final long drawingTime = getDrawingTime();
538             // If we are flinging, draw only the current screen and the target screen
539             if (mNextScreen >= 0 && mNextScreen < getChildCount() &&
540                     Math.abs(mCurrentScreen - mNextScreen) == 1) {
541                 drawChild(canvas, getChildAt(mCurrentScreen), drawingTime);
542                 drawChild(canvas, getChildAt(mNextScreen), drawingTime);
543             } else {
544                 // If we are scrolling, draw all of our children
545                 final int count = getChildCount();
546                 for (int i = 0; i < count; i++) {
547                     drawChild(canvas, getChildAt(i), drawingTime);
548                 }
549             }
550         }
551 
552         if (restore) {
553             canvas.restoreToCount(restoreCount);
554         }
555     }
556 
557     private float mScale = 1.0f;
setScale(float scale)558     public void setScale(float scale) {
559         mScale = scale;
560         invalidate();
561     }
562 
onAttachedToWindow()563     protected void onAttachedToWindow() {
564         super.onAttachedToWindow();
565         mDragController.setWindowToken(getWindowToken());
566     }
567 
568     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)569     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
570         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
571 
572         final int width = MeasureSpec.getSize(widthMeasureSpec);
573         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
574         if (widthMode != MeasureSpec.EXACTLY) {
575             throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
576         }
577 
578         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
579         if (heightMode != MeasureSpec.EXACTLY) {
580             throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
581         }
582 
583         // The children are given the same width and height as the workspace
584         final int count = getChildCount();
585         for (int i = 0; i < count; i++) {
586             getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
587         }
588 
589 
590         if (mFirstLayout) {
591             setHorizontalScrollBarEnabled(false);
592             scrollTo(mCurrentScreen * width, 0);
593             setHorizontalScrollBarEnabled(true);
594             updateWallpaperOffset(width * (getChildCount() - 1));
595             mFirstLayout = false;
596         }
597     }
598 
599     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)600     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
601         int childLeft = 0;
602 
603         final int count = getChildCount();
604         for (int i = 0; i < count; i++) {
605             final View child = getChildAt(i);
606             if (child.getVisibility() != View.GONE) {
607                 final int childWidth = child.getMeasuredWidth();
608                 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
609                 childLeft += childWidth;
610             }
611         }
612     }
613 
614     @Override
requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate)615     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
616         int screen = indexOfChild(child);
617         if (screen != mCurrentScreen || !mScroller.isFinished()) {
618             if (!mLauncher.isWorkspaceLocked()) {
619                 snapToScreen(screen);
620             }
621             return true;
622         }
623         return false;
624     }
625 
626     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)627     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
628         if (!mLauncher.isAllAppsVisible()) {
629             final Folder openFolder = getOpenFolder();
630             if (openFolder != null) {
631                 return openFolder.requestFocus(direction, previouslyFocusedRect);
632             } else {
633                 int focusableScreen;
634                 if (mNextScreen != INVALID_SCREEN) {
635                     focusableScreen = mNextScreen;
636                 } else {
637                     focusableScreen = mCurrentScreen;
638                 }
639                 getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
640             }
641         }
642         return false;
643     }
644 
645     @Override
dispatchUnhandledMove(View focused, int direction)646     public boolean dispatchUnhandledMove(View focused, int direction) {
647         if (direction == View.FOCUS_LEFT) {
648             if (getCurrentScreen() > 0) {
649                 snapToScreen(getCurrentScreen() - 1);
650                 return true;
651             }
652         } else if (direction == View.FOCUS_RIGHT) {
653             if (getCurrentScreen() < getChildCount() - 1) {
654                 snapToScreen(getCurrentScreen() + 1);
655                 return true;
656             }
657         }
658         return super.dispatchUnhandledMove(focused, direction);
659     }
660 
661     @Override
addFocusables(ArrayList<View> views, int direction, int focusableMode)662     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
663         if (!mLauncher.isAllAppsVisible()) {
664             final Folder openFolder = getOpenFolder();
665             if (openFolder == null) {
666                 getChildAt(mCurrentScreen).addFocusables(views, direction);
667                 if (direction == View.FOCUS_LEFT) {
668                     if (mCurrentScreen > 0) {
669                         getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
670                     }
671                 } else if (direction == View.FOCUS_RIGHT){
672                     if (mCurrentScreen < getChildCount() - 1) {
673                         getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
674                     }
675                 }
676             } else {
677                 openFolder.addFocusables(views, direction);
678             }
679         }
680     }
681 
682     @Override
dispatchTouchEvent(MotionEvent ev)683     public boolean dispatchTouchEvent(MotionEvent ev) {
684         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
685             if (mLauncher.isWorkspaceLocked() || mLauncher.isAllAppsVisible()) {
686                 return false;
687             }
688         }
689         return super.dispatchTouchEvent(ev);
690     }
691 
692     @Override
onInterceptTouchEvent(MotionEvent ev)693     public boolean onInterceptTouchEvent(MotionEvent ev) {
694         final boolean workspaceLocked = mLauncher.isWorkspaceLocked();
695         final boolean allAppsVisible = mLauncher.isAllAppsVisible();
696         Log.d(TAG, "workspaceLocked=" + workspaceLocked + " allAppsVisible=" + allAppsVisible);
697         if (workspaceLocked || allAppsVisible) {
698             return false; // We don't want the events.  Let them fall through to the all apps view.
699         }
700 
701         /*
702          * This method JUST determines whether we want to intercept the motion.
703          * If we return true, onTouchEvent will be called and we do the actual
704          * scrolling there.
705          */
706 
707         /*
708          * Shortcut the most recurring case: the user is in the dragging
709          * state and he is moving his finger.  We want to intercept this
710          * motion.
711          */
712         final int action = ev.getAction();
713         if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
714             return true;
715         }
716 
717         final float x = ev.getX();
718         final float y = ev.getY();
719 
720         switch (action) {
721             case MotionEvent.ACTION_MOVE:
722                 /*
723                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
724                  * whether the user has moved far enough from his original down touch.
725                  */
726 
727                 /*
728                  * Locally do absolute value. mLastMotionX is set to the y value
729                  * of the down event.
730                  */
731                 final int xDiff = (int) Math.abs(x - mLastMotionX);
732                 final int yDiff = (int) Math.abs(y - mLastMotionY);
733 
734                 final int touchSlop = mTouchSlop;
735                 boolean xMoved = xDiff > touchSlop;
736                 boolean yMoved = yDiff > touchSlop;
737 
738                 if (xMoved || yMoved) {
739 
740                     if (xMoved) {
741                         // Scroll if the user moved far enough along the X axis
742                         mTouchState = TOUCH_STATE_SCROLLING;
743                         enableChildrenCache(mCurrentScreen - 1, mCurrentScreen + 1);
744                     }
745                     // Either way, cancel any pending longpress
746                     if (mAllowLongPress) {
747                         mAllowLongPress = false;
748                         // Try canceling the long press. It could also have been scheduled
749                         // by a distant descendant, so use the mAllowLongPress flag to block
750                         // everything
751                         final View currentScreen = getChildAt(mCurrentScreen);
752                         currentScreen.cancelLongPress();
753                     }
754                 }
755                 break;
756 
757             case MotionEvent.ACTION_DOWN:
758                 // Remember location of down touch
759                 mLastMotionX = x;
760                 mLastMotionY = y;
761                 mAllowLongPress = true;
762 
763                 /*
764                  * If being flinged and user touches the screen, initiate drag;
765                  * otherwise don't.  mScroller.isFinished should be false when
766                  * being flinged.
767                  */
768                 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
769                 break;
770 
771             case MotionEvent.ACTION_CANCEL:
772             case MotionEvent.ACTION_UP:
773 
774                 if (mTouchState != TOUCH_STATE_SCROLLING) {
775 
776                     final CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
777                     if (!currentScreen.lastDownOnOccupiedCell()) {
778                         // Send a tap to the wallpaper if the last down was on empty space
779                         mWallpaperManager.sendWallpaperCommand(getWindowToken(),
780                                 "android.wallpaper.tap", (int) ev.getX(), (int) ev.getY(), 0, null);
781                     }
782                 }
783 
784                 // Release the drag
785                 clearChildrenCache();
786                 mTouchState = TOUCH_STATE_REST;
787                 mAllowLongPress = false;
788 
789                 break;
790         }
791 
792         /*
793          * The only time we want to intercept motion events is if we are in the
794          * drag mode.
795          */
796         return mTouchState != TOUCH_STATE_REST;
797     }
798 
799     /**
800      * If one of our descendant views decides that it could be focused now, only
801      * pass that along if it's on the current screen.
802      *
803      * This happens when live folders requery, and if they're off screen, they
804      * end up calling requestFocus, which pulls it on screen.
805      */
806     @Override
focusableViewAvailable(View focused)807     public void focusableViewAvailable(View focused) {
808         View current = getChildAt(mCurrentScreen);
809         View v = focused;
810         while (true) {
811             if (v == current) {
812                 super.focusableViewAvailable(focused);
813                 return;
814             }
815             if (v == this) {
816                 return;
817             }
818             ViewParent parent = v.getParent();
819             if (parent instanceof View) {
820                 v = (View)v.getParent();
821             } else {
822                 return;
823             }
824         }
825     }
826 
enableChildrenCache(int fromScreen, int toScreen)827     void enableChildrenCache(int fromScreen, int toScreen) {
828         if (fromScreen > toScreen) {
829             fromScreen = toScreen;
830             toScreen = fromScreen;
831         }
832 
833         final int count = getChildCount();
834 
835         fromScreen = Math.max(fromScreen, 0);
836         toScreen = Math.min(toScreen, count - 1);
837 
838         for (int i = fromScreen; i <= toScreen; i++) {
839             final CellLayout layout = (CellLayout) getChildAt(i);
840             layout.setChildrenDrawnWithCacheEnabled(true);
841             layout.setChildrenDrawingCacheEnabled(true);
842         }
843     }
844 
clearChildrenCache()845     void clearChildrenCache() {
846         final int count = getChildCount();
847         for (int i = 0; i < count; i++) {
848             final CellLayout layout = (CellLayout) getChildAt(i);
849             layout.setChildrenDrawnWithCacheEnabled(false);
850         }
851     }
852 
853     @Override
onTouchEvent(MotionEvent ev)854     public boolean onTouchEvent(MotionEvent ev) {
855 
856         if (mLauncher.isWorkspaceLocked()) {
857             return false; // We don't want the events.  Let them fall through to the all apps view.
858         }
859         if (mLauncher.isAllAppsVisible()) {
860             // Cancel any scrolling that is in progress.
861             if (!mScroller.isFinished()) {
862                 mScroller.abortAnimation();
863             }
864             snapToScreen(mCurrentScreen);
865             return false; // We don't want the events.  Let them fall through to the all apps view.
866         }
867 
868         if (mVelocityTracker == null) {
869             mVelocityTracker = VelocityTracker.obtain();
870         }
871         mVelocityTracker.addMovement(ev);
872 
873         final int action = ev.getAction();
874         final float x = ev.getX();
875 
876         switch (action) {
877         case MotionEvent.ACTION_DOWN:
878             /*
879              * If being flinged and user touches, stop the fling. isFinished
880              * will be false if being flinged.
881              */
882             if (!mScroller.isFinished()) {
883                 mScroller.abortAnimation();
884             }
885 
886             // Remember where the motion event started
887             mLastMotionX = x;
888             break;
889         case MotionEvent.ACTION_MOVE:
890             if (mTouchState == TOUCH_STATE_SCROLLING) {
891                 // Scroll to follow the motion event
892                 final int deltaX = (int) (mLastMotionX - x);
893                 mLastMotionX = x;
894 
895                 if (deltaX < 0) {
896                     if (mScrollX > 0) {
897                         scrollBy(Math.max(-mScrollX, deltaX), 0);
898                         updateWallpaperOffset();
899                     }
900                 } else if (deltaX > 0) {
901                     final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -
902                             mScrollX - getWidth();
903                     if (availableToScroll > 0) {
904                         scrollBy(Math.min(availableToScroll, deltaX), 0);
905                         updateWallpaperOffset();
906                     }
907                 } else {
908                     awakenScrollBars();
909                 }
910             }
911             break;
912         case MotionEvent.ACTION_UP:
913             if (mTouchState == TOUCH_STATE_SCROLLING) {
914                 final VelocityTracker velocityTracker = mVelocityTracker;
915                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
916                 int velocityX = (int) velocityTracker.getXVelocity();
917 
918                 if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
919                     // Fling hard enough to move left
920                     snapToScreen(mCurrentScreen - 1);
921                 } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
922                     // Fling hard enough to move right
923                     snapToScreen(mCurrentScreen + 1);
924                 } else {
925                     snapToDestination();
926                 }
927 
928                 if (mVelocityTracker != null) {
929                     mVelocityTracker.recycle();
930                     mVelocityTracker = null;
931                 }
932             }
933             mTouchState = TOUCH_STATE_REST;
934             break;
935         case MotionEvent.ACTION_CANCEL:
936             mTouchState = TOUCH_STATE_REST;
937         }
938 
939         return true;
940     }
941 
snapToDestination()942     private void snapToDestination() {
943         final int screenWidth = getWidth();
944         final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
945 
946         snapToScreen(whichScreen);
947     }
948 
snapToScreen(int whichScreen)949     void snapToScreen(int whichScreen) {
950         //if (!mScroller.isFinished()) return;
951 
952         whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
953 
954         clearVacantCache();
955         enableChildrenCache(mCurrentScreen, whichScreen);
956 
957         final int screenDelta = Math.abs(whichScreen - mCurrentScreen);
958 
959         mNextScreen = whichScreen;
960 
961         mPreviousIndicator.setLevel(mNextScreen);
962         mNextIndicator.setLevel(mNextScreen);
963 
964         View focusedChild = getFocusedChild();
965         if (focusedChild != null && screenDelta != 0 && focusedChild == getChildAt(mCurrentScreen)) {
966             focusedChild.clearFocus();
967         }
968 
969         final int newX = whichScreen * getWidth();
970         final int delta = newX - mScrollX;
971         final int duration = screenDelta * 300;
972         awakenScrollBars(duration);
973         mScroller.startScroll(mScrollX, 0, delta, 0, duration);
974         invalidate();
975     }
976 
startDrag(CellLayout.CellInfo cellInfo)977     void startDrag(CellLayout.CellInfo cellInfo) {
978         View child = cellInfo.cell;
979 
980         // Make sure the drag was started by a long press as opposed to a long click.
981         // Note that Search takes focus when clicked rather than entering touch mode
982         if (!child.isInTouchMode() && !(child instanceof Search)) {
983             return;
984         }
985 
986         mDragInfo = cellInfo;
987         mDragInfo.screen = mCurrentScreen;
988 
989         CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
990 
991         current.onDragChild(child);
992         mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
993         invalidate();
994     }
995 
996     @Override
onSaveInstanceState()997     protected Parcelable onSaveInstanceState() {
998         final SavedState state = new SavedState(super.onSaveInstanceState());
999         state.currentScreen = mCurrentScreen;
1000         return state;
1001     }
1002 
1003     @Override
onRestoreInstanceState(Parcelable state)1004     protected void onRestoreInstanceState(Parcelable state) {
1005         SavedState savedState = (SavedState) state;
1006         super.onRestoreInstanceState(savedState.getSuperState());
1007         if (savedState.currentScreen != -1) {
1008             mCurrentScreen = savedState.currentScreen;
1009             Launcher.setScreen(mCurrentScreen);
1010         }
1011     }
1012 
addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo)1013     void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo) {
1014         addApplicationShortcut(info, cellInfo, false);
1015     }
1016 
addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo, boolean insertAtFirst)1017     void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo,
1018             boolean insertAtFirst) {
1019         final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
1020         final int[] result = new int[2];
1021 
1022         layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
1023         onDropExternal(result[0], result[1], info, layout, insertAtFirst);
1024     }
1025 
onDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo)1026     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
1027             DragView dragView, Object dragInfo) {
1028         final CellLayout cellLayout = getCurrentDropLayout();
1029         if (source != this) {
1030             onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
1031         } else {
1032             // Move internally
1033             if (mDragInfo != null) {
1034                 final View cell = mDragInfo.cell;
1035                 int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
1036                 if (index != mDragInfo.screen) {
1037                     final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1038                     originalCellLayout.removeView(cell);
1039                     cellLayout.addView(cell);
1040                 }
1041                 mTargetCell = estimateDropCell(x - xOffset, y - yOffset,
1042                         mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
1043                 cellLayout.onDropChild(cell, mTargetCell);
1044 
1045                 final ItemInfo info = (ItemInfo) cell.getTag();
1046                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
1047                 LauncherModel.moveItemInDatabase(mLauncher, info,
1048                         LauncherSettings.Favorites.CONTAINER_DESKTOP, index, lp.cellX, lp.cellY);
1049             }
1050         }
1051     }
1052 
onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo)1053     public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
1054             DragView dragView, Object dragInfo) {
1055         clearVacantCache();
1056     }
1057 
onDragOver(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo)1058     public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
1059             DragView dragView, Object dragInfo) {
1060     }
1061 
onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo)1062     public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
1063             DragView dragView, Object dragInfo) {
1064         clearVacantCache();
1065     }
1066 
onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout)1067     private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
1068         onDropExternal(x, y, dragInfo, cellLayout, false);
1069     }
1070 
onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout, boolean insertAtFirst)1071     private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout,
1072             boolean insertAtFirst) {
1073         // Drag from somewhere else
1074         ItemInfo info = (ItemInfo) dragInfo;
1075 
1076         View view;
1077 
1078         switch (info.itemType) {
1079         case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1080         case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1081             if (info.container == NO_ID) {
1082                 // Came from all apps -- make a copy
1083                 info = new ApplicationInfo((ApplicationInfo) info);
1084             }
1085             view = mLauncher.createShortcut(R.layout.application, cellLayout,
1086                     (ApplicationInfo) info);
1087             break;
1088         case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
1089             view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
1090                     (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
1091             break;
1092         default:
1093             throw new IllegalStateException("Unknown item type: " + info.itemType);
1094         }
1095 
1096         cellLayout.addView(view, insertAtFirst ? 0 : -1);
1097         view.setOnLongClickListener(mLongClickListener);
1098         if (view instanceof DropTarget) {
1099             mDragController.addDropTarget((DropTarget) view);
1100         }
1101 
1102         mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
1103         cellLayout.onDropChild(view, mTargetCell);
1104         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
1105 
1106         LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
1107                 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
1108     }
1109 
1110     /**
1111      * Return the current {@link CellLayout}, correctly picking the destination
1112      * screen while a scroll is in progress.
1113      */
getCurrentDropLayout()1114     private CellLayout getCurrentDropLayout() {
1115         int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
1116         return (CellLayout) getChildAt(index);
1117     }
1118 
1119     /**
1120      * {@inheritDoc}
1121      */
acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo)1122     public boolean acceptDrop(DragSource source, int x, int y,
1123             int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1124         final CellLayout layout = getCurrentDropLayout();
1125         final CellLayout.CellInfo cellInfo = mDragInfo;
1126         final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1127         final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1128 
1129         if (mVacantCache == null) {
1130             final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1131             mVacantCache = layout.findAllVacantCells(null, ignoreView);
1132         }
1133 
1134         return mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false);
1135     }
1136 
1137     /**
1138      * {@inheritDoc}
1139      */
estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle)1140     public Rect estimateDropLocation(DragSource source, int x, int y,
1141             int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) {
1142         final CellLayout layout = getCurrentDropLayout();
1143 
1144         final CellLayout.CellInfo cellInfo = mDragInfo;
1145         final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1146         final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1147         final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1148 
1149         final Rect location = recycle != null ? recycle : new Rect();
1150 
1151         // Find drop cell and convert into rectangle
1152         int[] dropCell = estimateDropCell(x - xOffset, y - yOffset,
1153                 spanX, spanY, ignoreView, layout, mTempCell);
1154 
1155         if (dropCell == null) {
1156             return null;
1157         }
1158 
1159         layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
1160         location.left = mTempEstimate[0];
1161         location.top = mTempEstimate[1];
1162 
1163         layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
1164         location.right = mTempEstimate[0];
1165         location.bottom = mTempEstimate[1];
1166 
1167         return location;
1168     }
1169 
1170     /**
1171      * Calculate the nearest cell where the given object would be dropped.
1172      */
estimateDropCell(int pixelX, int pixelY, int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle)1173     private int[] estimateDropCell(int pixelX, int pixelY,
1174             int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
1175         // Create vacant cell cache if none exists
1176         if (mVacantCache == null) {
1177             mVacantCache = layout.findAllVacantCells(null, ignoreView);
1178         }
1179 
1180         // Find the best target drop location
1181         return layout.findNearestVacantArea(pixelX, pixelY,
1182                 spanX, spanY, mVacantCache, recycle);
1183     }
1184 
setLauncher(Launcher launcher)1185     void setLauncher(Launcher launcher) {
1186         mLauncher = launcher;
1187     }
1188 
setDragController(DragController dragController)1189     public void setDragController(DragController dragController) {
1190         mDragController = dragController;
1191     }
1192 
onDropCompleted(View target, boolean success)1193     public void onDropCompleted(View target, boolean success) {
1194         clearVacantCache();
1195 
1196         if (success){
1197             if (target != this && mDragInfo != null) {
1198                 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1199                 cellLayout.removeView(mDragInfo.cell);
1200                 if (mDragInfo.cell instanceof DropTarget) {
1201                     mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
1202                 }
1203                 //final Object tag = mDragInfo.cell.getTag();
1204             }
1205         } else {
1206             if (mDragInfo != null) {
1207                 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1208                 cellLayout.onDropAborted(mDragInfo.cell);
1209             }
1210         }
1211 
1212         mDragInfo = null;
1213     }
1214 
scrollLeft()1215     public void scrollLeft() {
1216         clearVacantCache();
1217         if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
1218             snapToScreen(mCurrentScreen - 1);
1219         }
1220     }
1221 
scrollRight()1222     public void scrollRight() {
1223         clearVacantCache();
1224         if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
1225                 mScroller.isFinished()) {
1226             snapToScreen(mCurrentScreen + 1);
1227         }
1228     }
1229 
getScreenForView(View v)1230     public int getScreenForView(View v) {
1231         int result = -1;
1232         if (v != null) {
1233             ViewParent vp = v.getParent();
1234             int count = getChildCount();
1235             for (int i = 0; i < count; i++) {
1236                 if (vp == getChildAt(i)) {
1237                     return i;
1238                 }
1239             }
1240         }
1241         return result;
1242     }
1243 
1244     /**
1245      * Find a search widget on the given screen
1246      */
findSearchWidget(CellLayout screen)1247     private Search findSearchWidget(CellLayout screen) {
1248         final int count = screen.getChildCount();
1249         for (int i = 0; i < count; i++) {
1250             View v = screen.getChildAt(i);
1251             if (v instanceof Search) {
1252                 return (Search) v;
1253             }
1254         }
1255         return null;
1256     }
1257 
1258     /**
1259      * Gets the first search widget on the current screen, if there is one.
1260      * Returns <code>null</code> otherwise.
1261      */
findSearchWidgetOnCurrentScreen()1262     public Search findSearchWidgetOnCurrentScreen() {
1263         CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
1264         return findSearchWidget(currentScreen);
1265     }
1266 
getFolderForTag(Object tag)1267     public Folder getFolderForTag(Object tag) {
1268         int screenCount = getChildCount();
1269         for (int screen = 0; screen < screenCount; screen++) {
1270             CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1271             int count = currentScreen.getChildCount();
1272             for (int i = 0; i < count; i++) {
1273                 View child = currentScreen.getChildAt(i);
1274                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1275                 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1276                     Folder f = (Folder) child;
1277                     if (f.getInfo() == tag) {
1278                         return f;
1279                     }
1280                 }
1281             }
1282         }
1283         return null;
1284     }
1285 
getViewForTag(Object tag)1286     public View getViewForTag(Object tag) {
1287         int screenCount = getChildCount();
1288         for (int screen = 0; screen < screenCount; screen++) {
1289             CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1290             int count = currentScreen.getChildCount();
1291             for (int i = 0; i < count; i++) {
1292                 View child = currentScreen.getChildAt(i);
1293                 if (child.getTag() == tag) {
1294                     return child;
1295                 }
1296             }
1297         }
1298         return null;
1299     }
1300 
1301     /**
1302      * @return True is long presses are still allowed for the current touch
1303      */
allowLongPress()1304     public boolean allowLongPress() {
1305         return mAllowLongPress;
1306     }
1307 
1308     /**
1309      * Set true to allow long-press events to be triggered, usually checked by
1310      * {@link Launcher} to accept or block dpad-initiated long-presses.
1311      */
setAllowLongPress(boolean allowLongPress)1312     public void setAllowLongPress(boolean allowLongPress) {
1313         mAllowLongPress = allowLongPress;
1314     }
1315 
removeShortcutsForPackage(String packageName)1316     void removeShortcutsForPackage(String packageName) {
1317         final ArrayList<View> childrenToRemove = new ArrayList<View>();
1318         final int count = getChildCount();
1319 
1320         for (int i = 0; i < count; i++) {
1321             final CellLayout layout = (CellLayout) getChildAt(i);
1322             int childCount = layout.getChildCount();
1323 
1324             childrenToRemove.clear();
1325 
1326             for (int j = 0; j < childCount; j++) {
1327                 final View view = layout.getChildAt(j);
1328                 Object tag = view.getTag();
1329 
1330                 if (tag instanceof ApplicationInfo) {
1331                     final ApplicationInfo info = (ApplicationInfo) tag;
1332                     // We need to check for ACTION_MAIN otherwise getComponent() might
1333                     // return null for some shortcuts (for instance, for shortcuts to
1334                     // web pages.)
1335                     final Intent intent = info.intent;
1336                     final ComponentName name = intent.getComponent();
1337 
1338                     if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1339                             name != null && packageName.equals(name.getPackageName())) {
1340                         LauncherModel.deleteItemFromDatabase(mLauncher, info);
1341                         childrenToRemove.add(view);
1342                     }
1343                 } else if (tag instanceof UserFolderInfo) {
1344                     final UserFolderInfo info = (UserFolderInfo) tag;
1345                     final ArrayList<ApplicationInfo> contents = info.contents;
1346                     final ArrayList<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>(1);
1347                     final int contentsCount = contents.size();
1348                     boolean removedFromFolder = false;
1349 
1350                     for (int k = 0; k < contentsCount; k++) {
1351                         final ApplicationInfo appInfo = contents.get(k);
1352                         final Intent intent = appInfo.intent;
1353                         final ComponentName name = intent.getComponent();
1354 
1355                         if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1356                                 name != null && packageName.equals(name.getPackageName())) {
1357                             toRemove.add(appInfo);
1358                             LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
1359                             removedFromFolder = true;
1360                         }
1361                     }
1362 
1363                     contents.removeAll(toRemove);
1364                     if (removedFromFolder) {
1365                         final Folder folder = getOpenFolder();
1366                         if (folder != null) folder.notifyDataSetChanged();
1367                     }
1368                 }
1369             }
1370 
1371             childCount = childrenToRemove.size();
1372             for (int j = 0; j < childCount; j++) {
1373                 View child = childrenToRemove.get(j);
1374                 layout.removeViewInLayout(child);
1375                 if (child instanceof DropTarget) {
1376                     mDragController.removeDropTarget((DropTarget)child);
1377                 }
1378             }
1379 
1380             if (childCount > 0) {
1381                 layout.requestLayout();
1382                 layout.invalidate();
1383             }
1384         }
1385     }
1386 
updateShortcutsForPackage(String packageName)1387     void updateShortcutsForPackage(String packageName) {
1388         final PackageManager pm = mLauncher.getPackageManager();
1389 
1390         final int count = getChildCount();
1391         for (int i = 0; i < count; i++) {
1392             final CellLayout layout = (CellLayout) getChildAt(i);
1393             int childCount = layout.getChildCount();
1394             for (int j = 0; j < childCount; j++) {
1395                 final View view = layout.getChildAt(j);
1396                 Object tag = view.getTag();
1397                 if (tag instanceof ApplicationInfo) {
1398                     ApplicationInfo info = (ApplicationInfo) tag;
1399                     // We need to check for ACTION_MAIN otherwise getComponent() might
1400                     // return null for some shortcuts (for instance, for shortcuts to
1401                     // web pages.)
1402                     final Intent intent = info.intent;
1403                     final ComponentName name = intent.getComponent();
1404                     if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
1405                             Intent.ACTION_MAIN.equals(intent.getAction()) && name != null &&
1406                             packageName.equals(name.getPackageName())) {
1407 
1408                         final Drawable icon = AppInfoCache.getIconDrawable(pm, info);
1409                         if (icon != null && icon != info.icon) {
1410                             info.icon.setCallback(null);
1411                             info.icon = Utilities.createIconThumbnail(icon, mContext);
1412                             info.filtered = true;
1413                             ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(null,
1414                                     info.icon, null, null);
1415                         }
1416                     }
1417                 }
1418             }
1419         }
1420     }
1421 
moveToDefaultScreen()1422     void moveToDefaultScreen() {
1423         snapToScreen(mDefaultScreen);
1424         getChildAt(mDefaultScreen).requestFocus();
1425     }
1426 
setIndicators(Drawable previous, Drawable next)1427     void setIndicators(Drawable previous, Drawable next) {
1428         mPreviousIndicator = previous;
1429         mNextIndicator = next;
1430         previous.setLevel(mCurrentScreen);
1431         next.setLevel(mCurrentScreen);
1432     }
1433 
1434     public static class SavedState extends BaseSavedState {
1435         int currentScreen = -1;
1436 
SavedState(Parcelable superState)1437         SavedState(Parcelable superState) {
1438             super(superState);
1439         }
1440 
SavedState(Parcel in)1441         private SavedState(Parcel in) {
1442             super(in);
1443             currentScreen = in.readInt();
1444         }
1445 
1446         @Override
writeToParcel(Parcel out, int flags)1447         public void writeToParcel(Parcel out, int flags) {
1448             super.writeToParcel(out, flags);
1449             out.writeInt(currentScreen);
1450         }
1451 
1452         public static final Parcelable.Creator<SavedState> CREATOR =
1453                 new Parcelable.Creator<SavedState>() {
1454             public SavedState createFromParcel(Parcel in) {
1455                 return new SavedState(in);
1456             }
1457 
1458             public SavedState[] newArray(int size) {
1459                 return new SavedState[size];
1460             }
1461         };
1462     }
1463 
show()1464     void show() {
1465         setVisibility(VISIBLE);
1466     }
1467 
hide()1468     void hide() {
1469     }
1470 }
1471