blob: d834b8fe0c669530e4cb05c9ae5dba8b184ea1e8 [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
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
17package com.android.launcher;
18
19import android.content.Context;
20import android.content.Intent;
21import android.content.ComponentName;
22import android.content.res.TypedArray;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Paint;
26import android.graphics.RectF;
27import android.graphics.Rect;
28import android.util.AttributeSet;
29import android.view.MotionEvent;
30import android.view.VelocityTracker;
31import android.view.View;
32import android.view.ViewConfiguration;
33import android.view.ViewGroup;
34import android.view.ViewParent;
35import android.widget.Scroller;
36import android.os.Parcelable;
37import android.os.Parcel;
38
39import java.util.ArrayList;
40
41/**
42 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
43 * screen contains a number of icons, folders or widgets the user can interact with.
44 * A workspace is meant to be used with a fixed width only.
45 */
46public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
47 private static final int INVALID_SCREEN = -1;
48
49 /**
50 * The velocity at which a fling gesture will cause us to snap to the next screen
51 */
52 private static final int SNAP_VELOCITY = 1000;
53
54 private int mDefaultScreen;
55
56 private Paint mPaint;
57 private Bitmap mWallpaper;
58
59 private int mWallpaperWidth;
60 private int mWallpaperHeight;
61 private float mWallpaperOffset;
62 private boolean mWallpaperLoaded;
63
64 private boolean mFirstLayout = true;
65
66 private int mCurrentScreen;
67 private int mNextScreen = INVALID_SCREEN;
68 private Scroller mScroller;
69 private VelocityTracker mVelocityTracker;
70
71 /**
72 * CellInfo for the cell that is currently being dragged
73 */
74 private CellLayout.CellInfo mDragInfo;
75
76 private float mLastMotionX;
77 private float mLastMotionY;
78
79 private final static int TOUCH_STATE_REST = 0;
80 private final static int TOUCH_STATE_SCROLLING = 1;
81
82 private int mTouchState = TOUCH_STATE_REST;
83
84 private OnLongClickListener mLongClickListener;
85
86 private Launcher mLauncher;
87 private DragController mDragger;
88
89 private int[] mTempCell = new int[2];
90
91 private boolean mAllowLongPress;
92 private boolean mLocked;
93
94 private int mTouchSlop;
95
96 final Rect mDrawerBounds = new Rect();
97 final Rect mClipBounds = new Rect();
98
99 /**
100 * Used to inflate the Workspace from XML.
101 *
102 * @param context The application's context.
103 * @param attrs The attribtues set containing the Workspace's customization values.
104 */
105 public Workspace(Context context, AttributeSet attrs) {
106 this(context, attrs, 0);
107 }
108
109 /**
110 * Used to inflate the Workspace from XML.
111 *
112 * @param context The application's context.
113 * @param attrs The attribtues set containing the Workspace's customization values.
114 * @param defStyle Unused.
115 */
116 public Workspace(Context context, AttributeSet attrs, int defStyle) {
117 super(context, attrs, defStyle);
118
119 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
120 mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
121 a.recycle();
122
123 initWorkspace();
124 }
125
126 /**
127 * Initializes various states for this workspace.
128 */
129 private void initWorkspace() {
130 mScroller = new Scroller(getContext());
131 mCurrentScreen = mDefaultScreen;
132 Launcher.setScreen(mCurrentScreen);
133
134 mPaint = new Paint();
135 mPaint.setDither(false);
136
137 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
138 }
139
140 /**
141 * Set the background's wallpaper.
142 */
143 void loadWallpaper(Bitmap bitmap) {
144 mWallpaper = bitmap;
145 mWallpaperLoaded = true;
146 requestLayout();
147 invalidate();
148 }
149
150 @Override
151 public void addView(View child, int index, LayoutParams params) {
152 if (!(child instanceof CellLayout)) {
153 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
154 }
155 super.addView(child, index, params);
156 }
157
158 @Override
159 public void addView(View child) {
160 if (!(child instanceof CellLayout)) {
161 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
162 }
163 super.addView(child);
164 }
165
166 @Override
167 public void addView(View child, int index) {
168 if (!(child instanceof CellLayout)) {
169 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
170 }
171 super.addView(child, index);
172 }
173
174 @Override
175 public void addView(View child, int width, int height) {
176 if (!(child instanceof CellLayout)) {
177 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
178 }
179 super.addView(child, width, height);
180 }
181
182 @Override
183 public void addView(View child, LayoutParams params) {
184 if (!(child instanceof CellLayout)) {
185 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
186 }
187 super.addView(child, params);
188 }
189
190 /**
191 * @return The open folder on the current screen, or null if there is none
192 */
193 Folder getOpenFolder() {
194 CellLayout currentScreen = (CellLayout) getChildAt(mCurrentScreen);
195 int count = currentScreen.getChildCount();
196 for (int i = 0; i < count; i++) {
197 View child = currentScreen.getChildAt(i);
198 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
199 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
200 return (Folder) child;
201 }
202 }
203 return null;
204 }
205
206 ArrayList<Folder> getOpenFolders() {
207 final int screens = getChildCount();
208 ArrayList<Folder> folders = new ArrayList<Folder>(screens);
209
210 for (int screen = 0; screen < screens; screen++) {
211 CellLayout currentScreen = (CellLayout) getChildAt(screen);
212 int count = currentScreen.getChildCount();
213 for (int i = 0; i < count; i++) {
214 View child = currentScreen.getChildAt(i);
215 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
216 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
217 folders.add((Folder) child);
218 break;
219 }
220 }
221 }
222
223 return folders;
224 }
225
226 boolean isDefaultScreenShowing() {
227 return mCurrentScreen == mDefaultScreen;
228 }
229
230 /**
231 * Returns the index of the currently displayed screen.
232 *
233 * @return The index of the currently displayed screen.
234 */
235 int getCurrentScreen() {
236 return mCurrentScreen;
237 }
238
239 /**
240 * Computes a bounding rectangle for a range of cells
241 *
242 * @param cellX X coordinate of upper left corner expressed as a cell position
243 * @param cellY Y coordinate of upper left corner expressed as a cell position
244 * @param cellHSpan Width in cells
245 * @param cellVSpan Height in cells
246 * @param rect Rectnagle into which to put the results
247 */
248 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF rect) {
249 ((CellLayout)getChildAt(mCurrentScreen)).cellToRect(cellX, cellY,
250 cellHSpan, cellVSpan, rect);
251 }
252
253 /**
254 * Sets the current screen.
255 *
256 * @param currentScreen
257 */
258 void setCurrentScreen(int currentScreen) {
259 mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
260 scrollTo(mCurrentScreen * getWidth(), 0);
261 invalidate();
262 }
263
264 /**
265 * Shows the default screen (defined by the firstScreen attribute in XML.)
266 */
267 void showDefaultScreen() {
268 setCurrentScreen(mDefaultScreen);
269 }
270
271 /**
272 * Adds the specified child in the current screen. The position and dimension of
273 * the child are defined by x, y, spanX and spanY.
274 *
275 * @param child The child to add in one of the workspace's screens.
276 * @param x The X position of the child in the screen's grid.
277 * @param y The Y position of the child in the screen's grid.
278 * @param spanX The number of cells spanned horizontally by the child.
279 * @param spanY The number of cells spanned vertically by the child.
280 */
281 void addInCurrentScreen(View child, int x, int y, int spanX, int spanY) {
282 addInScreen(child, mCurrentScreen, x, y, spanX, spanY, false);
283 }
284
285 /**
286 * Adds the specified child in the current screen. The position and dimension of
287 * the child are defined by x, y, spanX and spanY.
288 *
289 * @param child The child to add in one of the workspace's screens.
290 * @param x The X position of the child in the screen's grid.
291 * @param y The Y position of the child in the screen's grid.
292 * @param spanX The number of cells spanned horizontally by the child.
293 * @param spanY The number of cells spanned vertically by the child.
294 * @param insert When true, the child is inserted at the beginning of the children list.
295 */
296 void addInCurrentScreen(View child, int x, int y, int spanX, int spanY, boolean insert) {
297 addInScreen(child, mCurrentScreen, x, y, spanX, spanY, insert);
298 }
299
300 /**
301 * Adds the specified child in the specified screen. The position and dimension of
302 * the child are defined by x, y, spanX and spanY.
303 *
304 * @param child The child to add in one of the workspace's screens.
305 * @param screen The screen in which to add the child.
306 * @param x The X position of the child in the screen's grid.
307 * @param y The Y position of the child in the screen's grid.
308 * @param spanX The number of cells spanned horizontally by the child.
309 * @param spanY The number of cells spanned vertically by the child.
310 */
311 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY) {
312 addInScreen(child, screen, x, y, spanX, spanY, false);
313 }
314
315 /**
316 * Adds the specified child in the specified screen. The position and dimension of
317 * the child are defined by x, y, spanX and spanY.
318 *
319 * @param child The child to add in one of the workspace's screens.
320 * @param screen The screen in which to add the child.
321 * @param x The X position of the child in the screen's grid.
322 * @param y The Y position of the child in the screen's grid.
323 * @param spanX The number of cells spanned horizontally by the child.
324 * @param spanY The number of cells spanned vertically by the child.
325 * @param insert When true, the child is inserted at the beginning of the children list.
326 */
327 void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {
328 if (screen < 0 || screen >= getChildCount()) {
329 throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
330 }
331
332 final CellLayout group = (CellLayout) getChildAt(screen);
333 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
334 if (lp == null) {
335 lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
336 } else {
337 lp.cellX = x;
338 lp.cellY = y;
339 lp.cellHSpan = spanX;
340 lp.cellVSpan = spanY;
341 }
342 group.addView(child, insert ? 0 : -1, lp);
343 if (!(child instanceof Folder)) {
344 child.setOnLongClickListener(mLongClickListener);
345 }
346 }
347
348 void addWidget(View view, Widget widget) {
349 addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
350 widget.spanY, false);
351 }
352
353 void addWidget(View view, Widget widget, boolean insert) {
354 addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
355 widget.spanY, insert);
356 }
357
358 CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
359 CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
360 if (group != null) {
361 return group.findAllVacantCells(occupied);
362 }
363 return null;
364 }
365
366 /**
367 * Returns the coordinate of a vacant cell for the current screen.
368 */
369 boolean getVacantCell(int[] vacant, int spanX, int spanY) {
370 CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
371 if (group != null) {
372 return group.getVacantCell(vacant, spanX, spanY);
373 }
374 return false;
375 }
376
377 /**
378 * Adds the specified child in the current screen. The position and dimension of
379 * the child are defined by x, y, spanX and spanY.
380 *
381 * @param child The child to add in one of the workspace's screens.
382 * @param spanX The number of cells spanned horizontally by the child.
383 * @param spanY The number of cells spanned vertically by the child.
384 */
385 void fitInCurrentScreen(View child, int spanX, int spanY) {
386 fitInScreen(child, mCurrentScreen, spanX, spanY);
387 }
388
389 /**
390 * Adds the specified child in the specified screen. The position and dimension of
391 * the child are defined by x, y, spanX and spanY.
392 *
393 * @param child The child to add in one of the workspace's screens.
394 * @param screen The screen in which to add the child.
395 * @param spanX The number of cells spanned horizontally by the child.
396 * @param spanY The number of cells spanned vertically by the child.
397 */
398 void fitInScreen(View child, int screen, int spanX, int spanY) {
399 if (screen < 0 || screen >= getChildCount()) {
400 throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
401 }
402
403 final CellLayout group = (CellLayout) getChildAt(screen);
404 boolean vacant = group.getVacantCell(mTempCell, spanX, spanY);
405 if (vacant) {
406 group.addView(child,
407 new CellLayout.LayoutParams(mTempCell[0], mTempCell[1], spanX, spanY));
408 child.setOnLongClickListener(mLongClickListener);
409 if (!(child instanceof Folder)) {
410 child.setOnLongClickListener(mLongClickListener);
411 }
412 }
413 }
414
415 /**
416 * Registers the specified listener on each screen contained in this workspace.
417 *
418 * @param l The listener used to respond to long clicks.
419 */
420 @Override
421 public void setOnLongClickListener(OnLongClickListener l) {
422 mLongClickListener = l;
423 final int count = getChildCount();
424 for (int i = 0; i < count; i++) {
425 getChildAt(i).setOnLongClickListener(l);
426 }
427 }
428
429 @Override
430 public void computeScroll() {
431 if (mScroller.computeScrollOffset()) {
432 mScrollX = mScroller.getCurrX();
433 mScrollY = mScroller.getCurrY();
434 postInvalidate();
435 } else if (mNextScreen != INVALID_SCREEN) {
436 mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
437 Launcher.setScreen(mCurrentScreen);
438 mNextScreen = INVALID_SCREEN;
439 clearChildrenCache();
440 }
441 }
442
443 @Override
444 protected void dispatchDraw(Canvas canvas) {
445 // If the all apps drawer is open and the drawing region for the workspace
446 // is contained within the drawer's bounds, we skip the drawing. This requires
447 // the drawer to be fully opaque.
448 if (mLauncher.isDrawerUp()) {
449 final Rect clipBounds = mClipBounds;
450 canvas.getClipBounds(clipBounds);
451 clipBounds.offset(-mScrollX, -mScrollY);
452 if (mDrawerBounds.contains(clipBounds)) {
453 return;
454 }
455 }
456
457 float x = mScrollX * mWallpaperOffset;
458 if (x + mWallpaperWidth < mRight - mLeft) {
459 x = mRight - mLeft - mWallpaperWidth;
460 }
461
462 canvas.drawBitmap(mWallpaper, x, (mBottom - mTop - mWallpaperHeight) / 2, mPaint);
463
464 // ViewGroup.dispatchDraw() supports many features we don't need:
465 // clip to padding, layout animation, animation listener, disappearing
466 // children, etc. The following implementation attempts to fast-track
467 // the drawing dispatch by drawing only what we know needs to be drawn.
468
469 boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN;
470 // If we are not scrolling or flinging, draw only the current screen
471 if (fastDraw) {
472 drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
473 } else {
474 final long drawingTime = getDrawingTime();
475 // If we are flinging, draw only the current screen and the target screen
476 if (mNextScreen >= 0 && mNextScreen < getChildCount() &&
477 Math.abs(mCurrentScreen - mNextScreen) == 1) {
478 drawChild(canvas, getChildAt(mCurrentScreen), drawingTime);
479 drawChild(canvas, getChildAt(mNextScreen), drawingTime);
480 } else {
481 // If we are scrolling, draw all of our children
482 final int count = getChildCount();
483 for (int i = 0; i < count; i++) {
484 drawChild(canvas, getChildAt(i), drawingTime);
485 }
486 }
487 }
488 }
489
490 @Override
491 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
492 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
493
494 final int width = MeasureSpec.getSize(widthMeasureSpec);
495 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
496 if (widthMode != MeasureSpec.EXACTLY) {
497 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
498 }
499
500 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
501 if (heightMode != MeasureSpec.EXACTLY) {
502 throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
503 }
504
505 // The children are given the same width and height as the workspace
506 final int count = getChildCount();
507 for (int i = 0; i < count; i++) {
508 getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
509 }
510
511 if (mWallpaperLoaded) {
512 mWallpaperLoaded = false;
513 mWallpaper = Utilities.centerToFit(mWallpaper, width,
514 MeasureSpec.getSize(heightMeasureSpec), getContext());
515 mWallpaperWidth = mWallpaper.getWidth();
516 mWallpaperHeight = mWallpaper.getHeight();
517 }
518
519 final int wallpaperWidth = mWallpaperWidth;
520 mWallpaperOffset = wallpaperWidth > width ? (count * width - wallpaperWidth) /
521 ((count - 1) * (float) width) : 1.0f;
522
523 if (mFirstLayout) {
524 scrollTo(mCurrentScreen * width, 0);
525 mFirstLayout = false;
526 }
527 }
528
529 @Override
530 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
531 int childLeft = 0;
532
533 final int count = getChildCount();
534 for (int i = 0; i < count; i++) {
535 final View child = getChildAt(i);
536 if (child.getVisibility() != View.GONE) {
537 final int childWidth = child.getMeasuredWidth();
538 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
539 childLeft += childWidth;
540 }
541 }
542 }
543
544 @Override
545 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
546 int screen = indexOfChild(child);
547 if (screen != mCurrentScreen || !mScroller.isFinished()) {
548 if (!mLauncher.isWorkspaceLocked()) {
549 snapToScreen(screen);
550 }
551 return true;
552 }
553 return false;
554 }
555
556 @Override
557 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
558 if (mLauncher.isDrawerDown()) {
559 final Folder openFolder = getOpenFolder();
560 if (openFolder != null) {
561 return openFolder.requestFocus(direction, previouslyFocusedRect);
562 } else {
563 int focusableScreen;
564 if (mNextScreen != INVALID_SCREEN) {
565 focusableScreen = mNextScreen;
566 } else {
567 focusableScreen = mCurrentScreen;
568 }
569 getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
570 }
571 }
572 return false;
573 }
574
575 @Override
576 public boolean dispatchUnhandledMove(View focused, int direction) {
577 if (direction == View.FOCUS_LEFT) {
578 if (getCurrentScreen() > 0) {
579 snapToScreen(getCurrentScreen() - 1);
580 return true;
581 }
582 } else if (direction == View.FOCUS_RIGHT) {
583 if (getCurrentScreen() < getChildCount() - 1) {
584 snapToScreen(getCurrentScreen() + 1);
585 return true;
586 }
587 }
588 return super.dispatchUnhandledMove(focused, direction);
589 }
590
591 @Override
592 public void addFocusables(ArrayList<View> views, int direction) {
593 if (mLauncher.isDrawerDown()) {
594 final Folder openFolder = getOpenFolder();
595 if (openFolder == null) {
596 getChildAt(mCurrentScreen).addFocusables(views, direction);
597 if (direction == View.FOCUS_LEFT) {
598 if (mCurrentScreen > 0) {
599 getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
600 }
601 } else if (direction == View.FOCUS_RIGHT){
602 if (mCurrentScreen < getChildCount() - 1) {
603 getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
604 }
605 }
606 } else {
607 openFolder.addFocusables(views, direction);
608 }
609 }
610 }
611
612 @Override
613 public boolean onInterceptTouchEvent(MotionEvent ev) {
614 if (mLocked || !mLauncher.isDrawerDown()) {
615 return true;
616 }
617
618 /*
619 * This method JUST determines whether we want to intercept the motion.
620 * If we return true, onTouchEvent will be called and we do the actual
621 * scrolling there.
622 */
623
624 /*
625 * Shortcut the most recurring case: the user is in the dragging
626 * state and he is moving his finger. We want to intercept this
627 * motion.
628 */
629 final int action = ev.getAction();
630 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
631 return true;
632 }
633
634 final float x = ev.getX();
635 final float y = ev.getY();
636
637 switch (action) {
638 case MotionEvent.ACTION_MOVE:
639 /*
640 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
641 * whether the user has moved far enough from his original down touch.
642 */
643
644 /*
645 * Locally do absolute value. mLastMotionX is set to the y value
646 * of the down event.
647 */
648 final int xDiff = (int) Math.abs(x - mLastMotionX);
649 final int yDiff = (int) Math.abs(y - mLastMotionY);
650
651 final int touchSlop = mTouchSlop;
652 boolean xMoved = xDiff > touchSlop;
653 boolean yMoved = yDiff > touchSlop;
654
655 if (xMoved || yMoved) {
656
657 if (xMoved) {
658 // Scroll if the user moved far enough along the X axis
659 mTouchState = TOUCH_STATE_SCROLLING;
660 enableChildrenCache();
661 }
662 // Either way, cancel any pending longpress
663 if (mAllowLongPress) {
664 mAllowLongPress = false;
665 // Try canceling the long press. It could also have been scheduled
666 // by a distant descendant, so use the mAllowLongPress flag to block
667 // everything
668 final View currentScreen = getChildAt(mCurrentScreen);
669 currentScreen.cancelLongPress();
670 }
671 }
672 break;
673
674 case MotionEvent.ACTION_DOWN:
675 // Remember location of down touch
676 mLastMotionX = x;
677 mLastMotionY = y;
678 mAllowLongPress = true;
679
680 /*
681 * If being flinged and user touches the screen, initiate drag;
682 * otherwise don't. mScroller.isFinished should be false when
683 * being flinged.
684 */
685 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
686 break;
687
688 case MotionEvent.ACTION_CANCEL:
689 case MotionEvent.ACTION_UP:
690 // Release the drag
691 clearChildrenCache();
692 mTouchState = TOUCH_STATE_REST;
693 mAllowLongPress = false;
694 break;
695 }
696
697 /*
698 * The only time we want to intercept motion events is if we are in the
699 * drag mode.
700 */
701 return mTouchState != TOUCH_STATE_REST;
702 }
703
704 void enableChildrenCache() {
705 final int count = getChildCount();
706 for (int i = 0; i < count; i++) {
707 final CellLayout layout = (CellLayout) getChildAt(i);
708 layout.setChildrenDrawnWithCacheEnabled(true);
709 layout.setChildrenDrawingCacheEnabled(true);
710 }
711 }
712
713 void clearChildrenCache() {
714 final int count = getChildCount();
715 for (int i = 0; i < count; i++) {
716 final CellLayout layout = (CellLayout) getChildAt(i);
717 layout.setChildrenDrawnWithCacheEnabled(false);
718 }
719 }
720
721 @Override
722 public boolean onTouchEvent(MotionEvent ev) {
723 if (mLocked || !mLauncher.isDrawerDown()) {
724 return true;
725 }
726
727 if (mVelocityTracker == null) {
728 mVelocityTracker = VelocityTracker.obtain();
729 }
730 mVelocityTracker.addMovement(ev);
731
732 final int action = ev.getAction();
733 final float x = ev.getX();
734
735 switch (action) {
736 case MotionEvent.ACTION_DOWN:
737 /*
738 * If being flinged and user touches, stop the fling. isFinished
739 * will be false if being flinged.
740 */
741 if (!mScroller.isFinished()) {
742 mScroller.abortAnimation();
743 }
744
745 // Remember where the motion event started
746 mLastMotionX = x;
747 break;
748 case MotionEvent.ACTION_MOVE:
749 if (mTouchState == TOUCH_STATE_SCROLLING) {
750 // Scroll to follow the motion event
751 final int deltaX = (int) (mLastMotionX - x);
752 mLastMotionX = x;
753
754 if (deltaX < 0) {
755 if (mScrollX > 0) {
756 scrollBy(Math.max(-mScrollX, deltaX), 0);
757 }
758 } else if (deltaX > 0) {
759 final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -
760 mScrollX - getWidth();
761 if (availableToScroll > 0) {
762 scrollBy(Math.min(availableToScroll, deltaX), 0);
763 }
764 }
765 }
766 break;
767 case MotionEvent.ACTION_UP:
768 if (mTouchState == TOUCH_STATE_SCROLLING) {
769 final VelocityTracker velocityTracker = mVelocityTracker;
770 velocityTracker.computeCurrentVelocity(1000);
771 int velocityX = (int) velocityTracker.getXVelocity();
772
773 if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
774 // Fling hard enough to move left
775 snapToScreen(mCurrentScreen - 1);
776 } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
777 // Fling hard enough to move right
778 snapToScreen(mCurrentScreen + 1);
779 } else {
780 snapToDestination();
781 }
782
783 if (mVelocityTracker != null) {
784 mVelocityTracker.recycle();
785 mVelocityTracker = null;
786 }
787 }
788 mTouchState = TOUCH_STATE_REST;
789 break;
790 case MotionEvent.ACTION_CANCEL:
791 mTouchState = TOUCH_STATE_REST;
792 }
793
794 return true;
795 }
796
797 private void snapToDestination() {
798 final int screenWidth = getWidth();
799 final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
800
801 snapToScreen(whichScreen);
802 }
803
804 void snapToScreen(int whichScreen) {
805 enableChildrenCache();
806
807 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
808 boolean changingScreens = whichScreen != mCurrentScreen;
809
810 mNextScreen = whichScreen;
811
812 View focusedChild = getFocusedChild();
813 if (focusedChild != null && changingScreens && focusedChild == getChildAt(mCurrentScreen)) {
814 focusedChild.clearFocus();
815 }
816
817 final int newX = whichScreen * getWidth();
818 final int delta = newX - mScrollX;
819 mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
820 invalidate();
821 }
822
823 void startDrag(CellLayout.CellInfo cellInfo) {
824 View child = cellInfo.cell;
825
826 // Make sure the drag was started by a long press as opposed to a long click.
827 // Note that Search takes focus when clicked rather than entering touch mode
828 if (!child.isInTouchMode() && !(child instanceof Search)) {
829 return;
830 }
831
832 mDragInfo = cellInfo;
833 mDragInfo.screen = mCurrentScreen;
834
835 CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
836
837 current.onDragChild(child);
838 mDragger.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
839 invalidate();
840 }
841
842 @Override
843 protected Parcelable onSaveInstanceState() {
844 final SavedState state = new SavedState(super.onSaveInstanceState());
845 state.currentScreen = mCurrentScreen;
846 return state;
847 }
848
849 @Override
850 protected void onRestoreInstanceState(Parcelable state) {
851 SavedState savedState = (SavedState) state;
852 super.onRestoreInstanceState(savedState.getSuperState());
853 if (savedState.currentScreen != -1) {
854 mCurrentScreen = savedState.currentScreen;
855 Launcher.setScreen(mCurrentScreen);
856 }
857 }
858
859 void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo) {
860 addApplicationShortcut(info, cellInfo, false);
861 }
862
863 void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo,
864 boolean insertAtFirst) {
865 final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
866 final int[] result = new int[2];
867
868 layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
869 onDropExternal(result[0], result[1], info, layout, insertAtFirst);
870 }
871
872 public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
873 final CellLayout cellLayout = (CellLayout) getChildAt(mCurrentScreen);
874 if (source != this) {
875 onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
876 } else {
877 // Move internally
878 if (mDragInfo != null) {
879 final View cell = mDragInfo.cell;
880 if (mCurrentScreen != mDragInfo.screen) {
881 final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
882 originalCellLayout.removeView(cell);
883 cellLayout.addView(cell);
884 }
885 cellLayout.onDropChild(cell, x - xOffset, y - yOffset);
886
887 final ItemInfo info = (ItemInfo)cell.getTag();
888 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
889 LauncherModel.moveItemInDatabase(mLauncher, info,
890 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
891 }
892 }
893 }
894
895 public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
896 Object dragInfo) {
897 }
898
899 public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
900 Object dragInfo) {
901 }
902
903 public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
904 Object dragInfo) {
905 }
906
907 private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
908 onDropExternal(x, y, dragInfo, cellLayout, false);
909 }
910
911 private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout,
912 boolean insertAtFirst) {
913 // Drag from somewhere else
914 ItemInfo info = (ItemInfo) dragInfo;
915
916 View view;
917
918 switch (info.itemType) {
919 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
920 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
921 if (info.container == NO_ID) {
922 // Came from all apps -- make a copy
923 info = new ApplicationInfo((ApplicationInfo) info);
924 }
925 view = mLauncher.createShortcut(R.layout.application, cellLayout,
926 (ApplicationInfo) info);
927 break;
928 case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
929 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
930 (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
931 break;
932 default:
933 throw new IllegalStateException("Unknown item type: " + info.itemType);
934 }
935
936 cellLayout.addView(view, insertAtFirst ? 0 : -1);
937 view.setOnLongClickListener(mLongClickListener);
938 cellLayout.onDropChild(view, x, y);
939 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
940
941 final LauncherModel model = Launcher.getModel();
942 model.addDesktopItem(info);
943 LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
944 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
945 }
946
947 public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
948 Object dragInfo) {
949
950 final CellLayout.CellInfo cellInfo = mDragInfo;
951 int cellHSpan = cellInfo == null ? 1 : cellInfo.spanX;
952 int cellVSpan = cellInfo == null ? 1 : cellInfo.spanY;
953
954 return ((CellLayout) getChildAt(mCurrentScreen)).acceptChildDrop(x - xOffset, y - yOffset,
955 cellHSpan, cellVSpan, cellInfo == null ? null : cellInfo.cell);
956 }
957
958 void setLauncher(Launcher launcher) {
959 mLauncher = launcher;
960 }
961
962 public void setDragger(DragController dragger) {
963 mDragger = dragger;
964 }
965
966 public void onDropCompleted(View target, boolean success) {
967 if (success){
968 if (target != this && mDragInfo != null) {
969 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
970 cellLayout.removeView(mDragInfo.cell);
971 final Object tag = mDragInfo.cell.getTag();
972 Launcher.getModel().removeDesktopItem((ItemInfo) tag);
973 }
974 } else {
975 if (mDragInfo != null) {
976 final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
977 cellLayout.onDropAborted(mDragInfo.cell);
978 }
979 }
980
981 mDragInfo = null;
982 }
983
984 public void scrollLeft() {
985 if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
986 snapToScreen(mCurrentScreen - 1);
987 }
988 }
989
990 public void scrollRight() {
991 if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
992 mScroller.isFinished()) {
993 snapToScreen(mCurrentScreen + 1);
994 }
995 }
996
997 public int getScreenForView(View v) {
998 int result = -1;
999 if (v != null) {
1000 ViewParent vp = v.getParent();
1001 int count = getChildCount();
1002 for (int i = 0; i < count; i++) {
1003 if (vp == getChildAt(i)) {
1004 return i;
1005 }
1006 }
1007 }
1008 return result;
1009 }
1010
1011 /**
1012 * Find a search widget on the given screen
1013 */
1014 private Search findSearchWidget(CellLayout screen) {
1015 final int count = screen.getChildCount();
1016 for (int i = 0; i < count; i++) {
1017 View v = screen.getChildAt(i);
1018 if (v instanceof Search) {
1019 return (Search) v;
1020 }
1021 }
1022 return null;
1023 }
1024
1025 /**
1026 * Focuses on the search widget on the specified screen,
1027 * if there is one. Also clears the current search selection so we don't
1028 */
1029 private boolean focusOnSearch(int screen) {
1030 CellLayout currentScreen = (CellLayout) getChildAt(screen);
1031 final Search searchWidget = findSearchWidget(currentScreen);
1032 if (searchWidget != null) {
1033 // This is necessary when focus on search is requested from the menu
1034 // If the workspace was not in touch mode before the menu is invoked
1035 // and the user clicks "Search" by touching the menu item, the following
1036 // happens:
1037 //
1038 // - We request focus from touch on the search widget
1039 // - The search widget gains focus
1040 // - The window focus comes back to Home's window
1041 // - The touch mode change is propagated to Home's window
1042 // - The search widget is not focusable in touch mode and ViewRoot
1043 // clears its focus
1044 //
1045 // Forcing focusable in touch mode ensures the search widget will
1046 // keep the focus no matter what happens.
1047 //
1048 // Note: the search input field disables focusable in touch mode
1049 // after the window gets the focus back, see SearchAutoCompleteTextView
1050 final SearchAutoCompleteTextView input = searchWidget.getSearchInputField();
1051 input.setFocusableInTouchMode(true);
1052 input.showKeyboardOnNextFocus();
1053
1054 if (isInTouchMode()) {
1055 searchWidget.requestFocusFromTouch();
1056 } else {
1057 searchWidget.requestFocus();
1058 }
1059 searchWidget.clearQuery();
1060 return true;
1061 }
1062 return false;
1063 }
1064
1065 /**
1066 * Snap to the nearest screen with a search widget and give it focus
1067 *
1068 * @return True if a search widget was found
1069 */
1070 public boolean snapToSearch() {
1071 // The screen we are searching
1072 int current = mCurrentScreen;
1073
1074 // first position scanned so far
1075 int first = current;
1076
1077 // last position scanned so far
1078 int last = current;
1079
1080 // True if we should move down on the next iteration
1081 boolean next = false;
1082
1083 // True when we have looked at the first item in the data
1084 boolean hitFirst;
1085
1086 // True when we have looked at the last item in the data
1087 boolean hitLast;
1088
1089 final int count = getChildCount();
1090
1091 while (true) {
1092 if (focusOnSearch(current)) {
1093 return true;
1094 }
1095
1096 hitLast = last == count - 1;
1097 hitFirst = first == 0;
1098
1099 if (hitLast && hitFirst) {
1100 // Looked at everything
1101 break;
1102 }
1103
1104 if (hitFirst || (next && !hitLast)) {
1105 // Either we hit the top, or we are trying to move down
1106 last++;
1107 current = last;
1108 // Try going up next time
1109 next = false;
1110 } else {
1111 // Either we hit the bottom, or we are trying to move up
1112 first--;
1113 current = first;
1114 // Try going down next time
1115 next = true;
1116 }
1117
1118 }
1119 return false;
1120 }
1121
1122 public Folder getFolderForTag(Object tag) {
1123 int screenCount = getChildCount();
1124 for (int screen = 0; screen < screenCount; screen++) {
1125 CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1126 int count = currentScreen.getChildCount();
1127 for (int i = 0; i < count; i++) {
1128 View child = currentScreen.getChildAt(i);
1129 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1130 if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1131 Folder f = (Folder) child;
1132 if (f.getInfo() == tag) {
1133 return f;
1134 }
1135 }
1136 }
1137 }
1138 return null;
1139 }
1140
1141 public View getViewForTag(Object tag) {
1142 int screenCount = getChildCount();
1143 for (int screen = 0; screen < screenCount; screen++) {
1144 CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1145 int count = currentScreen.getChildCount();
1146 for (int i = 0; i < count; i++) {
1147 View child = currentScreen.getChildAt(i);
1148 if (child.getTag() == tag) {
1149 return child;
1150 }
1151 }
1152 }
1153 return null;
1154 }
1155
1156 /**
1157 * Unlocks the SlidingDrawer so that touch events are processed.
1158 *
1159 * @see #lock()
1160 */
1161 public void unlock() {
1162 mLocked = false;
1163 }
1164
1165 /**
1166 * Locks the SlidingDrawer so that touch events are ignores.
1167 *
1168 * @see #unlock()
1169 */
1170 public void lock() {
1171 mLocked = true;
1172 }
1173
1174 /**
1175 * @return True is long presses are still allowed for the current touch
1176 */
1177 public boolean allowLongPress() {
1178 return mAllowLongPress;
1179 }
1180
1181 void removeShortcutsForPackage(String packageName) {
1182 final ArrayList<View> childrenToRemove = new ArrayList<View>();
1183 final LauncherModel model = Launcher.getModel();
1184 final int count = getChildCount();
1185 for (int i = 0; i < count; i++) {
1186 final CellLayout layout = (CellLayout) getChildAt(i);
1187 int childCount = layout.getChildCount();
1188 childrenToRemove.clear();
1189 for (int j = 0; j < childCount; j++) {
1190 final View view = layout.getChildAt(j);
1191 Object tag = view.getTag();
1192 if (tag instanceof ApplicationInfo) {
1193 ApplicationInfo info = (ApplicationInfo) tag;
1194 // We need to check for ACTION_MAIN otherwise getComponent() might
1195 // return null for some shortcuts (for instance, for shortcuts to
1196 // web pages.)
1197 final Intent intent = info.intent;
1198 final ComponentName name = intent.getComponent();
1199 if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1200 name != null && packageName.equals(name.getPackageName())) {
1201 model.removeDesktopItem(info);
1202 LauncherModel.deleteItemFromDatabase(mLauncher, info);
1203 childrenToRemove.add(view);
1204 }
1205 }
1206 }
1207 childCount = childrenToRemove.size();
1208 for (int j = 0; j < childCount; j++) {
1209 layout.removeViewInLayout(childrenToRemove.get(j));
1210 }
1211 if (childCount > 0) {
1212 layout.requestLayout();
1213 layout.invalidate();
1214 }
1215 }
1216 }
1217
1218 // TODO: remove gadgets when gadgetmanager tells us they're gone
1219// void removeGadgetsForProvider() {
1220// }
1221
1222 void moveToDefaultScreen() {
1223 snapToScreen(mDefaultScreen);
1224 getChildAt(mDefaultScreen).requestFocus();
1225 }
1226
1227 public static class SavedState extends BaseSavedState {
1228 int currentScreen = -1;
1229
1230 SavedState(Parcelable superState) {
1231 super(superState);
1232 }
1233
1234 private SavedState(Parcel in) {
1235 super(in);
1236 currentScreen = in.readInt();
1237 }
1238
1239 @Override
1240 public void writeToParcel(Parcel out, int flags) {
1241 super.writeToParcel(out, flags);
1242 out.writeInt(currentScreen);
1243 }
1244
1245 public static final Parcelable.Creator<SavedState> CREATOR =
1246 new Parcelable.Creator<SavedState>() {
1247 public SavedState createFromParcel(Parcel in) {
1248 return new SavedState(in);
1249 }
1250
1251 public SavedState[] newArray(int size) {
1252 return new SavedState[size];
1253 }
1254 };
1255 }
1256}