Workspace.java revision a9c28f6f97a4b34d9527c786e3aa8f02d92d9e07
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
17package com.android.launcher2;
18
19import android.app.WallpaperManager;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ComponentName;
23import android.content.res.TypedArray;
24import android.content.pm.PackageManager;
25import android.graphics.Canvas;
26import android.graphics.RectF;
27import android.graphics.Rect;
28import android.graphics.drawable.Drawable;
29import android.util.AttributeSet;
30import android.view.MotionEvent;
31import android.view.VelocityTracker;
32import android.view.View;
33import android.view.ViewConfiguration;
34import android.view.ViewGroup;
35import android.view.ViewParent;
36import android.widget.Scroller;
37import android.widget.TextView;
38import android.os.Parcelable;
39import android.os.Parcel;
40
41import java.util.ArrayList;
42
43/**
44 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
45 * screen contains a number of icons, folders or widgets the user can interact with.
46 * A workspace is meant to be used with a fixed width only.
47 */
48public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
49    private static final int INVALID_SCREEN = -1;
50
51    /**
52     * The velocity at which a fling gesture will cause us to snap to the next screen
53     */
54    private static final int SNAP_VELOCITY = 1000;
55
56    private final WallpaperManager mWallpaperManager;
57
58    private int mDefaultScreen;
59
60    private boolean mFirstLayout = true;
61
62    private int mCurrentScreen;
63    private int mNextScreen = INVALID_SCREEN;
64    private Scroller mScroller;
65    private VelocityTracker mVelocityTracker;
66
67    /**
68     * CellInfo for the cell that is currently being dragged
69     */
70    private CellLayout.CellInfo mDragInfo;
71
72    /**
73     * Target drop area calculated during last acceptDrop call.
74     */
75    private int[] mTargetCell = null;
76
77    private float mLastMotionX;
78    private float mLastMotionY;
79
80    private final static int TOUCH_STATE_REST = 0;
81    private final static int TOUCH_STATE_SCROLLING = 1;
82
83    private int mTouchState = TOUCH_STATE_REST;
84
85    private OnLongClickListener mLongClickListener;
86
87    private Launcher mLauncher;
88    private DragController mDragController;
89
90    /**
91     * Cache of vacant cells, used during drag events and invalidated as needed.
92     */
93    private CellLayout.CellInfo mVacantCache = null;
94
95    private int[] mTempCell = new int[2];
96    private int[] mTempEstimate = new int[2];
97
98    private boolean mAllowLongPress = true;
99
100    private int mTouchSlop;
101    private int mMaximumVelocity;
102
103    final Rect mDrawerBounds = new Rect();
104    final Rect mClipBounds = new Rect();
105    int mDrawerContentHeight;
106    int mDrawerContentWidth;
107
108    /**
109     * Used to inflate the Workspace from XML.
110     *
111     * @param context The application's context.
112     * @param attrs The attribtues set containing the Workspace's customization values.
113     */
114    public Workspace(Context context, AttributeSet attrs) {
115        this(context, attrs, 0);
116    }
117
118    /**
119     * Used to inflate the Workspace from XML.
120     *
121     * @param context The application's context.
122     * @param attrs The attribtues set containing the Workspace's customization values.
123     * @param defStyle Unused.
124     */
125    public Workspace(Context context, AttributeSet attrs, int defStyle) {
126        super(context, attrs, defStyle);
127
128        mWallpaperManager = WallpaperManager.getInstance(context);
129
130        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
131        mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
132        a.recycle();
133
134        initWorkspace();
135    }
136
137    /**
138     * Initializes various states for this workspace.
139     */
140    private void initWorkspace() {
141        mScroller = new Scroller(getContext());
142        mCurrentScreen = mDefaultScreen;
143        Launcher.setScreen(mCurrentScreen);
144
145        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
146        mTouchSlop = configuration.getScaledTouchSlop();
147        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
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     * Returns how many screens there are.
241     */
242    int getScreenCount() {
243        return getChildCount();
244    }
245
246    /**
247     * Computes a bounding rectangle for a range of cells
248     *
249     * @param cellX X coordinate of upper left corner expressed as a cell position
250     * @param cellY Y coordinate of upper left corner expressed as a cell position
251     * @param cellHSpan Width in cells
252     * @param cellVSpan Height in cells
253     * @param rect Rectnagle into which to put the results
254     */
255    public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF rect) {
256        ((CellLayout)getChildAt(mCurrentScreen)).cellToRect(cellX, cellY,
257                cellHSpan, cellVSpan, rect);
258    }
259
260    /**
261     * Sets the current screen.
262     *
263     * @param currentScreen
264     */
265    void setCurrentScreen(int currentScreen) {
266        clearVacantCache();
267        mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
268        scrollTo(mCurrentScreen * getWidth(), 0);
269        updateWallpaperOffset();
270        invalidate();
271    }
272
273    /**
274     * Shows the default screen (defined by the firstScreen attribute in XML.)
275     */
276    void showDefaultScreen() {
277        setCurrentScreen(mDefaultScreen);
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     */
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     */
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     */
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     */
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
362    void addWidget(View view, Widget widget) {
363        addInScreen(view, widget.screen, widget.cellX, widget.cellY, widget.spanX,
364                widget.spanY, false);
365    }
366
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
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
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     */
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     */
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     */
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
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
453    private void updateWallpaperOffset() {
454        updateWallpaperOffset(getChildAt(getChildCount() - 1).getRight() - (mRight - mLeft));
455    }
456
457    private void updateWallpaperOffset(int scrollRange) {
458        mWallpaperManager.setWallpaperOffsets(getWindowToken(), mScrollX / (float) scrollRange, 0);
459    }
460
461    @Override
462    public void computeScroll() {
463        if (mScroller.computeScrollOffset()) {
464            mScrollX = mScroller.getCurrX();
465            mScrollY = mScroller.getCurrY();
466            updateWallpaperOffset();
467            postInvalidate();
468        } else if (mNextScreen != INVALID_SCREEN) {
469            mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
470            Launcher.setScreen(mCurrentScreen);
471            mNextScreen = INVALID_SCREEN;
472            clearChildrenCache();
473        }
474    }
475
476    @Override
477    protected void dispatchDraw(Canvas canvas) {
478        boolean restore = false;
479        int restoreCount = 0;
480
481        // For the fade.  If view gets setAlpha(), use that instead.
482        float scale = mScale;
483        if (scale < 0.999f) {
484            int sx = mScrollX;
485
486            int alpha = (scale < 0.5f) ? (int)(255 * 2 * scale) : 255;
487
488            restoreCount = canvas.saveLayerAlpha(sx, 0, sx+getWidth(), getHeight(), alpha,
489                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
490            restore = true;
491
492            if (scale < 0.999f) {
493                int w = getWidth();
494                w += 2 * mCurrentScreen * w;
495                int dx = w/2;
496                int h = getHeight();
497                int dy = (h/2) - (h/4);
498                canvas.translate(dx, dy);
499                canvas.scale(scale, scale);
500                canvas.translate(-dx, -dy);
501            }
502        }
503
504        // ViewGroup.dispatchDraw() supports many features we don't need:
505        // clip to padding, layout animation, animation listener, disappearing
506        // children, etc. The following implementation attempts to fast-track
507        // the drawing dispatch by drawing only what we know needs to be drawn.
508
509        boolean fastDraw = mTouchState != TOUCH_STATE_SCROLLING && mNextScreen == INVALID_SCREEN
510                && scale > 0.999f;
511        // If we are not scrolling or flinging, draw only the current screen
512        if (fastDraw) {
513            drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
514        } else {
515            final long drawingTime = getDrawingTime();
516            // If we are flinging, draw only the current screen and the target screen
517            if (mNextScreen >= 0 && mNextScreen < getChildCount() &&
518                    Math.abs(mCurrentScreen - mNextScreen) == 1) {
519                drawChild(canvas, getChildAt(mCurrentScreen), drawingTime);
520                drawChild(canvas, getChildAt(mNextScreen), drawingTime);
521            } else {
522                // If we are scrolling, draw all of our children
523                final int count = getChildCount();
524                for (int i = 0; i < count; i++) {
525                    drawChild(canvas, getChildAt(i), drawingTime);
526                }
527            }
528        }
529
530        if (restore) {
531            canvas.restoreToCount(restoreCount);
532        }
533    }
534
535    private float mScale = 1.0f;
536    public void setScale(float scale) {
537        mScale = scale;
538        invalidate();
539    }
540
541    protected void onAttachedToWindow() {
542        super.onAttachedToWindow();
543        mDragController.setWindowToken(getWindowToken());
544    }
545
546    @Override
547    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
548        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
549
550        final int width = MeasureSpec.getSize(widthMeasureSpec);
551        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
552        if (widthMode != MeasureSpec.EXACTLY) {
553            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
554        }
555
556        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
557        if (heightMode != MeasureSpec.EXACTLY) {
558            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
559        }
560
561        // The children are given the same width and height as the workspace
562        final int count = getChildCount();
563        for (int i = 0; i < count; i++) {
564            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
565        }
566
567        if (mFirstLayout) {
568            scrollTo(mCurrentScreen * width, 0);
569            updateWallpaperOffset(width * (getChildCount() - 1));
570            mFirstLayout = false;
571        }
572    }
573
574    @Override
575    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
576        int childLeft = 0;
577
578        final int count = getChildCount();
579        for (int i = 0; i < count; i++) {
580            final View child = getChildAt(i);
581            if (child.getVisibility() != View.GONE) {
582                final int childWidth = child.getMeasuredWidth();
583                child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
584                childLeft += childWidth;
585            }
586        }
587    }
588
589    @Override
590    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
591        int screen = indexOfChild(child);
592        if (screen != mCurrentScreen || !mScroller.isFinished()) {
593            if (!mLauncher.isWorkspaceLocked()) {
594                snapToScreen(screen);
595            }
596            return true;
597        }
598        return false;
599    }
600
601    @Override
602    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
603        if (mLauncher.isAllAppsVisible()) {
604            final Folder openFolder = getOpenFolder();
605            if (openFolder != null) {
606                return openFolder.requestFocus(direction, previouslyFocusedRect);
607            } else {
608                int focusableScreen;
609                if (mNextScreen != INVALID_SCREEN) {
610                    focusableScreen = mNextScreen;
611                } else {
612                    focusableScreen = mCurrentScreen;
613                }
614                getChildAt(focusableScreen).requestFocus(direction, previouslyFocusedRect);
615            }
616        }
617        return false;
618    }
619
620    @Override
621    public boolean dispatchUnhandledMove(View focused, int direction) {
622        if (direction == View.FOCUS_LEFT) {
623            if (getCurrentScreen() > 0) {
624                snapToScreen(getCurrentScreen() - 1);
625                return true;
626            }
627        } else if (direction == View.FOCUS_RIGHT) {
628            if (getCurrentScreen() < getChildCount() - 1) {
629                snapToScreen(getCurrentScreen() + 1);
630                return true;
631            }
632        }
633        return super.dispatchUnhandledMove(focused, direction);
634    }
635
636    @Override
637    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
638        if (mLauncher.isAllAppsVisible()) {
639            final Folder openFolder = getOpenFolder();
640            if (openFolder == null) {
641                getChildAt(mCurrentScreen).addFocusables(views, direction);
642                if (direction == View.FOCUS_LEFT) {
643                    if (mCurrentScreen > 0) {
644                        getChildAt(mCurrentScreen - 1).addFocusables(views, direction);
645                    }
646                } else if (direction == View.FOCUS_RIGHT){
647                    if (mCurrentScreen < getChildCount() - 1) {
648                        getChildAt(mCurrentScreen + 1).addFocusables(views, direction);
649                    }
650                }
651            } else {
652                openFolder.addFocusables(views, direction);
653            }
654        }
655    }
656
657    @Override
658    public boolean onInterceptTouchEvent(MotionEvent ev) {
659        if (mLauncher.isWorkspaceLocked() || !mLauncher.isAllAppsVisible()) {
660            return false; // We don't want the events.  Let them fall through to the all apps view.
661        }
662
663        /*
664         * This method JUST determines whether we want to intercept the motion.
665         * If we return true, onTouchEvent will be called and we do the actual
666         * scrolling there.
667         */
668
669        /*
670         * Shortcut the most recurring case: the user is in the dragging
671         * state and he is moving his finger.  We want to intercept this
672         * motion.
673         */
674        final int action = ev.getAction();
675        if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {
676            return true;
677        }
678
679        final float x = ev.getX();
680        final float y = ev.getY();
681
682        switch (action) {
683            case MotionEvent.ACTION_MOVE:
684                /*
685                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
686                 * whether the user has moved far enough from his original down touch.
687                 */
688
689                /*
690                 * Locally do absolute value. mLastMotionX is set to the y value
691                 * of the down event.
692                 */
693                final int xDiff = (int) Math.abs(x - mLastMotionX);
694                final int yDiff = (int) Math.abs(y - mLastMotionY);
695
696                final int touchSlop = mTouchSlop;
697                boolean xMoved = xDiff > touchSlop;
698                boolean yMoved = yDiff > touchSlop;
699
700                if (xMoved || yMoved) {
701
702                    if (xMoved) {
703                        // Scroll if the user moved far enough along the X axis
704                        mTouchState = TOUCH_STATE_SCROLLING;
705                        enableChildrenCache();
706                    }
707                    // Either way, cancel any pending longpress
708                    if (mAllowLongPress) {
709                        mAllowLongPress = false;
710                        // Try canceling the long press. It could also have been scheduled
711                        // by a distant descendant, so use the mAllowLongPress flag to block
712                        // everything
713                        final View currentScreen = getChildAt(mCurrentScreen);
714                        currentScreen.cancelLongPress();
715                    }
716                }
717                break;
718
719            case MotionEvent.ACTION_DOWN:
720                // Remember location of down touch
721                mLastMotionX = x;
722                mLastMotionY = y;
723                mAllowLongPress = true;
724
725                /*
726                 * If being flinged and user touches the screen, initiate drag;
727                 * otherwise don't.  mScroller.isFinished should be false when
728                 * being flinged.
729                 */
730                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
731                break;
732
733            case MotionEvent.ACTION_CANCEL:
734            case MotionEvent.ACTION_UP:
735                // Release the drag
736                clearChildrenCache();
737                mTouchState = TOUCH_STATE_REST;
738                mAllowLongPress = false;
739                break;
740        }
741
742        /*
743         * The only time we want to intercept motion events is if we are in the
744         * drag mode.
745         */
746        return mTouchState != TOUCH_STATE_REST;
747    }
748
749    void enableChildrenCache() {
750        final int count = getChildCount();
751        for (int i = 0; i < count; i++) {
752            final CellLayout layout = (CellLayout) getChildAt(i);
753            layout.setChildrenDrawnWithCacheEnabled(true);
754            layout.setChildrenDrawingCacheEnabled(true);
755        }
756    }
757
758    void clearChildrenCache() {
759        final int count = getChildCount();
760        for (int i = 0; i < count; i++) {
761            final CellLayout layout = (CellLayout) getChildAt(i);
762            layout.setChildrenDrawnWithCacheEnabled(false);
763        }
764    }
765
766    @Override
767    public boolean onTouchEvent(MotionEvent ev) {
768
769        if (mLauncher.isWorkspaceLocked() || !mLauncher.isAllAppsVisible()) {
770            return false; // We don't want the events.  Let them fall through to the all apps view.
771        }
772
773        if (mVelocityTracker == null) {
774            mVelocityTracker = VelocityTracker.obtain();
775        }
776        mVelocityTracker.addMovement(ev);
777
778        final int action = ev.getAction();
779        final float x = ev.getX();
780
781        switch (action) {
782        case MotionEvent.ACTION_DOWN:
783            /*
784             * If being flinged and user touches, stop the fling. isFinished
785             * will be false if being flinged.
786             */
787            if (!mScroller.isFinished()) {
788                mScroller.abortAnimation();
789            }
790
791            // Remember where the motion event started
792            mLastMotionX = x;
793            break;
794        case MotionEvent.ACTION_MOVE:
795            if (mTouchState == TOUCH_STATE_SCROLLING) {
796                // Scroll to follow the motion event
797                final int deltaX = (int) (mLastMotionX - x);
798                mLastMotionX = x;
799
800                if (deltaX < 0) {
801                    if (mScrollX > 0) {
802                        scrollBy(Math.max(-mScrollX, deltaX), 0);
803                        updateWallpaperOffset();
804                    }
805                } else if (deltaX > 0) {
806                    final int availableToScroll = getChildAt(getChildCount() - 1).getRight() -
807                            mScrollX - getWidth();
808                    if (availableToScroll > 0) {
809                        scrollBy(Math.min(availableToScroll, deltaX), 0);
810                        updateWallpaperOffset();
811                    }
812                }
813            }
814            break;
815        case MotionEvent.ACTION_UP:
816            if (mTouchState == TOUCH_STATE_SCROLLING) {
817                final VelocityTracker velocityTracker = mVelocityTracker;
818                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
819                int velocityX = (int) velocityTracker.getXVelocity();
820
821                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
822                    // Fling hard enough to move left
823                    snapToScreen(mCurrentScreen - 1);
824                } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
825                    // Fling hard enough to move right
826                    snapToScreen(mCurrentScreen + 1);
827                } else {
828                    snapToDestination();
829                }
830
831                if (mVelocityTracker != null) {
832                    mVelocityTracker.recycle();
833                    mVelocityTracker = null;
834                }
835            }
836            mTouchState = TOUCH_STATE_REST;
837            break;
838        case MotionEvent.ACTION_CANCEL:
839            mTouchState = TOUCH_STATE_REST;
840        }
841
842        return true;
843    }
844
845    private void snapToDestination() {
846        final int screenWidth = getWidth();
847        final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
848
849        snapToScreen(whichScreen);
850    }
851
852    void snapToScreen(int whichScreen) {
853        if (!mScroller.isFinished()) return;
854
855        clearVacantCache();
856        enableChildrenCache();
857
858        whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
859        boolean changingScreens = whichScreen != mCurrentScreen;
860
861        mNextScreen = whichScreen;
862
863        View focusedChild = getFocusedChild();
864        if (focusedChild != null && changingScreens && focusedChild == getChildAt(mCurrentScreen)) {
865            focusedChild.clearFocus();
866        }
867
868        final int newX = whichScreen * getWidth();
869        final int delta = newX - mScrollX;
870        mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
871        invalidate();
872    }
873
874    void startDrag(CellLayout.CellInfo cellInfo) {
875        View child = cellInfo.cell;
876
877        // Make sure the drag was started by a long press as opposed to a long click.
878        // Note that Search takes focus when clicked rather than entering touch mode
879        if (!child.isInTouchMode() && !(child instanceof Search)) {
880            return;
881        }
882
883        mDragInfo = cellInfo;
884        mDragInfo.screen = mCurrentScreen;
885
886        CellLayout current = ((CellLayout) getChildAt(mCurrentScreen));
887
888        current.onDragChild(child);
889        mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
890        invalidate();
891    }
892
893    @Override
894    protected Parcelable onSaveInstanceState() {
895        final SavedState state = new SavedState(super.onSaveInstanceState());
896        state.currentScreen = mCurrentScreen;
897        return state;
898    }
899
900    @Override
901    protected void onRestoreInstanceState(Parcelable state) {
902        SavedState savedState = (SavedState) state;
903        super.onRestoreInstanceState(savedState.getSuperState());
904        if (savedState.currentScreen != -1) {
905            mCurrentScreen = savedState.currentScreen;
906            Launcher.setScreen(mCurrentScreen);
907        }
908    }
909
910    void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo) {
911        addApplicationShortcut(info, cellInfo, false);
912    }
913
914    void addApplicationShortcut(ApplicationInfo info, CellLayout.CellInfo cellInfo,
915            boolean insertAtFirst) {
916        final CellLayout layout = (CellLayout) getChildAt(cellInfo.screen);
917        final int[] result = new int[2];
918
919        layout.cellToPoint(cellInfo.cellX, cellInfo.cellY, result);
920        onDropExternal(result[0], result[1], info, layout, insertAtFirst);
921    }
922
923    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
924            DragView dragView, Object dragInfo) {
925        final CellLayout cellLayout = getCurrentDropLayout();
926        if (source != this) {
927            onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
928        } else {
929            // Move internally
930            if (mDragInfo != null) {
931                final View cell = mDragInfo.cell;
932                int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
933                if (index != mDragInfo.screen) {
934                    final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
935                    originalCellLayout.removeView(cell);
936                    cellLayout.addView(cell);
937                }
938                mTargetCell = estimateDropCell(x - xOffset, y - yOffset,
939                        mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
940                cellLayout.onDropChild(cell, mTargetCell);
941
942                final ItemInfo info = (ItemInfo)cell.getTag();
943                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
944                LauncherModel.moveItemInDatabase(mLauncher, info,
945                        LauncherSettings.Favorites.CONTAINER_DESKTOP, index, lp.cellX, lp.cellY);
946            }
947        }
948    }
949
950    public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
951            DragView dragView, Object dragInfo) {
952        clearVacantCache();
953    }
954
955    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
956            DragView dragView, Object dragInfo) {
957    }
958
959    public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
960            DragView dragView, Object dragInfo) {
961        clearVacantCache();
962    }
963
964    private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
965        onDropExternal(x, y, dragInfo, cellLayout, false);
966    }
967
968    private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout,
969            boolean insertAtFirst) {
970        // Drag from somewhere else
971        ItemInfo info = (ItemInfo) dragInfo;
972
973        View view;
974
975        switch (info.itemType) {
976        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
977        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
978            if (info.container == NO_ID) {
979                // Came from all apps -- make a copy
980                info = new ApplicationInfo((ApplicationInfo) info);
981            }
982            view = mLauncher.createShortcut(R.layout.application, cellLayout,
983                    (ApplicationInfo) info);
984            break;
985        case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
986            view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher,
987                    (ViewGroup) getChildAt(mCurrentScreen), ((UserFolderInfo) info));
988            break;
989        default:
990            throw new IllegalStateException("Unknown item type: " + info.itemType);
991        }
992
993        cellLayout.addView(view, insertAtFirst ? 0 : -1);
994        view.setOnLongClickListener(mLongClickListener);
995        if (view instanceof DropTarget) {
996            mDragController.addDropTarget((DropTarget)view);
997        }
998
999        mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
1000        cellLayout.onDropChild(view, mTargetCell);
1001        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
1002
1003        LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
1004                LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
1005    }
1006
1007    /**
1008     * Return the current {@link CellLayout}, correctly picking the destination
1009     * screen while a scroll is in progress.
1010     */
1011    private CellLayout getCurrentDropLayout() {
1012        int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
1013        return (CellLayout) getChildAt(index);
1014    }
1015
1016    /**
1017     * {@inheritDoc}
1018     */
1019    public boolean acceptDrop(DragSource source, int x, int y,
1020            int xOffset, int yOffset, DragView dragView, Object dragInfo) {
1021        final CellLayout layout = getCurrentDropLayout();
1022        final CellLayout.CellInfo cellInfo = mDragInfo;
1023        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1024        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1025
1026        if (mVacantCache == null) {
1027            final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1028            mVacantCache = layout.findAllVacantCells(null, ignoreView);
1029        }
1030
1031        return mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false);
1032    }
1033
1034    /**
1035     * {@inheritDoc}
1036     */
1037    public Rect estimateDropLocation(DragSource source, int x, int y,
1038            int xOffset, int yOffset, DragView dragView, Object dragInfo, Rect recycle) {
1039        final CellLayout layout = getCurrentDropLayout();
1040
1041        final CellLayout.CellInfo cellInfo = mDragInfo;
1042        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
1043        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
1044        final View ignoreView = cellInfo == null ? null : cellInfo.cell;
1045
1046        final Rect location = recycle != null ? recycle : new Rect();
1047
1048        // Find drop cell and convert into rectangle
1049        int[] dropCell = estimateDropCell(x - xOffset, y - yOffset,
1050                spanX, spanY, ignoreView, layout, mTempCell);
1051
1052        if (dropCell == null) {
1053            return null;
1054        }
1055
1056        layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
1057        location.left = mTempEstimate[0];
1058        location.top = mTempEstimate[1];
1059
1060        layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
1061        location.right = mTempEstimate[0];
1062        location.bottom = mTempEstimate[1];
1063
1064        return location;
1065    }
1066
1067    /**
1068     * Calculate the nearest cell where the given object would be dropped.
1069     */
1070    private int[] estimateDropCell(int pixelX, int pixelY,
1071            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
1072        // Create vacant cell cache if none exists
1073        if (mVacantCache == null) {
1074            mVacantCache = layout.findAllVacantCells(null, ignoreView);
1075        }
1076
1077        // Find the best target drop location
1078        return layout.findNearestVacantArea(pixelX, pixelY,
1079                spanX, spanY, mVacantCache, recycle);
1080    }
1081
1082    void setLauncher(Launcher launcher) {
1083        mLauncher = launcher;
1084    }
1085
1086    public void setDragController(DragController dragController) {
1087        mDragController = dragController;
1088    }
1089
1090    public void onDropCompleted(View target, boolean success) {
1091        if (success){
1092            if (target != this && mDragInfo != null) {
1093                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1094                cellLayout.removeView(mDragInfo.cell);
1095                if (mDragInfo.cell instanceof DropTarget) {
1096                    mDragController.removeDropTarget((DropTarget)mDragInfo.cell);
1097                }
1098                final Object tag = mDragInfo.cell.getTag();
1099            }
1100        } else {
1101            if (mDragInfo != null) {
1102                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
1103                cellLayout.onDropAborted(mDragInfo.cell);
1104            }
1105        }
1106
1107        mDragInfo = null;
1108    }
1109
1110    public void scrollLeft() {
1111        clearVacantCache();
1112        if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
1113            snapToScreen(mCurrentScreen - 1);
1114        }
1115    }
1116
1117    public void scrollRight() {
1118        clearVacantCache();
1119        if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
1120                mScroller.isFinished()) {
1121            snapToScreen(mCurrentScreen + 1);
1122        }
1123    }
1124
1125    public int getScreenForView(View v) {
1126        int result = -1;
1127        if (v != null) {
1128            ViewParent vp = v.getParent();
1129            int count = getChildCount();
1130            for (int i = 0; i < count; i++) {
1131                if (vp == getChildAt(i)) {
1132                    return i;
1133                }
1134            }
1135        }
1136        return result;
1137    }
1138
1139    /**
1140     * Find a search widget on the given screen
1141     */
1142    private Search findSearchWidget(CellLayout screen) {
1143        final int count = screen.getChildCount();
1144        for (int i = 0; i < count; i++) {
1145            View v = screen.getChildAt(i);
1146            if (v instanceof Search) {
1147                return (Search) v;
1148            }
1149        }
1150        return null;
1151    }
1152
1153    /**
1154     * Gets the first search widget on the current screen, if there is one.
1155     * Returns <code>null</code> otherwise.
1156     */
1157    public Search findSearchWidgetOnCurrentScreen() {
1158        CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
1159        return findSearchWidget(currentScreen);
1160    }
1161
1162    public Folder getFolderForTag(Object tag) {
1163        int screenCount = getChildCount();
1164        for (int screen = 0; screen < screenCount; screen++) {
1165            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1166            int count = currentScreen.getChildCount();
1167            for (int i = 0; i < count; i++) {
1168                View child = currentScreen.getChildAt(i);
1169                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
1170                if (lp.cellHSpan == 4 && lp.cellVSpan == 4 && child instanceof Folder) {
1171                    Folder f = (Folder) child;
1172                    if (f.getInfo() == tag) {
1173                        return f;
1174                    }
1175                }
1176            }
1177        }
1178        return null;
1179    }
1180
1181    public View getViewForTag(Object tag) {
1182        int screenCount = getChildCount();
1183        for (int screen = 0; screen < screenCount; screen++) {
1184            CellLayout currentScreen = ((CellLayout) getChildAt(screen));
1185            int count = currentScreen.getChildCount();
1186            for (int i = 0; i < count; i++) {
1187                View child = currentScreen.getChildAt(i);
1188                if (child.getTag() == tag) {
1189                    return child;
1190                }
1191            }
1192        }
1193        return null;
1194    }
1195
1196    /**
1197     * @return True is long presses are still allowed for the current touch
1198     */
1199    public boolean allowLongPress() {
1200        return mAllowLongPress;
1201    }
1202
1203    /**
1204     * Set true to allow long-press events to be triggered, usually checked by
1205     * {@link Launcher} to accept or block dpad-initiated long-presses.
1206     */
1207    public void setAllowLongPress(boolean allowLongPress) {
1208        mAllowLongPress = allowLongPress;
1209    }
1210
1211    void removeShortcutsForPackage(String packageName) {
1212        final ArrayList<View> childrenToRemove = new ArrayList<View>();
1213        final int count = getChildCount();
1214
1215        for (int i = 0; i < count; i++) {
1216            final CellLayout layout = (CellLayout) getChildAt(i);
1217            int childCount = layout.getChildCount();
1218
1219            childrenToRemove.clear();
1220
1221            for (int j = 0; j < childCount; j++) {
1222                final View view = layout.getChildAt(j);
1223                Object tag = view.getTag();
1224
1225                if (tag instanceof ApplicationInfo) {
1226                    final ApplicationInfo info = (ApplicationInfo) tag;
1227                    // We need to check for ACTION_MAIN otherwise getComponent() might
1228                    // return null for some shortcuts (for instance, for shortcuts to
1229                    // web pages.)
1230                    final Intent intent = info.intent;
1231                    final ComponentName name = intent.getComponent();
1232
1233                    if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1234                            name != null && packageName.equals(name.getPackageName())) {
1235                        LauncherModel.deleteItemFromDatabase(mLauncher, info);
1236                        childrenToRemove.add(view);
1237                    }
1238                } else if (tag instanceof UserFolderInfo) {
1239                    final UserFolderInfo info = (UserFolderInfo) tag;
1240                    final ArrayList<ApplicationInfo> contents = info.contents;
1241                    final ArrayList<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>(1);
1242                    final int contentsCount = contents.size();
1243                    boolean removedFromFolder = false;
1244
1245                    for (int k = 0; k < contentsCount; k++) {
1246                        final ApplicationInfo appInfo = contents.get(k);
1247                        final Intent intent = appInfo.intent;
1248                        final ComponentName name = intent.getComponent();
1249
1250                        if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
1251                                name != null && packageName.equals(name.getPackageName())) {
1252                            toRemove.add(appInfo);
1253                            LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
1254                            removedFromFolder = true;
1255                        }
1256                    }
1257
1258                    contents.removeAll(toRemove);
1259                    if (removedFromFolder) {
1260                        final Folder folder = getOpenFolder();
1261                        if (folder != null) folder.notifyDataSetChanged();
1262                    }
1263                }
1264            }
1265
1266            childCount = childrenToRemove.size();
1267            for (int j = 0; j < childCount; j++) {
1268                View child = childrenToRemove.get(j);
1269                layout.removeViewInLayout(child);
1270                if (child instanceof DropTarget) {
1271                    mDragController.removeDropTarget((DropTarget)child);
1272                }
1273            }
1274
1275            if (childCount > 0) {
1276                layout.requestLayout();
1277                layout.invalidate();
1278            }
1279        }
1280    }
1281
1282    void updateShortcutsForPackage(String packageName) {
1283        final PackageManager pm = mLauncher.getPackageManager();
1284
1285        final int count = getChildCount();
1286        for (int i = 0; i < count; i++) {
1287            final CellLayout layout = (CellLayout) getChildAt(i);
1288            int childCount = layout.getChildCount();
1289            for (int j = 0; j < childCount; j++) {
1290                final View view = layout.getChildAt(j);
1291                Object tag = view.getTag();
1292                if (tag instanceof ApplicationInfo) {
1293                    ApplicationInfo info = (ApplicationInfo) tag;
1294                    // We need to check for ACTION_MAIN otherwise getComponent() might
1295                    // return null for some shortcuts (for instance, for shortcuts to
1296                    // web pages.)
1297                    final Intent intent = info.intent;
1298                    final ComponentName name = intent.getComponent();
1299                    if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
1300                            Intent.ACTION_MAIN.equals(intent.getAction()) && name != null &&
1301                            packageName.equals(name.getPackageName())) {
1302
1303                        final Drawable icon = AppInfoCache.getIconDrawable(pm, info);
1304                        if (icon != null && icon != info.icon) {
1305                            info.icon.setCallback(null);
1306                            info.icon = Utilities.createIconThumbnail(icon, mContext);
1307                            info.filtered = true;
1308                            ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(null,
1309                                    info.icon, null, null);
1310                        }
1311                    }
1312                }
1313            }
1314        }
1315    }
1316
1317    void moveToDefaultScreen() {
1318        snapToScreen(mDefaultScreen);
1319        getChildAt(mDefaultScreen).requestFocus();
1320    }
1321
1322    public static class SavedState extends BaseSavedState {
1323        int currentScreen = -1;
1324
1325        SavedState(Parcelable superState) {
1326            super(superState);
1327        }
1328
1329        private SavedState(Parcel in) {
1330            super(in);
1331            currentScreen = in.readInt();
1332        }
1333
1334        @Override
1335        public void writeToParcel(Parcel out, int flags) {
1336            super.writeToParcel(out, flags);
1337            out.writeInt(currentScreen);
1338        }
1339
1340        public static final Parcelable.Creator<SavedState> CREATOR =
1341                new Parcelable.Creator<SavedState>() {
1342            public SavedState createFromParcel(Parcel in) {
1343                return new SavedState(in);
1344            }
1345
1346            public SavedState[] newArray(int size) {
1347                return new SavedState[size];
1348            }
1349        };
1350    }
1351
1352    void show() {
1353        setVisibility(VISIBLE);
1354    }
1355
1356    void hide() {
1357    }
1358}
1359