Workspace.java revision 76128b6359fc430542811fbb32e4a1d593a9ef07
1bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant/*
2bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant * Copyright (C) 2008 The Android Open Source Project
3f5256e16dfc425c1d466f6308d4026d529ce9e0bHoward Hinnant *
4bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant * Licensed under the Apache License, Version 2.0 (the "License");
5b64f8b07c104c6cc986570ac8ee0ed16a9f23976Howard Hinnant * you may not use this file except in compliance with the License.
6b64f8b07c104c6cc986570ac8ee0ed16a9f23976Howard Hinnant * You may obtain a copy of the License at
7bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant *
8bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant *      http://www.apache.org/licenses/LICENSE-2.0
9bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant *
10bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant * Unless required by applicable law or agreed to in writing, software
11bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant * distributed under the License is distributed on an "AS IS" BASIS,
12bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant * See the License for the specific language governing permissions and
14bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant * limitations under the License.
15bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant */
16bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
17bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantpackage com.android.launcher3;
18bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
19bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.animation.Animator;
20bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.animation.Animator.AnimatorListener;
21bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.animation.AnimatorListenerAdapter;
22bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.animation.AnimatorSet;
23bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.animation.LayoutTransition;
24bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.animation.ObjectAnimator;
25bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.animation.PropertyValuesHolder;
26bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.animation.TimeInterpolator;
27bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.animation.ValueAnimator;
28bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.animation.ValueAnimator.AnimatorUpdateListener;
29bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.app.WallpaperManager;
30bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.appwidget.AppWidgetHostView;
31bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.appwidget.AppWidgetProviderInfo;
32bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.content.ComponentName;
33bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.content.Context;
34bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.content.Intent;
35bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.content.SharedPreferences;
36bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.content.res.Resources;
37bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.content.res.TypedArray;
38bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.graphics.Bitmap;
39bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.graphics.Canvas;
40bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.graphics.Matrix;
41bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.graphics.Point;
42bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.graphics.PointF;
43bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.graphics.Rect;
44bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.graphics.Region.Op;
45bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.graphics.drawable.Drawable;
46bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.net.Uri;
47bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.os.IBinder;
48bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.os.Parcelable;
49bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.support.v4.view.ViewCompat;
50bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.util.AttributeSet;
51bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.util.Log;
52bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.util.SparseArray;
53bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.Choreographer;
54bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.Display;
55bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.MotionEvent;
56bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.View;
57bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.ViewGroup;
58bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.View.OnClickListener;
59bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.accessibility.AccessibilityEvent;
60bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.accessibility.AccessibilityManager;
61bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.accessibility.AccessibilityNodeInfo;
62bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.animation.DecelerateInterpolator;
63bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.view.animation.Interpolator;
64bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport android.widget.TextView;
65bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
66bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport com.android.launcher3.FolderIcon.FolderRingAnimator;
67bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport com.android.launcher3.Launcher.CustomContentCallbacks;
68bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport com.android.launcher3.LauncherSettings.Favorites;
69bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
70bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport java.util.ArrayList;
71bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport java.util.HashMap;
72bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport java.util.HashSet;
73bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantimport java.util.Iterator;
74bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
75bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant/**
76bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant * The workspace is a wide area with a wallpaper and a finite number of pages.
77bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant * Each page contains a number of icons, folders or widgets the user can
78bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant * interact with. A workspace is meant to be used with a fixed width only.
79bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant */
80bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnantpublic class Workspace extends SmoothPagedView
81bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant        implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
82bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant        DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
83bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant        Insettable {
84bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final String TAG = "Launcher.Workspace";
85bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
86bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    // Y rotation to apply to the workspace screens
87bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
88bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
89bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
90bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
91bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
92bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
93bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
94bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
95bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
96bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final int BACKGROUND_FADE_OUT_DURATION = 350;
97bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
98bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final int FLING_THRESHOLD_VELOCITY = 500;
99bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
100bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
101bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
102bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    // These animators are used to fade the children's outlines
103bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private ObjectAnimator mChildrenOutlineFadeInAnimation;
104bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private ObjectAnimator mChildrenOutlineFadeOutAnimation;
105bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private float mChildrenOutlineAlpha = 0;
106bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
107bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    // These properties refer to the background protection gradient used for AllApps and Customize
108bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private ValueAnimator mBackgroundFadeInAnimation;
109bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private ValueAnimator mBackgroundFadeOutAnimation;
110bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private Drawable mBackground;
111bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    boolean mDrawBackground = true;
112bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private float mBackgroundAlpha = 0;
113bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
114bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
115bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private long mTouchDownTime = -1;
116bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private long mCustomContentShowTime = -1;
117bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
118bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private LayoutTransition mLayoutTransition;
119bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private final WallpaperManager mWallpaperManager;
120bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private IBinder mWindowToken;
121bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
122bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private int mOriginalDefaultPage;
123bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private int mDefaultPage;
124bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
125bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private ShortcutAndWidgetContainer mDragSourceInternal;
126bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static boolean sAccessibilityEnabled;
127bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
128bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    // The screen id used for the empty screen always present to the right.
129bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private final static long EXTRA_EMPTY_SCREEN_ID = -201;
130bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
131bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
132bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
133bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
134bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
135bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private Runnable mRemoveEmptyScreenRunnable;
136bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
137bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    /**
138bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant     * CellInfo for the cell that is currently being dragged
139bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant     */
140bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private CellLayout.CellInfo mDragInfo;
141bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
142bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    /**
143bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant     * Target drop area calculated during last acceptDrop call.
144bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant     */
145bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private int[] mTargetCell = new int[2];
146bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private int mDragOverX = -1;
147bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private int mDragOverY = -1;
148bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
149bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    static Rect mLandscapeCellLayoutMetrics = null;
150bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    static Rect mPortraitCellLayoutMetrics = null;
151bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
152bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    CustomContentCallbacks mCustomContentCallbacks;
153bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    boolean mCustomContentShowing;
154bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private float mLastCustomContentScrollProgress = -1f;
155bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private String mCustomContentDescription = "";
156bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
157bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    /**
158bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant     * The CellLayout that is currently being dragged over
159bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant     */
160bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private CellLayout mDragTargetLayout = null;
161bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    /**
162bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant     * The CellLayout that we will show as glowing
163bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant     */
164bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private CellLayout mDragOverlappingLayout = null;
165bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
166bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    /**
167bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant     * The CellLayout which will be dropped to
168bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant     */
169bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private CellLayout mDropToLayout = null;
170bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
171bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private Launcher mLauncher;
172bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private IconCache mIconCache;
173bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private DragController mDragController;
174bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
175bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    // These are temporary variables to prevent having to allocate a new object just to
176bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
177bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private int[] mTempCell = new int[2];
178bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private int[] mTempPt = new int[2];
179bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private int[] mTempEstimate = new int[2];
180bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private float[] mDragViewVisualCenter = new float[2];
181bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private float[] mTempCellLayoutCenterCoordinates = new float[2];
182bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private Matrix mTempInverseMatrix = new Matrix();
183bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
184bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private SpringLoadedDragController mSpringLoadedDragController;
185bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private float mSpringLoadedShrinkFactor;
186bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private float mOverviewModeShrinkFactor;
187bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
188bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    // State variable that indicates whether the pages are small (ie when you're
189bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    // in all apps or customize mode)
190bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
191bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    enum State { NORMAL, SPRING_LOADED, SMALL, OVERVIEW};
192bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private State mState = State.NORMAL;
193bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private boolean mIsSwitchingState = false;
194bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
195bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    boolean mAnimatingViewIntoPlace = false;
196bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    boolean mIsDragOccuring = false;
197bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    boolean mChildrenLayersEnabled = true;
198bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
199bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private boolean mStripScreensOnPageStopMoving = false;
200bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
201bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    /** Is the user is dragging an item near the edge of a page? */
202bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private boolean mInScrollArea = false;
203bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
204bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private HolographicOutlineHelper mOutlineHelper;
205bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private Bitmap mDragOutline = null;
206bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private final Rect mTempRect = new Rect();
207bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private final int[] mTempXY = new int[2];
208bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private int[] mTempVisiblePagesRange = new int[2];
209bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private boolean mOverscrollTransformsSet;
210bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private float mLastOverscrollPivotX;
211bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    public static final int DRAG_BITMAP_PADDING = 2;
212bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private boolean mWorkspaceFadeInAdjacentScreens;
213bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
214bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    WallpaperOffsetInterpolator mWallpaperOffset;
215bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private Runnable mDelayedResizeRunnable;
216bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private Runnable mDelayedSnapToPageRunnable;
217bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private Point mDisplaySize = new Point();
218bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private int mCameraDistance;
219bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant
220bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
221bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final int FOLDER_CREATION_TIMEOUT = 0;
222bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private static final int REORDER_TIMEOUT = 250;
223bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private final Alarm mFolderCreationAlarm = new Alarm();
224bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private final Alarm mReorderAlarm = new Alarm();
225bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private FolderRingAnimator mDragFolderRingAnimator = null;
226bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private FolderIcon mDragOverFolderIcon = null;
227bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private boolean mCreateUserFolderOnDrop = false;
228bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private boolean mAddToExistingFolderOnDrop = false;
229bc8d3f97eb5c958007f2713238472e0c1c8fe02Howard Hinnant    private DropTarget.DragEnforcer mDragEnforcer;
2300e20cae1a5be18fba591cd884aa2a389b66a3f49Howard Hinnant    private float mMaxDistanceForFolderCreation;
231
232    // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
233    private float mXDown;
234    private float mYDown;
235    final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
236    final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
237    final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
238
239    // Relating to the animation of items being dropped externally
240    public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
241    public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
242    public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
243    public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
244    public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
245
246    // Related to dragging, folder creation and reordering
247    private static final int DRAG_MODE_NONE = 0;
248    private static final int DRAG_MODE_CREATE_FOLDER = 1;
249    private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
250    private static final int DRAG_MODE_REORDER = 3;
251    private int mDragMode = DRAG_MODE_NONE;
252    private int mLastReorderX = -1;
253    private int mLastReorderY = -1;
254
255    private SparseArray<Parcelable> mSavedStates;
256    private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
257
258    // These variables are used for storing the initial and final values during workspace animations
259    private int mSavedScrollX;
260    private float mSavedRotationY;
261    private float mSavedTranslationX;
262
263    private float mCurrentScale;
264    private float mNewScale;
265    private float[] mOldBackgroundAlphas;
266    private float[] mOldAlphas;
267    private float[] mNewBackgroundAlphas;
268    private float[] mNewAlphas;
269    private int mLastChildCount = -1;
270    private float mTransitionProgress;
271
272    private Runnable mDeferredAction;
273    private boolean mDeferDropAfterUninstall;
274    private boolean mUninstallSuccessful;
275
276    private final Runnable mBindPages = new Runnable() {
277        @Override
278        public void run() {
279            mLauncher.getModel().bindRemainingSynchronousPages();
280        }
281    };
282
283    /**
284     * Used to inflate the Workspace from XML.
285     *
286     * @param context The application's context.
287     * @param attrs The attributes set containing the Workspace's customization values.
288     */
289    public Workspace(Context context, AttributeSet attrs) {
290        this(context, attrs, 0);
291    }
292
293    /**
294     * Used to inflate the Workspace from XML.
295     *
296     * @param context The application's context.
297     * @param attrs The attributes set containing the Workspace's customization values.
298     * @param defStyle Unused.
299     */
300    public Workspace(Context context, AttributeSet attrs, int defStyle) {
301        super(context, attrs, defStyle);
302        mContentIsRefreshable = false;
303
304        mOutlineHelper = HolographicOutlineHelper.obtain(context);
305
306        mDragEnforcer = new DropTarget.DragEnforcer(context);
307        // With workspace, data is available straight from the get-go
308        setDataIsReady();
309
310        mLauncher = (Launcher) context;
311        final Resources res = getResources();
312        mWorkspaceFadeInAdjacentScreens = LauncherAppState.getInstance().getDynamicGrid().
313                getDeviceProfile().shouldFadeAdjacentWorkspaceScreens();
314        mFadeInAdjacentScreens = false;
315        mWallpaperManager = WallpaperManager.getInstance(context);
316
317        LauncherAppState app = LauncherAppState.getInstance();
318        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
319        TypedArray a = context.obtainStyledAttributes(attrs,
320                R.styleable.Workspace, defStyle, 0);
321        mSpringLoadedShrinkFactor =
322            res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
323        mOverviewModeShrinkFactor = grid.getOverviewModeScale();
324        mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
325        mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
326        a.recycle();
327
328        setOnHierarchyChangeListener(this);
329        setHapticFeedbackEnabled(false);
330
331        initWorkspace();
332
333        // Disable multitouch across the workspace/all apps/customize tray
334        setMotionEventSplittingEnabled(true);
335        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
336    }
337
338    @Override
339    public void setInsets(Rect insets) {
340        mInsets.set(insets);
341    }
342
343    // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
344    // dimension if unsuccessful
345    public int[] estimateItemSize(int hSpan, int vSpan,
346            ItemInfo itemInfo, boolean springLoaded) {
347        int[] size = new int[2];
348        if (getChildCount() > 0) {
349            // Use the first non-custom page to estimate the child position
350            CellLayout cl = (CellLayout) getChildAt(numCustomPages());
351            Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
352            size[0] = r.width();
353            size[1] = r.height();
354            if (springLoaded) {
355                size[0] *= mSpringLoadedShrinkFactor;
356                size[1] *= mSpringLoadedShrinkFactor;
357            }
358            return size;
359        } else {
360            size[0] = Integer.MAX_VALUE;
361            size[1] = Integer.MAX_VALUE;
362            return size;
363        }
364    }
365
366    public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
367            int hCell, int vCell, int hSpan, int vSpan) {
368        Rect r = new Rect();
369        cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
370        return r;
371    }
372
373    public void onDragStart(final DragSource source, Object info, int dragAction) {
374        mIsDragOccuring = true;
375        updateChildrenLayersEnabled(false);
376        mLauncher.lockScreenOrientation();
377        mLauncher.onInteractionBegin();
378        setChildrenBackgroundAlphaMultipliers(1f);
379        // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
380        InstallShortcutReceiver.enableInstallQueue();
381        UninstallShortcutReceiver.enableUninstallQueue();
382        post(new Runnable() {
383            @Override
384            public void run() {
385                if (mIsDragOccuring) {
386                    addExtraEmptyScreenOnDrag();
387                }
388            }
389        });
390    }
391
392    public void onDragEnd() {
393        mIsDragOccuring = false;
394        updateChildrenLayersEnabled(false);
395        mLauncher.unlockScreenOrientation(false);
396
397        // Re-enable any Un/InstallShortcutReceiver and now process any queued items
398        InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
399        UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
400
401        mDragSourceInternal = null;
402        mLauncher.onInteractionEnd();
403    }
404
405    /**
406     * Initializes various states for this workspace.
407     */
408    protected void initWorkspace() {
409        Context context = getContext();
410        mCurrentPage = mDefaultPage;
411        Launcher.setScreen(mCurrentPage);
412        LauncherAppState app = LauncherAppState.getInstance();
413        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
414        mIconCache = app.getIconCache();
415        setWillNotDraw(false);
416        setClipChildren(false);
417        setClipToPadding(false);
418        setChildrenDrawnWithCacheEnabled(true);
419
420        setMinScale(mOverviewModeShrinkFactor);
421        setupLayoutTransition();
422
423        final Resources res = getResources();
424        try {
425            mBackground = res.getDrawable(R.drawable.apps_customize_bg);
426        } catch (Resources.NotFoundException e) {
427            // In this case, we will skip drawing background protection
428        }
429
430        mWallpaperOffset = new WallpaperOffsetInterpolator();
431        Display display = mLauncher.getWindowManager().getDefaultDisplay();
432        display.getSize(mDisplaySize);
433
434        mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
435        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
436    }
437
438    private void setupLayoutTransition() {
439        // We want to show layout transitions when pages are deleted, to close the gap.
440        mLayoutTransition = new LayoutTransition();
441        mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
442        mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
443        mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
444        mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
445        setLayoutTransition(mLayoutTransition);
446    }
447
448    void enableLayoutTransitions() {
449        setLayoutTransition(mLayoutTransition);
450    }
451    void disableLayoutTransitions() {
452        setLayoutTransition(null);
453    }
454
455    @Override
456    protected int getScrollMode() {
457        return SmoothPagedView.X_LARGE_MODE;
458    }
459
460    @Override
461    public void onChildViewAdded(View parent, View child) {
462        if (!(child instanceof CellLayout)) {
463            throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
464        }
465        CellLayout cl = ((CellLayout) child);
466        cl.setOnInterceptTouchListener(this);
467        cl.setClickable(true);
468        cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
469        super.onChildViewAdded(parent, child);
470    }
471
472    protected boolean shouldDrawChild(View child) {
473        final CellLayout cl = (CellLayout) child;
474        return super.shouldDrawChild(child) &&
475            (mIsSwitchingState ||
476             cl.getShortcutsAndWidgets().getAlpha() > 0 ||
477             cl.getBackgroundAlpha() > 0);
478    }
479
480    /**
481     * @return The open folder on the current screen, or null if there is none
482     */
483    Folder getOpenFolder() {
484        DragLayer dragLayer = mLauncher.getDragLayer();
485        int count = dragLayer.getChildCount();
486        for (int i = 0; i < count; i++) {
487            View child = dragLayer.getChildAt(i);
488            if (child instanceof Folder) {
489                Folder folder = (Folder) child;
490                if (folder.getInfo().opened)
491                    return folder;
492            }
493        }
494        return null;
495    }
496
497    boolean isTouchActive() {
498        return mTouchState != TOUCH_STATE_REST;
499    }
500
501    public void removeAllWorkspaceScreens() {
502        // Disable all layout transitions before removing all pages to ensure that we don't get the
503        // transition animations competing with us changing the scroll when we add pages or the
504        // custom content screen
505        disableLayoutTransitions();
506
507        // Since we increment the current page when we call addCustomContentPage via bindScreens
508        // (and other places), we need to adjust the current page back when we clear the pages
509        if (hasCustomContent()) {
510            removeCustomContentPage();
511        }
512
513        // Remove the pages and clear the screen models
514        removeAllViews();
515        mScreenOrder.clear();
516        mWorkspaceScreens.clear();
517
518        // Re-enable the layout transitions
519        enableLayoutTransitions();
520    }
521
522    public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
523        // Find the index to insert this view into.  If the empty screen exists, then
524        // insert it before that.
525        int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
526        if (insertIndex < 0) {
527            insertIndex = mScreenOrder.size();
528        }
529        return insertNewWorkspaceScreen(screenId, insertIndex);
530    }
531
532    public long insertNewWorkspaceScreen(long screenId) {
533        return insertNewWorkspaceScreen(screenId, getChildCount());
534    }
535
536    public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
537        if (mWorkspaceScreens.containsKey(screenId)) {
538            throw new RuntimeException("Screen id " + screenId + " already exists!");
539        }
540
541        CellLayout newScreen = (CellLayout)
542                mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
543
544        newScreen.setOnLongClickListener(mLongClickListener);
545        newScreen.setOnClickListener(mLauncher);
546        newScreen.setSoundEffectsEnabled(false);
547        mWorkspaceScreens.put(screenId, newScreen);
548        mScreenOrder.add(insertIndex, screenId);
549        addView(newScreen, insertIndex);
550        return screenId;
551    }
552
553    public void createCustomContentPage() {
554        CellLayout customScreen = (CellLayout)
555                mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
556
557        mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
558        mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
559
560        // We want no padding on the custom content
561        customScreen.setPadding(0, 0, 0, 0);
562
563        addFullScreenPage(customScreen);
564
565        // Ensure that the current page and default page are maintained.
566        mDefaultPage = mOriginalDefaultPage + 1;
567
568        // Update the custom content hint
569        mLauncher.updateCustomContentHintVisibility();
570        if (mRestorePage != INVALID_RESTORE_PAGE) {
571            mRestorePage = mRestorePage + 1;
572        } else {
573            setCurrentPage(getCurrentPage() + 1);
574        }
575    }
576
577    public void removeCustomContentPage() {
578        CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
579        if (customScreen == null) {
580            throw new RuntimeException("Expected custom content screen to exist");
581        }
582
583        mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
584        mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
585        removeView(customScreen);
586
587        if (mCustomContentCallbacks != null) {
588            mCustomContentCallbacks.onScrollProgressChanged(0);
589            mCustomContentCallbacks.onHide();
590        }
591
592        mCustomContentCallbacks = null;
593
594        // Ensure that the current page and default page are maintained.
595        mDefaultPage = mOriginalDefaultPage - 1;
596
597        // Update the custom content hint
598        mLauncher.updateCustomContentHintVisibility();
599        if (mRestorePage != INVALID_RESTORE_PAGE) {
600            mRestorePage = mRestorePage - 1;
601        } else {
602            setCurrentPage(getCurrentPage() - 1);
603        }
604    }
605
606    public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
607            String description) {
608        if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
609            throw new RuntimeException("Expected custom content screen to exist");
610        }
611
612        // Add the custom content to the full screen custom page
613        CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
614        int spanX = customScreen.getCountX();
615        int spanY = customScreen.getCountY();
616        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
617        lp.canReorder  = false;
618        lp.isFullscreen = true;
619        if (customContent instanceof Insettable) {
620            ((Insettable)customContent).setInsets(mInsets);
621        }
622        customScreen.removeAllViews();
623        customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
624        mCustomContentDescription = description;
625
626        mCustomContentCallbacks = callbacks;
627    }
628
629    public void addExtraEmptyScreenOnDrag() {
630        boolean lastChildOnScreen = false;
631        boolean childOnFinalScreen = false;
632
633        // Cancel any pending removal of empty screen
634        mRemoveEmptyScreenRunnable = null;
635
636        if (mDragSourceInternal != null) {
637            if (mDragSourceInternal.getChildCount() == 1) {
638                lastChildOnScreen = true;
639            }
640            CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
641            if (indexOfChild(cl) == getChildCount() - 1) {
642                childOnFinalScreen = true;
643            }
644        }
645
646        // If this is the last item on the final screen
647        if (lastChildOnScreen && childOnFinalScreen) {
648            return;
649        }
650        if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
651            insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
652        }
653    }
654
655    public boolean addExtraEmptyScreen() {
656        if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
657            insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
658            return true;
659        }
660        return false;
661    }
662
663    private void convertFinalScreenToEmptyScreenIfNecessary() {
664        if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
665        long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
666
667        if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return;
668        CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
669
670        // If the final screen is empty, convert it to the extra empty screen
671        if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
672                !finalScreen.isDropPending()) {
673            mWorkspaceScreens.remove(finalScreenId);
674            mScreenOrder.remove(finalScreenId);
675
676            // if this is the last non-custom content screen, convert it to the empty screen
677            mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
678            mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
679        }
680    }
681
682    public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete) {
683        removeExtraEmptyScreen(animate, onComplete, 0, false);
684    }
685
686    public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete,
687            final int delay, final boolean stripEmptyScreens) {
688        if (delay > 0) {
689            postDelayed(new Runnable() {
690                @Override
691                public void run() {
692                    removeExtraEmptyScreen(animate, onComplete, 0, stripEmptyScreens);
693                }
694
695            }, delay);
696            return;
697        }
698
699        convertFinalScreenToEmptyScreenIfNecessary();
700        if (hasExtraEmptyScreen()) {
701            int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
702            if (getNextPage() == emptyIndex) {
703                snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
704                fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
705                        onComplete, stripEmptyScreens);
706            } else {
707                fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
708                        onComplete, stripEmptyScreens);
709            }
710            return;
711        }
712        if (onComplete != null) {
713            onComplete.run();
714        }
715    }
716
717    private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
718            final boolean stripEmptyScreens) {
719        PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
720        PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
721
722        final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
723
724        mRemoveEmptyScreenRunnable = new Runnable() {
725            @Override
726            public void run() {
727                if (hasExtraEmptyScreen()) {
728                    mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
729                    mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
730                    removeView(cl);
731                    if (stripEmptyScreens) {
732                        stripEmptyScreens();
733                    }
734                }
735            }
736        };
737
738        ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
739        oa.setDuration(duration);
740        oa.setStartDelay(delay);
741        oa.addListener(new AnimatorListenerAdapter() {
742            @Override
743            public void onAnimationEnd(Animator animation) {
744                if (mRemoveEmptyScreenRunnable != null) {
745                    mRemoveEmptyScreenRunnable.run();
746                }
747                if (onComplete != null) {
748                    onComplete.run();
749                }
750            }
751        });
752        oa.start();
753    }
754
755    public boolean hasExtraEmptyScreen() {
756        int nScreens = getChildCount();
757        nScreens = nScreens - numCustomPages();
758        return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
759    }
760
761    public long commitExtraEmptyScreen() {
762        int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
763        CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
764        mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
765        mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
766
767        long newId = LauncherAppState.getLauncherProvider().generateNewScreenId();
768        mWorkspaceScreens.put(newId, cl);
769        mScreenOrder.add(newId);
770
771        // Update the page indicator marker
772        if (getPageIndicator() != null) {
773            getPageIndicator().updateMarker(index, getPageIndicatorMarker(index));
774        }
775
776        // Update the model for the new screen
777        mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
778
779        return newId;
780    }
781
782    public CellLayout getScreenWithId(long screenId) {
783        CellLayout layout = mWorkspaceScreens.get(screenId);
784        return layout;
785    }
786
787    public long getIdForScreen(CellLayout layout) {
788        Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
789        while (iter.hasNext()) {
790            long id = iter.next();
791            if (mWorkspaceScreens.get(id) == layout) {
792                return id;
793            }
794        }
795        return -1;
796    }
797
798    public int getPageIndexForScreenId(long screenId) {
799        return indexOfChild(mWorkspaceScreens.get(screenId));
800    }
801
802    public long getScreenIdForPageIndex(int index) {
803        if (0 <= index && index < mScreenOrder.size()) {
804            return mScreenOrder.get(index);
805        }
806        return -1;
807    }
808
809    ArrayList<Long> getScreenOrder() {
810        return mScreenOrder;
811    }
812
813    public void stripEmptyScreens() {
814        if (isPageMoving()) {
815            mStripScreensOnPageStopMoving = true;
816            return;
817        }
818
819        int currentPage = getNextPage();
820        ArrayList<Long> removeScreens = new ArrayList<Long>();
821        for (Long id: mWorkspaceScreens.keySet()) {
822            CellLayout cl = mWorkspaceScreens.get(id);
823            if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
824                removeScreens.add(id);
825            }
826        }
827
828        // We enforce at least one page to add new items to. In the case that we remove the last
829        // such screen, we convert the last screen to the empty screen
830        int minScreens = 1 + numCustomPages();
831
832        int pageShift = 0;
833        for (Long id: removeScreens) {
834            CellLayout cl = mWorkspaceScreens.get(id);
835            mWorkspaceScreens.remove(id);
836            mScreenOrder.remove(id);
837
838            if (getChildCount() > minScreens) {
839                if (indexOfChild(cl) < currentPage) {
840                    pageShift++;
841                }
842                removeView(cl);
843            } else {
844                // if this is the last non-custom content screen, convert it to the empty screen
845                mRemoveEmptyScreenRunnable = null;
846                mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
847                mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
848            }
849        }
850
851        if (!removeScreens.isEmpty()) {
852            // Update the model if we have changed any screens
853            mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
854        }
855
856        if (pageShift >= 0) {
857            setCurrentPage(currentPage - pageShift);
858        }
859    }
860
861    // See implementation for parameter definition.
862    void addInScreen(View child, long container, long screenId,
863            int x, int y, int spanX, int spanY) {
864        addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
865    }
866
867    // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
868    // See implementation for parameter definition.
869    void addInScreenFromBind(View child, long container, long screenId, int x, int y,
870            int spanX, int spanY) {
871        addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
872    }
873
874    // See implementation for parameter definition.
875    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
876            boolean insert) {
877        addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
878    }
879
880    /**
881     * Adds the specified child in the specified screen. The position and dimension of
882     * the child are defined by x, y, spanX and spanY.
883     *
884     * @param child The child to add in one of the workspace's screens.
885     * @param screenId The screen in which to add the child.
886     * @param x The X position of the child in the screen's grid.
887     * @param y The Y position of the child in the screen's grid.
888     * @param spanX The number of cells spanned horizontally by the child.
889     * @param spanY The number of cells spanned vertically by the child.
890     * @param insert When true, the child is inserted at the beginning of the children list.
891     * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
892     *                          the x and y position in which to place hotseat items. Otherwise
893     *                          we use the x and y position to compute the rank.
894     */
895    void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
896            boolean insert, boolean computeXYFromRank) {
897        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
898            if (getScreenWithId(screenId) == null) {
899                Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
900                // DEBUGGING - Print out the stack trace to see where we are adding from
901                new Throwable().printStackTrace();
902                return;
903            }
904        }
905        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
906            // This should never happen
907            throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
908        }
909
910        final CellLayout layout;
911        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
912            layout = mLauncher.getHotseat().getLayout();
913            child.setOnKeyListener(null);
914
915            // Hide folder title in the hotseat
916            if (child instanceof FolderIcon) {
917                ((FolderIcon) child).setTextVisible(false);
918            }
919
920            if (computeXYFromRank) {
921                x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
922                y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
923            } else {
924                screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
925            }
926        } else {
927            // Show folder title if not in the hotseat
928            if (child instanceof FolderIcon) {
929                ((FolderIcon) child).setTextVisible(true);
930            }
931            layout = getScreenWithId(screenId);
932            child.setOnKeyListener(new IconKeyEventListener());
933        }
934
935        ViewGroup.LayoutParams genericLp = child.getLayoutParams();
936        CellLayout.LayoutParams lp;
937        if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
938            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
939        } else {
940            lp = (CellLayout.LayoutParams) genericLp;
941            lp.cellX = x;
942            lp.cellY = y;
943            lp.cellHSpan = spanX;
944            lp.cellVSpan = spanY;
945        }
946
947        if (spanX < 0 && spanY < 0) {
948            lp.isLockedToGrid = false;
949        }
950
951        // Get the canonical child id to uniquely represent this view in this screen
952        ItemInfo info = (ItemInfo) child.getTag();
953        int childId = mLauncher.getViewIdForItem(info);
954
955        boolean markCellsAsOccupied = !(child instanceof Folder);
956        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
957            // TODO: This branch occurs when the workspace is adding views
958            // outside of the defined grid
959            // maybe we should be deleting these items from the LauncherModel?
960            Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout", true);
961        }
962
963        if (!(child instanceof Folder)) {
964            child.setHapticFeedbackEnabled(false);
965            child.setOnLongClickListener(mLongClickListener);
966        }
967        if (child instanceof DropTarget) {
968            mDragController.addDropTarget((DropTarget) child);
969        }
970    }
971
972    /**
973     * Called directly from a CellLayout (not by the framework), after we've been added as a
974     * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
975     * that it should intercept touch events, which is not something that is normally supported.
976     */
977    @Override
978    public boolean onTouch(View v, MotionEvent event) {
979        return (isSmall() || !isFinishedSwitchingState())
980                || (!isSmall() && indexOfChild(v) != mCurrentPage);
981    }
982
983    public boolean isSwitchingState() {
984        return mIsSwitchingState;
985    }
986
987    /** This differs from isSwitchingState in that we take into account how far the transition
988     *  has completed. */
989    public boolean isFinishedSwitchingState() {
990        return !mIsSwitchingState || (mTransitionProgress > 0.5f);
991    }
992
993    protected void onWindowVisibilityChanged (int visibility) {
994        mLauncher.onWindowVisibilityChanged(visibility);
995    }
996
997    @Override
998    public boolean dispatchUnhandledMove(View focused, int direction) {
999        if (isSmall() || !isFinishedSwitchingState()) {
1000            // when the home screens are shrunken, shouldn't allow side-scrolling
1001            return false;
1002        }
1003        return super.dispatchUnhandledMove(focused, direction);
1004    }
1005
1006    @Override
1007    public boolean onInterceptTouchEvent(MotionEvent ev) {
1008        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1009        case MotionEvent.ACTION_DOWN:
1010            mXDown = ev.getX();
1011            mYDown = ev.getY();
1012            mTouchDownTime = System.currentTimeMillis();
1013            break;
1014        case MotionEvent.ACTION_POINTER_UP:
1015        case MotionEvent.ACTION_UP:
1016            if (mTouchState == TOUCH_STATE_REST) {
1017                final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
1018                if (!currentPage.lastDownOnOccupiedCell()) {
1019                    onWallpaperTap(ev);
1020                }
1021            }
1022        }
1023        return super.onInterceptTouchEvent(ev);
1024    }
1025
1026    protected void reinflateWidgetsIfNecessary() {
1027        final int clCount = getChildCount();
1028        for (int i = 0; i < clCount; i++) {
1029            CellLayout cl = (CellLayout) getChildAt(i);
1030            ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
1031            final int itemCount = swc.getChildCount();
1032            for (int j = 0; j < itemCount; j++) {
1033                View v = swc.getChildAt(j);
1034
1035                if (v.getTag() instanceof LauncherAppWidgetInfo) {
1036                    LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
1037                    LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
1038                    if (lahv != null && lahv.orientationChangedSincedInflation()) {
1039                        mLauncher.removeAppWidget(info);
1040                        // Remove the current widget which is inflated with the wrong orientation
1041                        cl.removeView(lahv);
1042                        mLauncher.bindAppWidget(info);
1043                    }
1044                }
1045            }
1046        }
1047    }
1048
1049    @Override
1050    protected void determineScrollingStart(MotionEvent ev) {
1051        if (!isFinishedSwitchingState()) return;
1052
1053        float deltaX = ev.getX() - mXDown;
1054        float absDeltaX = Math.abs(deltaX);
1055        float absDeltaY = Math.abs(ev.getY() - mYDown);
1056
1057        if (Float.compare(absDeltaX, 0f) == 0) return;
1058
1059        float slope = absDeltaY / absDeltaX;
1060        float theta = (float) Math.atan(slope);
1061
1062        if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
1063            cancelCurrentPageLongPress();
1064        }
1065
1066        boolean passRightSwipesToCustomContent =
1067                (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY;
1068
1069        boolean swipeInIgnoreDirection = isLayoutRtl() ? deltaX < 0 : deltaX > 0;
1070        if (swipeInIgnoreDirection && getScreenIdForPageIndex(getCurrentPage()) ==
1071                CUSTOM_CONTENT_SCREEN_ID && passRightSwipesToCustomContent) {
1072            // Pass swipes to the right to the custom content page.
1073            return;
1074        }
1075
1076        if (theta > MAX_SWIPE_ANGLE) {
1077            // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
1078            return;
1079        } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
1080            // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
1081            // increase the touch slop to make it harder to begin scrolling the workspace. This
1082            // results in vertically scrolling widgets to more easily. The higher the angle, the
1083            // more we increase touch slop.
1084            theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
1085            float extraRatio = (float)
1086                    Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
1087            super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
1088        } else {
1089            // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
1090            super.determineScrollingStart(ev);
1091        }
1092    }
1093
1094    protected void onPageBeginMoving() {
1095        super.onPageBeginMoving();
1096
1097        if (isHardwareAccelerated()) {
1098            updateChildrenLayersEnabled(false);
1099        } else {
1100            if (mNextPage != INVALID_PAGE) {
1101                // we're snapping to a particular screen
1102                enableChildrenCache(mCurrentPage, mNextPage);
1103            } else {
1104                // this is when user is actively dragging a particular screen, they might
1105                // swipe it either left or right (but we won't advance by more than one screen)
1106                enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
1107            }
1108        }
1109
1110        // Only show page outlines as we pan if we are on large screen
1111        if (LauncherAppState.getInstance().isScreenLarge()) {
1112            showOutlines();
1113        }
1114
1115        // If we are not fading in adjacent screens, we still need to restore the alpha in case the
1116        // user scrolls while we are transitioning (should not affect dispatchDraw optimizations)
1117        if (!mWorkspaceFadeInAdjacentScreens) {
1118            for (int i = 0; i < getChildCount(); ++i) {
1119                ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f);
1120            }
1121        }
1122    }
1123
1124    protected void onPageEndMoving() {
1125        super.onPageEndMoving();
1126
1127        if (isHardwareAccelerated()) {
1128            updateChildrenLayersEnabled(false);
1129        } else {
1130            clearChildrenCache();
1131        }
1132
1133        if (mDragController.isDragging()) {
1134            if (isSmall()) {
1135                // If we are in springloaded mode, then force an event to check if the current touch
1136                // is under a new page (to scroll to)
1137                mDragController.forceTouchMove();
1138            }
1139        } else {
1140            // If we are not mid-dragging, hide the page outlines if we are on a large screen
1141            if (LauncherAppState.getInstance().isScreenLarge()) {
1142                hideOutlines();
1143            }
1144        }
1145
1146        if (mDelayedResizeRunnable != null) {
1147            mDelayedResizeRunnable.run();
1148            mDelayedResizeRunnable = null;
1149        }
1150
1151        if (mDelayedSnapToPageRunnable != null) {
1152            mDelayedSnapToPageRunnable.run();
1153            mDelayedSnapToPageRunnable = null;
1154        }
1155        if (mStripScreensOnPageStopMoving) {
1156            stripEmptyScreens();
1157            mStripScreensOnPageStopMoving = false;
1158        }
1159    }
1160
1161    @Override
1162    protected void notifyPageSwitchListener() {
1163        super.notifyPageSwitchListener();
1164        Launcher.setScreen(mCurrentPage);
1165
1166        if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
1167            mCustomContentShowing = true;
1168            if (mCustomContentCallbacks != null) {
1169                mCustomContentCallbacks.onShow();
1170                mCustomContentShowTime = System.currentTimeMillis();
1171                mLauncher.updateVoiceButtonProxyVisible(false);
1172            }
1173        } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
1174            mCustomContentShowing = false;
1175            if (mCustomContentCallbacks != null) {
1176                mCustomContentCallbacks.onHide();
1177                mLauncher.resetQSBScroll();
1178                mLauncher.updateVoiceButtonProxyVisible(false);
1179            }
1180        }
1181        if (getPageIndicator() != null) {
1182            getPageIndicator().setContentDescription(getPageIndicatorDescription());
1183        }
1184    }
1185
1186    protected CustomContentCallbacks getCustomContentCallbacks() {
1187        return mCustomContentCallbacks;
1188    }
1189
1190    protected void setWallpaperDimension() {
1191        String spKey = WallpaperCropActivity.getSharedPreferencesKey();
1192        SharedPreferences sp = mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
1193        WallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
1194                sp, mLauncher.getWindowManager(), mWallpaperManager);
1195    }
1196
1197    protected void snapToPage(int whichPage, Runnable r) {
1198        snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
1199    }
1200
1201    protected void snapToPage(int whichPage, int duration, Runnable r) {
1202        if (mDelayedSnapToPageRunnable != null) {
1203            mDelayedSnapToPageRunnable.run();
1204        }
1205        mDelayedSnapToPageRunnable = r;
1206        snapToPage(whichPage, duration);
1207    }
1208
1209    protected void snapToScreenId(long screenId, Runnable r) {
1210        snapToPage(getPageIndexForScreenId(screenId), r);
1211    }
1212
1213    class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
1214        float mFinalOffset = 0.0f;
1215        float mCurrentOffset = 0.5f; // to force an initial update
1216        boolean mWaitingForUpdate;
1217        Choreographer mChoreographer;
1218        Interpolator mInterpolator;
1219        boolean mAnimating;
1220        long mAnimationStartTime;
1221        float mAnimationStartOffset;
1222        private final int ANIMATION_DURATION = 250;
1223        // Don't use all the wallpaper for parallax until you have at least this many pages
1224        private final int MIN_PARALLAX_PAGE_SPAN = 3;
1225        int mNumScreens;
1226
1227        public WallpaperOffsetInterpolator() {
1228            mChoreographer = Choreographer.getInstance();
1229            mInterpolator = new DecelerateInterpolator(1.5f);
1230        }
1231
1232        @Override
1233        public void doFrame(long frameTimeNanos) {
1234            updateOffset(false);
1235        }
1236
1237        private void updateOffset(boolean force) {
1238            if (mWaitingForUpdate || force) {
1239                mWaitingForUpdate = false;
1240                if (computeScrollOffset() && mWindowToken != null) {
1241                    try {
1242                        mWallpaperManager.setWallpaperOffsets(mWindowToken,
1243                                mWallpaperOffset.getCurrX(), 0.5f);
1244                        setWallpaperOffsetSteps();
1245                    } catch (IllegalArgumentException e) {
1246                        Log.e(TAG, "Error updating wallpaper offset: " + e);
1247                    }
1248                }
1249            }
1250        }
1251
1252        public boolean computeScrollOffset() {
1253            final float oldOffset = mCurrentOffset;
1254            if (mAnimating) {
1255                long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
1256                float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
1257                float t1 = mInterpolator.getInterpolation(t0);
1258                mCurrentOffset = mAnimationStartOffset +
1259                        (mFinalOffset - mAnimationStartOffset) * t1;
1260                mAnimating = durationSinceAnimation < ANIMATION_DURATION;
1261            } else {
1262                mCurrentOffset = mFinalOffset;
1263            }
1264
1265            if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
1266                scheduleUpdate();
1267            }
1268            if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
1269                return true;
1270            }
1271            return false;
1272        }
1273
1274        private float wallpaperOffsetForCurrentScroll() {
1275            if (getChildCount() <= 1) {
1276                return 0;
1277            }
1278
1279            // Exclude the leftmost page
1280            int emptyExtraPages = numEmptyScreensToIgnore();
1281            int firstIndex = numCustomPages();
1282            // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
1283            int lastIndex = getChildCount() - 1 - emptyExtraPages;
1284            if (isLayoutRtl()) {
1285                int temp = firstIndex;
1286                firstIndex = lastIndex;
1287                lastIndex = temp;
1288            }
1289
1290            int firstPageScrollX = getScrollForPage(firstIndex);
1291            int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
1292            if (scrollRange == 0) {
1293                return 0;
1294            } else {
1295                // TODO: do different behavior if it's  a live wallpaper?
1296                // Sometimes the left parameter of the pages is animated during a layout transition;
1297                // this parameter offsets it to keep the wallpaper from animating as well
1298                int adjustedScroll =
1299                        getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
1300                float offset = Math.min(1, adjustedScroll / (float) scrollRange);
1301                offset = Math.max(0, offset);
1302                // Don't use up all the wallpaper parallax until you have at least
1303                // MIN_PARALLAX_PAGE_SPAN pages
1304                int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
1305                int parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
1306                // On RTL devices, push the wallpaper offset to the right if we don't have enough
1307                // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
1308                int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
1309                return offset * (padding + numScrollingPages - 1) / parallaxPageSpan;
1310            }
1311        }
1312
1313        private int numEmptyScreensToIgnore() {
1314            int numScrollingPages = getChildCount() - numCustomPages();
1315            if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
1316                return 1;
1317            } else {
1318                return 0;
1319            }
1320        }
1321
1322        private int getNumScreensExcludingEmptyAndCustom() {
1323            int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
1324            return numScrollingPages;
1325        }
1326
1327        public void syncWithScroll() {
1328            float offset = wallpaperOffsetForCurrentScroll();
1329            mWallpaperOffset.setFinalX(offset);
1330            updateOffset(true);
1331        }
1332
1333        public float getCurrX() {
1334            return mCurrentOffset;
1335        }
1336
1337        public float getFinalX() {
1338            return mFinalOffset;
1339        }
1340
1341        private void animateToFinal() {
1342            mAnimating = true;
1343            mAnimationStartOffset = mCurrentOffset;
1344            mAnimationStartTime = System.currentTimeMillis();
1345        }
1346
1347        private void setWallpaperOffsetSteps() {
1348            // Set wallpaper offset steps (1 / (number of screens - 1))
1349            mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f);
1350        }
1351
1352        public void setFinalX(float x) {
1353            scheduleUpdate();
1354            mFinalOffset = Math.max(0f, Math.min(x, 1.0f));
1355            if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
1356                if (mNumScreens > 0) {
1357                    // Don't animate if we're going from 0 screens
1358                    animateToFinal();
1359                }
1360                mNumScreens = getNumScreensExcludingEmptyAndCustom();
1361            }
1362        }
1363
1364        private void scheduleUpdate() {
1365            if (!mWaitingForUpdate) {
1366                mChoreographer.postFrameCallback(this);
1367                mWaitingForUpdate = true;
1368            }
1369        }
1370
1371        public void jumpToFinal() {
1372            mCurrentOffset = mFinalOffset;
1373        }
1374    }
1375
1376    @Override
1377    public void computeScroll() {
1378        super.computeScroll();
1379        mWallpaperOffset.syncWithScroll();
1380    }
1381
1382    void showOutlines() {
1383        if (!isSmall() && !mIsSwitchingState) {
1384            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1385            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1386            mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0f);
1387            mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1388            mChildrenOutlineFadeInAnimation.start();
1389        }
1390    }
1391
1392    void hideOutlines() {
1393        if (!isSmall() && !mIsSwitchingState) {
1394            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1395            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1396            mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.0f);
1397            mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1398            mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1399            mChildrenOutlineFadeOutAnimation.start();
1400        }
1401    }
1402
1403    public void showOutlinesTemporarily() {
1404        if (!mIsPageMoving && !isTouchActive()) {
1405            snapToPage(mCurrentPage);
1406        }
1407    }
1408
1409    public void setChildrenOutlineAlpha(float alpha) {
1410        mChildrenOutlineAlpha = alpha;
1411        for (int i = 0; i < getChildCount(); i++) {
1412            CellLayout cl = (CellLayout) getChildAt(i);
1413            cl.setBackgroundAlpha(alpha);
1414        }
1415    }
1416
1417    public float getChildrenOutlineAlpha() {
1418        return mChildrenOutlineAlpha;
1419    }
1420
1421    void disableBackground() {
1422        mDrawBackground = false;
1423    }
1424    void enableBackground() {
1425        mDrawBackground = true;
1426    }
1427
1428    private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1429        if (mBackground == null) return;
1430        if (mBackgroundFadeInAnimation != null) {
1431            mBackgroundFadeInAnimation.cancel();
1432            mBackgroundFadeInAnimation = null;
1433        }
1434        if (mBackgroundFadeOutAnimation != null) {
1435            mBackgroundFadeOutAnimation.cancel();
1436            mBackgroundFadeOutAnimation = null;
1437        }
1438        float startAlpha = getBackgroundAlpha();
1439        if (finalAlpha != startAlpha) {
1440            if (animated) {
1441                mBackgroundFadeOutAnimation =
1442                        LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
1443                mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1444                    public void onAnimationUpdate(ValueAnimator animation) {
1445                        setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue());
1446                    }
1447                });
1448                mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
1449                mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1450                mBackgroundFadeOutAnimation.start();
1451            } else {
1452                setBackgroundAlpha(finalAlpha);
1453            }
1454        }
1455    }
1456
1457    public void setBackgroundAlpha(float alpha) {
1458        if (alpha != mBackgroundAlpha) {
1459            mBackgroundAlpha = alpha;
1460            invalidate();
1461        }
1462    }
1463
1464    public float getBackgroundAlpha() {
1465        return mBackgroundAlpha;
1466    }
1467
1468    float backgroundAlphaInterpolator(float r) {
1469        float pivotA = 0.1f;
1470        float pivotB = 0.4f;
1471        if (r < pivotA) {
1472            return 0;
1473        } else if (r > pivotB) {
1474            return 1.0f;
1475        } else {
1476            return (r - pivotA)/(pivotB - pivotA);
1477        }
1478    }
1479
1480    private void updatePageAlphaValues(int screenCenter) {
1481        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1482        if (mWorkspaceFadeInAdjacentScreens &&
1483                mState == State.NORMAL &&
1484                !mIsSwitchingState &&
1485                !isInOverscroll) {
1486            for (int i = numCustomPages(); i < getChildCount(); i++) {
1487                CellLayout child = (CellLayout) getChildAt(i);
1488                if (child != null) {
1489                    float scrollProgress = getScrollProgress(screenCenter, child, i);
1490                    float alpha = 1 - Math.abs(scrollProgress);
1491                    child.getShortcutsAndWidgets().setAlpha(alpha);
1492                }
1493            }
1494        }
1495    }
1496
1497    private void setChildrenBackgroundAlphaMultipliers(float a) {
1498        for (int i = 0; i < getChildCount(); i++) {
1499            CellLayout child = (CellLayout) getChildAt(i);
1500            child.setBackgroundAlphaMultiplier(a);
1501        }
1502    }
1503
1504    public boolean hasCustomContent() {
1505        return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1506    }
1507
1508    public int numCustomPages() {
1509        return hasCustomContent() ? 1 : 0;
1510    }
1511
1512    public boolean isOnOrMovingToCustomContent() {
1513        return hasCustomContent() && getNextPage() == 0;
1514    }
1515
1516    private void updateStateForCustomContent(int screenCenter) {
1517        float translationX = 0;
1518        float progress = 0;
1519        if (hasCustomContent()) {
1520            int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1521
1522            int scrollDelta = getScrollX() - getScrollForPage(index) -
1523                    getLayoutTransitionOffsetForPage(index);
1524            float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
1525            translationX = scrollRange - scrollDelta;
1526            progress = (scrollRange - scrollDelta) / scrollRange;
1527
1528            if (isLayoutRtl()) {
1529                translationX = Math.min(0, translationX);
1530            } else {
1531                translationX = Math.max(0, translationX);
1532            }
1533            progress = Math.max(0, progress);
1534        }
1535
1536        if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
1537
1538        CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1539        if (progress > 0 && cc.getVisibility() != VISIBLE && !isSmall()) {
1540            cc.setVisibility(VISIBLE);
1541        }
1542
1543        mLastCustomContentScrollProgress = progress;
1544
1545        setBackgroundAlpha(progress * 0.8f);
1546
1547        if (mLauncher.getHotseat() != null) {
1548            mLauncher.getHotseat().setTranslationX(translationX);
1549        }
1550
1551        if (getPageIndicator() != null) {
1552            getPageIndicator().setTranslationX(translationX);
1553        }
1554
1555        if (mCustomContentCallbacks != null) {
1556            mCustomContentCallbacks.onScrollProgressChanged(progress);
1557        }
1558    }
1559
1560    @Override
1561    protected OnClickListener getPageIndicatorClickListener() {
1562        AccessibilityManager am = (AccessibilityManager)
1563                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1564        if (!am.isTouchExplorationEnabled()) {
1565            return null;
1566        }
1567        OnClickListener listener = new OnClickListener() {
1568            @Override
1569            public void onClick(View arg0) {
1570                enterOverviewMode();
1571            }
1572        };
1573        return listener;
1574    }
1575
1576    @Override
1577    protected void screenScrolled(int screenCenter) {
1578        final boolean isRtl = isLayoutRtl();
1579        super.screenScrolled(screenCenter);
1580
1581        updatePageAlphaValues(screenCenter);
1582        updateStateForCustomContent(screenCenter);
1583        enableHwLayersOnVisiblePages();
1584
1585        boolean shouldOverScroll = (mOverScrollX < 0 && (!hasCustomContent() || isLayoutRtl())) ||
1586                (mOverScrollX > mMaxScrollX && (!hasCustomContent() || !isLayoutRtl()));
1587
1588        if (shouldOverScroll) {
1589            int index = 0;
1590            float pivotX = 0f;
1591            final float leftBiasedPivot = 0.25f;
1592            final float rightBiasedPivot = 0.75f;
1593            final int lowerIndex = 0;
1594            final int upperIndex = getChildCount() - 1;
1595
1596            final boolean isLeftPage = mOverScrollX < 0;
1597            index = (!isRtl && isLeftPage) || (isRtl && !isLeftPage) ? lowerIndex : upperIndex;
1598            pivotX = isLeftPage ? rightBiasedPivot : leftBiasedPivot;
1599
1600            CellLayout cl = (CellLayout) getChildAt(index);
1601            float scrollProgress = getScrollProgress(screenCenter, cl, index);
1602            cl.setOverScrollAmount(Math.abs(scrollProgress), isLeftPage);
1603            float rotation = -WORKSPACE_OVERSCROLL_ROTATION * scrollProgress;
1604            cl.setRotationY(rotation);
1605
1606            if (!mOverscrollTransformsSet || Float.compare(mLastOverscrollPivotX, pivotX) != 0) {
1607                mOverscrollTransformsSet = true;
1608                mLastOverscrollPivotX = pivotX;
1609                cl.setCameraDistance(mDensity * mCameraDistance);
1610                cl.setPivotX(cl.getMeasuredWidth() * pivotX);
1611                cl.setPivotY(cl.getMeasuredHeight() * 0.5f);
1612                cl.setOverscrollTransformsDirty(true);
1613            }
1614        } else {
1615            if (mOverscrollTransformsSet) {
1616                mOverscrollTransformsSet = false;
1617                ((CellLayout) getChildAt(0)).resetOverscrollTransforms();
1618                ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms();
1619            }
1620        }
1621    }
1622
1623    @Override
1624    protected void overScroll(float amount) {
1625        acceleratedOverScroll(amount);
1626    }
1627
1628    protected void onAttachedToWindow() {
1629        super.onAttachedToWindow();
1630        mWindowToken = getWindowToken();
1631        computeScroll();
1632        mDragController.setWindowToken(mWindowToken);
1633    }
1634
1635    protected void onDetachedFromWindow() {
1636        super.onDetachedFromWindow();
1637        mWindowToken = null;
1638    }
1639
1640    protected void onResume() {
1641        if (getPageIndicator() != null) {
1642            // In case accessibility state has changed, we need to perform this on every
1643            // attach to window
1644            OnClickListener listener = getPageIndicatorClickListener();
1645            if (listener != null) {
1646                getPageIndicator().setOnClickListener(listener);
1647            }
1648        }
1649        AccessibilityManager am = (AccessibilityManager)
1650                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1651        sAccessibilityEnabled = am.isEnabled();
1652    }
1653
1654    @Override
1655    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1656        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1657            mWallpaperOffset.syncWithScroll();
1658            mWallpaperOffset.jumpToFinal();
1659        }
1660        super.onLayout(changed, left, top, right, bottom);
1661    }
1662
1663    @Override
1664    protected void onDraw(Canvas canvas) {
1665        // Draw the background gradient if necessary
1666        if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) {
1667            int alpha = (int) (mBackgroundAlpha * 255);
1668            mBackground.setAlpha(alpha);
1669            mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(),
1670                    getMeasuredHeight());
1671            mBackground.draw(canvas);
1672        }
1673
1674        super.onDraw(canvas);
1675
1676        // Call back to LauncherModel to finish binding after the first draw
1677        post(mBindPages);
1678    }
1679
1680    boolean isDrawingBackgroundGradient() {
1681        return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground);
1682    }
1683
1684    @Override
1685    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1686        if (!mLauncher.isAllAppsVisible()) {
1687            final Folder openFolder = getOpenFolder();
1688            if (openFolder != null) {
1689                return openFolder.requestFocus(direction, previouslyFocusedRect);
1690            } else {
1691                return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1692            }
1693        }
1694        return false;
1695    }
1696
1697    @Override
1698    public int getDescendantFocusability() {
1699        if (isSmall()) {
1700            return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1701        }
1702        return super.getDescendantFocusability();
1703    }
1704
1705    @Override
1706    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1707        if (!mLauncher.isAllAppsVisible()) {
1708            final Folder openFolder = getOpenFolder();
1709            if (openFolder != null) {
1710                openFolder.addFocusables(views, direction);
1711            } else {
1712                super.addFocusables(views, direction, focusableMode);
1713            }
1714        }
1715    }
1716
1717    public boolean isSmall() {
1718        return mState == State.SMALL || mState == State.SPRING_LOADED || mState == State.OVERVIEW;
1719    }
1720
1721    void enableChildrenCache(int fromPage, int toPage) {
1722        if (fromPage > toPage) {
1723            final int temp = fromPage;
1724            fromPage = toPage;
1725            toPage = temp;
1726        }
1727
1728        final int screenCount = getChildCount();
1729
1730        fromPage = Math.max(fromPage, 0);
1731        toPage = Math.min(toPage, screenCount - 1);
1732
1733        for (int i = fromPage; i <= toPage; i++) {
1734            final CellLayout layout = (CellLayout) getChildAt(i);
1735            layout.setChildrenDrawnWithCacheEnabled(true);
1736            layout.setChildrenDrawingCacheEnabled(true);
1737        }
1738    }
1739
1740    void clearChildrenCache() {
1741        final int screenCount = getChildCount();
1742        for (int i = 0; i < screenCount; i++) {
1743            final CellLayout layout = (CellLayout) getChildAt(i);
1744            layout.setChildrenDrawnWithCacheEnabled(false);
1745            // In software mode, we don't want the items to continue to be drawn into bitmaps
1746            if (!isHardwareAccelerated()) {
1747                layout.setChildrenDrawingCacheEnabled(false);
1748            }
1749        }
1750    }
1751
1752    private void updateChildrenLayersEnabled(boolean force) {
1753        boolean small = mState == State.SMALL || mState == State.OVERVIEW || mIsSwitchingState;
1754        boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
1755
1756        if (enableChildrenLayers != mChildrenLayersEnabled) {
1757            mChildrenLayersEnabled = enableChildrenLayers;
1758            if (mChildrenLayersEnabled) {
1759                enableHwLayersOnVisiblePages();
1760            } else {
1761                for (int i = 0; i < getPageCount(); i++) {
1762                    final CellLayout cl = (CellLayout) getChildAt(i);
1763                    cl.enableHardwareLayer(false);
1764                }
1765            }
1766        }
1767    }
1768
1769    private void enableHwLayersOnVisiblePages() {
1770        if (mChildrenLayersEnabled) {
1771            final int screenCount = getChildCount();
1772            getVisiblePages(mTempVisiblePagesRange);
1773            int leftScreen = mTempVisiblePagesRange[0];
1774            int rightScreen = mTempVisiblePagesRange[1];
1775            if (leftScreen == rightScreen) {
1776                // make sure we're caching at least two pages always
1777                if (rightScreen < screenCount - 1) {
1778                    rightScreen++;
1779                } else if (leftScreen > 0) {
1780                    leftScreen--;
1781                }
1782            }
1783
1784            final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1785            for (int i = 0; i < screenCount; i++) {
1786                final CellLayout layout = (CellLayout) getPageAt(i);
1787
1788                // enable layers between left and right screen inclusive, except for the
1789                // customScreen, which may animate its content during transitions.
1790                boolean enableLayer = layout != customScreen &&
1791                        leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
1792                layout.enableHardwareLayer(enableLayer);
1793            }
1794        }
1795    }
1796
1797    public void buildPageHardwareLayers() {
1798        // force layers to be enabled just for the call to buildLayer
1799        updateChildrenLayersEnabled(true);
1800        if (getWindowToken() != null) {
1801            final int childCount = getChildCount();
1802            for (int i = 0; i < childCount; i++) {
1803                CellLayout cl = (CellLayout) getChildAt(i);
1804                cl.buildHardwareLayer();
1805            }
1806        }
1807        updateChildrenLayersEnabled(false);
1808    }
1809
1810    protected void onWallpaperTap(MotionEvent ev) {
1811        final int[] position = mTempCell;
1812        getLocationOnScreen(position);
1813
1814        int pointerIndex = ev.getActionIndex();
1815        position[0] += (int) ev.getX(pointerIndex);
1816        position[1] += (int) ev.getY(pointerIndex);
1817
1818        mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1819                ev.getAction() == MotionEvent.ACTION_UP
1820                        ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1821                position[0], position[1], 0, null);
1822    }
1823
1824    /*
1825     * This interpolator emulates the rate at which the perceived scale of an object changes
1826     * as its distance from a camera increases. When this interpolator is applied to a scale
1827     * animation on a view, it evokes the sense that the object is shrinking due to moving away
1828     * from the camera.
1829     */
1830    static class ZInterpolator implements TimeInterpolator {
1831        private float focalLength;
1832
1833        public ZInterpolator(float foc) {
1834            focalLength = foc;
1835        }
1836
1837        public float getInterpolation(float input) {
1838            return (1.0f - focalLength / (focalLength + input)) /
1839                (1.0f - focalLength / (focalLength + 1.0f));
1840        }
1841    }
1842
1843    /*
1844     * The exact reverse of ZInterpolator.
1845     */
1846    static class InverseZInterpolator implements TimeInterpolator {
1847        private ZInterpolator zInterpolator;
1848        public InverseZInterpolator(float foc) {
1849            zInterpolator = new ZInterpolator(foc);
1850        }
1851        public float getInterpolation(float input) {
1852            return 1 - zInterpolator.getInterpolation(1 - input);
1853        }
1854    }
1855
1856    /*
1857     * ZInterpolator compounded with an ease-out.
1858     */
1859    static class ZoomOutInterpolator implements TimeInterpolator {
1860        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
1861        private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
1862
1863        public float getInterpolation(float input) {
1864            return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1865        }
1866    }
1867
1868    /*
1869     * InvereZInterpolator compounded with an ease-out.
1870     */
1871    static class ZoomInInterpolator implements TimeInterpolator {
1872        private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
1873        private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
1874
1875        public float getInterpolation(float input) {
1876            return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1877        }
1878    }
1879
1880    private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1881
1882    /*
1883    *
1884    * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1885    * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1886    *
1887    * These methods mark the appropriate pages as accepting drops (which alters their visual
1888    * appearance).
1889    *
1890    */
1891    public void onDragStartedWithItem(View v) {
1892        final Canvas canvas = new Canvas();
1893
1894        // The outline is used to visualize where the item will land if dropped
1895        mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
1896    }
1897
1898    public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
1899        final Canvas canvas = new Canvas();
1900
1901        int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
1902
1903        // The outline is used to visualize where the item will land if dropped
1904        mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0],
1905                size[1], clipAlpha);
1906    }
1907
1908    public void exitWidgetResizeMode() {
1909        DragLayer dragLayer = mLauncher.getDragLayer();
1910        dragLayer.clearAllResizeFrames();
1911    }
1912
1913    private void initAnimationArrays() {
1914        final int childCount = getChildCount();
1915        if (mLastChildCount == childCount) return;
1916
1917        mOldBackgroundAlphas = new float[childCount];
1918        mOldAlphas = new float[childCount];
1919        mNewBackgroundAlphas = new float[childCount];
1920        mNewAlphas = new float[childCount];
1921    }
1922
1923    Animator getChangeStateAnimation(final State state, boolean animated) {
1924        return getChangeStateAnimation(state, animated, 0, -1);
1925    }
1926
1927    @Override
1928    protected void getOverviewModePages(int[] range) {
1929        int start = numCustomPages();
1930        int end = getChildCount() - 1;
1931
1932        range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
1933        range[1] = Math.max(0,  end);
1934     }
1935
1936    protected void onStartReordering() {
1937        super.onStartReordering();
1938        showOutlines();
1939        // Reordering handles its own animations, disable the automatic ones.
1940        disableLayoutTransitions();
1941    }
1942
1943    protected void onEndReordering() {
1944        super.onEndReordering();
1945
1946        hideOutlines();
1947        mScreenOrder.clear();
1948        int count = getChildCount();
1949        for (int i = 0; i < count; i++) {
1950            CellLayout cl = ((CellLayout) getChildAt(i));
1951            mScreenOrder.add(getIdForScreen(cl));
1952        }
1953
1954        mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
1955
1956        // Re-enable auto layout transitions for page deletion.
1957        enableLayoutTransitions();
1958    }
1959
1960    public boolean isInOverviewMode() {
1961        return mState == State.OVERVIEW;
1962    }
1963
1964    public boolean enterOverviewMode() {
1965        if (mTouchState != TOUCH_STATE_REST) {
1966            return false;
1967        }
1968        enableOverviewMode(true, -1, true);
1969        return true;
1970    }
1971
1972    public void exitOverviewMode(boolean animated) {
1973        exitOverviewMode(-1, animated);
1974    }
1975
1976    public void exitOverviewMode(int snapPage, boolean animated) {
1977        enableOverviewMode(false, snapPage, animated);
1978    }
1979
1980    private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
1981        State finalState = Workspace.State.OVERVIEW;
1982        if (!enable) {
1983            finalState = Workspace.State.NORMAL;
1984        }
1985
1986        Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
1987        if (workspaceAnim != null) {
1988            onTransitionPrepare();
1989            workspaceAnim.addListener(new AnimatorListenerAdapter() {
1990                @Override
1991                public void onAnimationEnd(Animator arg0) {
1992                    onTransitionEnd();
1993                }
1994            });
1995            workspaceAnim.start();
1996        }
1997    }
1998
1999    int getOverviewModeTranslationY() {
2000        LauncherAppState app = LauncherAppState.getInstance();
2001        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2002        Rect overviewBar = grid.getOverviewModeButtonBarRect();
2003
2004        int availableHeight = getViewportHeight();
2005        int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
2006        int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
2007        int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
2008                - scaledHeight) / 2;
2009
2010        return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
2011    }
2012
2013    boolean shouldVoiceButtonProxyBeVisible() {
2014        if (isOnOrMovingToCustomContent()) {
2015            return false;
2016        }
2017        if (mState != State.NORMAL) {
2018            return false;
2019        }
2020        return true;
2021    }
2022
2023    public void updateInteractionForState() {
2024        if (mState != State.NORMAL) {
2025            mLauncher.onInteractionBegin();
2026        } else {
2027            mLauncher.onInteractionEnd();
2028        }
2029    }
2030
2031    private void setState(State state) {
2032        mState = state;
2033        updateInteractionForState();
2034        updateAccessibilityFlags();
2035    }
2036
2037    private void updateAccessibilityFlags() {
2038        int accessible = mState == State.NORMAL ?
2039                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
2040                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
2041        setImportantForAccessibility(accessible);
2042    }
2043
2044    Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
2045        if (mState == state) {
2046            return null;
2047        }
2048
2049        // Initialize animation arrays for the first time if necessary
2050        initAnimationArrays();
2051
2052        AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
2053
2054        final State oldState = mState;
2055        final boolean oldStateIsNormal = (oldState == State.NORMAL);
2056        final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
2057        final boolean oldStateIsSmall = (oldState == State.SMALL);
2058        final boolean oldStateIsOverview = (oldState == State.OVERVIEW);
2059        setState(state);
2060        final boolean stateIsNormal = (state == State.NORMAL);
2061        final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
2062        final boolean stateIsSmall = (state == State.SMALL);
2063        final boolean stateIsOverview = (state == State.OVERVIEW);
2064        float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
2065        float finalHotseatAndPageIndicatorAlpha = (stateIsOverview || stateIsSmall) ? 0f : 1f;
2066        float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
2067        float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
2068        float finalWorkspaceTranslationY = stateIsOverview ? getOverviewModeTranslationY() : 0;
2069
2070        boolean workspaceToAllApps = (oldStateIsNormal && stateIsSmall);
2071        boolean allAppsToWorkspace = (oldStateIsSmall && stateIsNormal);
2072        boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
2073        boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
2074
2075        mNewScale = 1.0f;
2076
2077        if (oldStateIsOverview) {
2078            disableFreeScroll(snapPage);
2079        } else if (stateIsOverview) {
2080            enableFreeScroll();
2081        }
2082
2083        if (state != State.NORMAL) {
2084            if (stateIsSpringLoaded) {
2085                mNewScale = mSpringLoadedShrinkFactor;
2086            } else if (stateIsOverview) {
2087                mNewScale = mOverviewModeShrinkFactor;
2088            } else if (stateIsSmall){
2089                mNewScale = mOverviewModeShrinkFactor - 0.3f;
2090            }
2091            if (workspaceToAllApps) {
2092                updateChildrenLayersEnabled(false);
2093            }
2094        }
2095
2096        final int duration;
2097        if (workspaceToAllApps) {
2098            duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
2099        } else if (workspaceToOverview || overviewToWorkspace) {
2100            duration = getResources().getInteger(R.integer.config_overviewTransitionTime);
2101        } else {
2102            duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
2103        }
2104
2105        for (int i = 0; i < getChildCount(); i++) {
2106            final CellLayout cl = (CellLayout) getChildAt(i);
2107            boolean isCurrentPage = (i == getNextPage());
2108            float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
2109            float finalAlpha;
2110            if (stateIsSmall) {
2111                finalAlpha = 0f;
2112            } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
2113
2114                finalAlpha = (i == getNextPage() || i < numCustomPages()) ? 1f : 0f;
2115            } else {
2116                finalAlpha = 1f;
2117            }
2118
2119            // If we are animating to/from the small state, then hide the side pages and fade the
2120            // current page in
2121            if (!mIsSwitchingState) {
2122                if (workspaceToAllApps || allAppsToWorkspace) {
2123                    if (allAppsToWorkspace && isCurrentPage) {
2124                        initialAlpha = 0f;
2125                    } else if (!isCurrentPage) {
2126                        initialAlpha = finalAlpha = 0f;
2127                    }
2128                    cl.setShortcutAndWidgetAlpha(initialAlpha);
2129                }
2130            }
2131
2132            mOldAlphas[i] = initialAlpha;
2133            mNewAlphas[i] = finalAlpha;
2134            if (animated) {
2135                mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
2136                mNewBackgroundAlphas[i] = finalBackgroundAlpha;
2137            } else {
2138                cl.setBackgroundAlpha(finalBackgroundAlpha);
2139                cl.setShortcutAndWidgetAlpha(finalAlpha);
2140            }
2141        }
2142
2143        final View searchBar = mLauncher.getQsbBar();
2144        final View overviewPanel = mLauncher.getOverviewPanel();
2145        final View hotseat = mLauncher.getHotseat();
2146        if (animated) {
2147            anim.setDuration(duration);
2148            LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
2149            scale.scaleX(mNewScale)
2150                .scaleY(mNewScale)
2151                .translationY(finalWorkspaceTranslationY)
2152                .setInterpolator(mZoomInInterpolator);
2153            anim.play(scale);
2154            for (int index = 0; index < getChildCount(); index++) {
2155                final int i = index;
2156                final CellLayout cl = (CellLayout) getChildAt(i);
2157                float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
2158                if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
2159                    cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
2160                    cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
2161                } else {
2162                    if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
2163                        LauncherViewPropertyAnimator alphaAnim =
2164                            new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
2165                        alphaAnim.alpha(mNewAlphas[i])
2166                            .setInterpolator(mZoomInInterpolator);
2167                        anim.play(alphaAnim);
2168                    }
2169                    if (mOldBackgroundAlphas[i] != 0 ||
2170                        mNewBackgroundAlphas[i] != 0) {
2171                        ValueAnimator bgAnim =
2172                                LauncherAnimUtils.ofFloat(cl, 0f, 1f);
2173                        bgAnim.setInterpolator(mZoomInInterpolator);
2174                        bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2175                                public void onAnimationUpdate(float a, float b) {
2176                                    cl.setBackgroundAlpha(
2177                                            a * mOldBackgroundAlphas[i] +
2178                                            b * mNewBackgroundAlphas[i]);
2179                                }
2180                            });
2181                        anim.play(bgAnim);
2182                    }
2183                }
2184            }
2185            ObjectAnimator pageIndicatorAlpha = null;
2186            if (getPageIndicator() != null) {
2187                pageIndicatorAlpha = ObjectAnimator.ofFloat(getPageIndicator(), "alpha",
2188                        finalHotseatAndPageIndicatorAlpha);
2189            }
2190            ObjectAnimator hotseatAlpha = ObjectAnimator.ofFloat(hotseat, "alpha",
2191                    finalHotseatAndPageIndicatorAlpha);
2192            ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(searchBar,
2193                    "alpha", finalSearchBarAlpha);
2194            ObjectAnimator overviewPanelAlpha = ObjectAnimator.ofFloat(overviewPanel,
2195                    "alpha", finalOverviewPanelAlpha);
2196
2197            overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
2198            hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
2199            searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
2200
2201            if (workspaceToOverview) {
2202                hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
2203            } else if (overviewToWorkspace) {
2204                overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
2205            }
2206
2207            if (getPageIndicator() != null) {
2208                pageIndicatorAlpha.addListener(new AlphaUpdateListener(getPageIndicator()));
2209            }
2210
2211            anim.play(overviewPanelAlpha);
2212            anim.play(hotseatAlpha);
2213            anim.play(searchBarAlpha);
2214            anim.play(pageIndicatorAlpha);
2215            anim.setStartDelay(delay);
2216        } else {
2217            overviewPanel.setAlpha(finalOverviewPanelAlpha);
2218            AlphaUpdateListener.updateVisibility(overviewPanel);
2219            hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
2220            AlphaUpdateListener.updateVisibility(hotseat);
2221            if (getPageIndicator() != null) {
2222                getPageIndicator().setAlpha(finalHotseatAndPageIndicatorAlpha);
2223                AlphaUpdateListener.updateVisibility(getPageIndicator());
2224            }
2225            searchBar.setAlpha(finalSearchBarAlpha);
2226            AlphaUpdateListener.updateVisibility(searchBar);
2227            updateCustomContentVisibility();
2228            setScaleX(mNewScale);
2229            setScaleY(mNewScale);
2230            setTranslationY(finalWorkspaceTranslationY);
2231        }
2232        mLauncher.updateVoiceButtonProxyVisible(false);
2233
2234        if (stateIsSpringLoaded) {
2235            // Right now we're covered by Apps Customize
2236            // Show the background gradient immediately, so the gradient will
2237            // be showing once AppsCustomize disappears
2238            animateBackgroundGradient(getResources().getInteger(
2239                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
2240        } else if (stateIsOverview) {
2241            animateBackgroundGradient(getResources().getInteger(
2242                    R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, true);
2243        } else {
2244            // Fade the background gradient away
2245            animateBackgroundGradient(0f, animated);
2246        }
2247        return anim;
2248    }
2249
2250    static class AlphaUpdateListener implements AnimatorUpdateListener, AnimatorListener {
2251        View view;
2252        public AlphaUpdateListener(View v) {
2253            view = v;
2254        }
2255
2256        @Override
2257        public void onAnimationUpdate(ValueAnimator arg0) {
2258            updateVisibility(view);
2259        }
2260
2261        public static void updateVisibility(View view) {
2262            // We want to avoid the extra layout pass by setting the views to GONE unless
2263            // accessibility is on, in which case not setting them to GONE causes a glitch.
2264            int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
2265            if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
2266                view.setVisibility(invisibleState);
2267            } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
2268                    && view.getVisibility() != VISIBLE) {
2269                view.setVisibility(VISIBLE);
2270            }
2271        }
2272
2273        @Override
2274        public void onAnimationCancel(Animator arg0) {
2275        }
2276
2277        @Override
2278        public void onAnimationEnd(Animator arg0) {
2279            updateVisibility(view);
2280        }
2281
2282        @Override
2283        public void onAnimationRepeat(Animator arg0) {
2284        }
2285
2286        @Override
2287        public void onAnimationStart(Animator arg0) {
2288            // We want the views to be visible for animation, so fade-in/out is visible
2289            view.setVisibility(VISIBLE);
2290        }
2291    }
2292
2293    @Override
2294    public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
2295        onTransitionPrepare();
2296    }
2297
2298    @Override
2299    public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
2300    }
2301
2302    @Override
2303    public void onLauncherTransitionStep(Launcher l, float t) {
2304        mTransitionProgress = t;
2305    }
2306
2307    @Override
2308    public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
2309        onTransitionEnd();
2310    }
2311
2312    private void onTransitionPrepare() {
2313        mIsSwitchingState = true;
2314
2315        // Invalidate here to ensure that the pages are rendered during the state change transition.
2316        invalidate();
2317
2318        updateChildrenLayersEnabled(false);
2319        hideCustomContentIfNecessary();
2320    }
2321
2322    void updateCustomContentVisibility() {
2323        int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2324        if (hasCustomContent()) {
2325            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2326        }
2327    }
2328
2329    void showCustomContentIfNecessary() {
2330        boolean show  = mState == Workspace.State.NORMAL;
2331        if (show && hasCustomContent()) {
2332            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2333        }
2334    }
2335
2336    void hideCustomContentIfNecessary() {
2337        boolean hide  = mState != Workspace.State.NORMAL;
2338        if (hide && hasCustomContent()) {
2339            mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2340        }
2341    }
2342
2343    private void onTransitionEnd() {
2344        mIsSwitchingState = false;
2345        updateChildrenLayersEnabled(false);
2346        // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
2347        // ensure that only the current page is visible during (and subsequently, after) the
2348        // transition animation.  If fade adjacent pages is disabled, then re-enable the page
2349        // visibility after the transition animation.
2350        if (!mWorkspaceFadeInAdjacentScreens) {
2351            for (int i = 0; i < getChildCount(); i++) {
2352                final CellLayout cl = (CellLayout) getChildAt(i);
2353                cl.setShortcutAndWidgetAlpha(1f);
2354            }
2355        } else {
2356            for (int i = 0; i < numCustomPages(); i++) {
2357                final CellLayout cl = (CellLayout) getChildAt(i);
2358                cl.setShortcutAndWidgetAlpha(1f);
2359            }
2360        }
2361        showCustomContentIfNecessary();
2362    }
2363
2364    @Override
2365    public View getContent() {
2366        return this;
2367    }
2368
2369    /**
2370     * Draw the View v into the given Canvas.
2371     *
2372     * @param v the view to draw
2373     * @param destCanvas the canvas to draw on
2374     * @param padding the horizontal and vertical padding to use when drawing
2375     */
2376    private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
2377        final Rect clipRect = mTempRect;
2378        v.getDrawingRect(clipRect);
2379
2380        boolean textVisible = false;
2381
2382        destCanvas.save();
2383        if (v instanceof TextView && pruneToDrawable) {
2384            Drawable d = ((TextView) v).getCompoundDrawables()[1];
2385            clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
2386            destCanvas.translate(padding / 2, padding / 2);
2387            d.draw(destCanvas);
2388        } else {
2389            if (v instanceof FolderIcon) {
2390                // For FolderIcons the text can bleed into the icon area, and so we need to
2391                // hide the text completely (which can't be achieved by clipping).
2392                if (((FolderIcon) v).getTextVisible()) {
2393                    ((FolderIcon) v).setTextVisible(false);
2394                    textVisible = true;
2395                }
2396            } else if (v instanceof BubbleTextView) {
2397                final BubbleTextView tv = (BubbleTextView) v;
2398                clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
2399                        tv.getLayout().getLineTop(0);
2400            } else if (v instanceof TextView) {
2401                final TextView tv = (TextView) v;
2402                clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
2403                        tv.getLayout().getLineTop(0);
2404            }
2405            destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
2406            destCanvas.clipRect(clipRect, Op.REPLACE);
2407            v.draw(destCanvas);
2408
2409            // Restore text visibility of FolderIcon if necessary
2410            if (textVisible) {
2411                ((FolderIcon) v).setTextVisible(true);
2412            }
2413        }
2414        destCanvas.restore();
2415    }
2416
2417    /**
2418     * Returns a new bitmap to show when the given View is being dragged around.
2419     * Responsibility for the bitmap is transferred to the caller.
2420     */
2421    public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
2422        Bitmap b;
2423
2424        if (v instanceof TextView) {
2425            Drawable d = ((TextView) v).getCompoundDrawables()[1];
2426            b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
2427                    d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
2428        } else {
2429            b = Bitmap.createBitmap(
2430                    v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2431        }
2432
2433        canvas.setBitmap(b);
2434        drawDragView(v, canvas, padding, true);
2435        canvas.setBitmap(null);
2436
2437        return b;
2438    }
2439
2440    /**
2441     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2442     * Responsibility for the bitmap is transferred to the caller.
2443     */
2444    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
2445        final int outlineColor = getResources().getColor(R.color.outline_color);
2446        final Bitmap b = Bitmap.createBitmap(
2447                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2448
2449        canvas.setBitmap(b);
2450        drawDragView(v, canvas, padding, true);
2451        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
2452        canvas.setBitmap(null);
2453        return b;
2454    }
2455
2456    /**
2457     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2458     * Responsibility for the bitmap is transferred to the caller.
2459     */
2460    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
2461            boolean clipAlpha) {
2462        final int outlineColor = getResources().getColor(R.color.outline_color);
2463        final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2464        canvas.setBitmap(b);
2465
2466        Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2467        float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
2468                (h - padding) / (float) orig.getHeight());
2469        int scaledWidth = (int) (scaleFactor * orig.getWidth());
2470        int scaledHeight = (int) (scaleFactor * orig.getHeight());
2471        Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2472
2473        // center the image
2474        dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2475
2476        canvas.drawBitmap(orig, src, dst, null);
2477        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
2478                clipAlpha);
2479        canvas.setBitmap(null);
2480
2481        return b;
2482    }
2483
2484    void startDrag(CellLayout.CellInfo cellInfo) {
2485        View child = cellInfo.cell;
2486
2487        // Make sure the drag was started by a long press as opposed to a long click.
2488        if (!child.isInTouchMode()) {
2489            return;
2490        }
2491
2492        mDragInfo = cellInfo;
2493        child.setVisibility(INVISIBLE);
2494        CellLayout layout = (CellLayout) child.getParent().getParent();
2495        layout.prepareChildForDrag(child);
2496
2497        child.clearFocus();
2498        child.setPressed(false);
2499
2500        final Canvas canvas = new Canvas();
2501
2502        // The outline is used to visualize where the item will land if dropped
2503        mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
2504        beginDragShared(child, this);
2505    }
2506
2507    public void beginDragShared(View child, DragSource source) {
2508        // The drag bitmap follows the touch point around on the screen
2509        final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
2510
2511        final int bmpWidth = b.getWidth();
2512        final int bmpHeight = b.getHeight();
2513
2514        float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2515        int dragLayerX =
2516                Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2517        int dragLayerY =
2518                Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
2519                        - DRAG_BITMAP_PADDING / 2);
2520
2521        LauncherAppState app = LauncherAppState.getInstance();
2522        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2523        Point dragVisualizeOffset = null;
2524        Rect dragRect = null;
2525        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
2526            int iconSize = grid.iconSizePx;
2527            int top = child.getPaddingTop();
2528            int left = (bmpWidth - iconSize) / 2;
2529            int right = left + iconSize;
2530            int bottom = top + iconSize;
2531            dragLayerY += top;
2532            // Note: The drag region is used to calculate drag layer offsets, but the
2533            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2534            dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2);
2535            dragRect = new Rect(left, top, right, bottom);
2536        } else if (child instanceof FolderIcon) {
2537            int previewSize = grid.folderIconSizePx;
2538            dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2539        }
2540
2541        // Clear the pressed state if necessary
2542        if (child instanceof BubbleTextView) {
2543            BubbleTextView icon = (BubbleTextView) child;
2544            icon.clearPressedOrFocusedBackground();
2545        }
2546
2547        if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2548            String msg = "Drag started with a view that has no tag set. This "
2549                    + "will cause a crash (issue 11627249) down the line. "
2550                    + "View: " + child + "  tag: " + child.getTag();
2551            throw new IllegalStateException(msg);
2552        }
2553
2554        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2555                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2556
2557        if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2558            mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2559        }
2560
2561        b.recycle();
2562    }
2563
2564    void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2565            int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2566        View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2567
2568        final int[] cellXY = new int[2];
2569        target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2570        addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2571
2572        LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2573                cellXY[1]);
2574    }
2575
2576    public boolean transitionStateShouldAllowDrop() {
2577        return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
2578    }
2579
2580    /**
2581     * {@inheritDoc}
2582     */
2583    public boolean acceptDrop(DragObject d) {
2584        // If it's an external drop (e.g. from All Apps), check if it should be accepted
2585        CellLayout dropTargetLayout = mDropToLayout;
2586        if (d.dragSource != this) {
2587            // Don't accept the drop if we're not over a screen at time of drop
2588            if (dropTargetLayout == null) {
2589                return false;
2590            }
2591            if (!transitionStateShouldAllowDrop()) return false;
2592
2593            mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2594                    d.dragView, mDragViewVisualCenter);
2595
2596            // We want the point to be mapped to the dragTarget.
2597            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2598                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2599            } else {
2600                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2601            }
2602
2603            int spanX = 1;
2604            int spanY = 1;
2605            if (mDragInfo != null) {
2606                final CellLayout.CellInfo dragCellInfo = mDragInfo;
2607                spanX = dragCellInfo.spanX;
2608                spanY = dragCellInfo.spanY;
2609            } else {
2610                final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2611                spanX = dragInfo.spanX;
2612                spanY = dragInfo.spanY;
2613            }
2614
2615            int minSpanX = spanX;
2616            int minSpanY = spanY;
2617            if (d.dragInfo instanceof PendingAddWidgetInfo) {
2618                minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2619                minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2620            }
2621
2622            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2623                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2624                    mTargetCell);
2625            float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2626                    mDragViewVisualCenter[1], mTargetCell);
2627            if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2628                    mTargetCell, distance, true)) {
2629                return true;
2630            }
2631            if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
2632                    mTargetCell, distance)) {
2633                return true;
2634            }
2635
2636            int[] resultSpan = new int[2];
2637            mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2638                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2639                    null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2640            boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2641
2642            // Don't accept the drop if there's no room for the item
2643            if (!foundCell) {
2644                // Don't show the message if we are dropping on the AllApps button and the hotseat
2645                // is full
2646                boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2647                if (mTargetCell != null && isHotseat) {
2648                    Hotseat hotseat = mLauncher.getHotseat();
2649                    if (hotseat.isAllAppsButtonRank(
2650                            hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2651                        return false;
2652                    }
2653                }
2654
2655                mLauncher.showOutOfSpaceMessage(isHotseat);
2656                return false;
2657            }
2658        }
2659
2660        long screenId = getIdForScreen(dropTargetLayout);
2661        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2662            commitExtraEmptyScreen();
2663        }
2664
2665        return true;
2666    }
2667
2668    boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2669            distance, boolean considerTimeout) {
2670        if (distance > mMaxDistanceForFolderCreation) return false;
2671        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2672
2673        if (dropOverView != null) {
2674            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2675            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2676                return false;
2677            }
2678        }
2679
2680        boolean hasntMoved = false;
2681        if (mDragInfo != null) {
2682            hasntMoved = dropOverView == mDragInfo.cell;
2683        }
2684
2685        if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2686            return false;
2687        }
2688
2689        boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2690        boolean willBecomeShortcut =
2691                (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2692                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2693
2694        return (aboveShortcut && willBecomeShortcut);
2695    }
2696
2697    boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2698            float distance) {
2699        if (distance > mMaxDistanceForFolderCreation) return false;
2700        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2701
2702        if (dropOverView != null) {
2703            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2704            if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2705                return false;
2706            }
2707        }
2708
2709        if (dropOverView instanceof FolderIcon) {
2710            FolderIcon fi = (FolderIcon) dropOverView;
2711            if (fi.acceptDrop(dragInfo)) {
2712                return true;
2713            }
2714        }
2715        return false;
2716    }
2717
2718    boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2719            int[] targetCell, float distance, boolean external, DragView dragView,
2720            Runnable postAnimationRunnable) {
2721        if (distance > mMaxDistanceForFolderCreation) return false;
2722        View v = target.getChildAt(targetCell[0], targetCell[1]);
2723
2724        boolean hasntMoved = false;
2725        if (mDragInfo != null) {
2726            CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2727            hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2728                    mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2729        }
2730
2731        if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2732        mCreateUserFolderOnDrop = false;
2733        final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
2734
2735        boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2736        boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2737
2738        if (aboveShortcut && willBecomeShortcut) {
2739            ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2740            ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2741            // if the drag started here, we need to remove it from the workspace
2742            if (!external) {
2743                getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2744            }
2745
2746            Rect folderLocation = new Rect();
2747            float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2748            target.removeView(v);
2749
2750            FolderIcon fi =
2751                mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2752            destInfo.cellX = -1;
2753            destInfo.cellY = -1;
2754            sourceInfo.cellX = -1;
2755            sourceInfo.cellY = -1;
2756
2757            // If the dragView is null, we can't animate
2758            boolean animate = dragView != null;
2759            if (animate) {
2760                fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2761                        postAnimationRunnable);
2762            } else {
2763                fi.addItem(destInfo);
2764                fi.addItem(sourceInfo);
2765            }
2766            return true;
2767        }
2768        return false;
2769    }
2770
2771    boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2772            float distance, DragObject d, boolean external) {
2773        if (distance > mMaxDistanceForFolderCreation) return false;
2774
2775        View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2776        if (!mAddToExistingFolderOnDrop) return false;
2777        mAddToExistingFolderOnDrop = false;
2778
2779        if (dropOverView instanceof FolderIcon) {
2780            FolderIcon fi = (FolderIcon) dropOverView;
2781            if (fi.acceptDrop(d.dragInfo)) {
2782                fi.onDrop(d);
2783
2784                // if the drag started here, we need to remove it from the workspace
2785                if (!external) {
2786                    getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2787                }
2788                return true;
2789            }
2790        }
2791        return false;
2792    }
2793
2794    public void onDrop(final DragObject d) {
2795        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
2796                mDragViewVisualCenter);
2797
2798        CellLayout dropTargetLayout = mDropToLayout;
2799
2800        // We want the point to be mapped to the dragTarget.
2801        if (dropTargetLayout != null) {
2802            if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2803                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2804            } else {
2805                mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2806            }
2807        }
2808
2809        int snapScreen = -1;
2810        boolean resizeOnDrop = false;
2811        if (d.dragSource != this) {
2812            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
2813                    (int) mDragViewVisualCenter[1] };
2814            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2815        } else if (mDragInfo != null) {
2816            final View cell = mDragInfo.cell;
2817
2818            Runnable resizeRunnable = null;
2819            if (dropTargetLayout != null && !d.cancelled) {
2820                // Move internally
2821                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
2822                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2823                long container = hasMovedIntoHotseat ?
2824                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
2825                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
2826                long screenId = (mTargetCell[0] < 0) ?
2827                        mDragInfo.screenId : getIdForScreen(dropTargetLayout);
2828                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
2829                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
2830                // First we find the cell nearest to point at which the item is
2831                // dropped, without any consideration to whether there is an item there.
2832
2833                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
2834                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
2835                float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2836                        mDragViewVisualCenter[1], mTargetCell);
2837
2838                // If the item being dropped is a shortcut and the nearest drop
2839                // cell also contains a shortcut, then create a folder with the two shortcuts.
2840                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
2841                        dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
2842                    removeExtraEmptyScreen(true, null, 0, true);
2843                    return;
2844                }
2845
2846                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
2847                        distance, d, false)) {
2848                    removeExtraEmptyScreen(true, null, 0, true);
2849                    return;
2850                }
2851
2852                // Aside from the special case where we're dropping a shortcut onto a shortcut,
2853                // we need to find the nearest cell location that is vacant
2854                ItemInfo item = (ItemInfo) d.dragInfo;
2855                int minSpanX = item.spanX;
2856                int minSpanY = item.spanY;
2857                if (item.minSpanX > 0 && item.minSpanY > 0) {
2858                    minSpanX = item.minSpanX;
2859                    minSpanY = item.minSpanY;
2860                }
2861
2862                int[] resultSpan = new int[2];
2863                mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
2864                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
2865                        mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
2866
2867                boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2868
2869                // if the widget resizes on drop
2870                if (foundCell && (cell instanceof AppWidgetHostView) &&
2871                        (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
2872                    resizeOnDrop = true;
2873                    item.spanX = resultSpan[0];
2874                    item.spanY = resultSpan[1];
2875                    AppWidgetHostView awhv = (AppWidgetHostView) cell;
2876                    AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
2877                            resultSpan[1]);
2878                }
2879
2880                if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
2881                    snapScreen = getPageIndexForScreenId(screenId);
2882                    snapToPage(snapScreen);
2883                }
2884
2885                if (foundCell) {
2886                    final ItemInfo info = (ItemInfo) cell.getTag();
2887                    if (hasMovedLayouts) {
2888                        // Reparent the view
2889                        getParentCellLayoutForView(cell).removeView(cell);
2890                        addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
2891                                info.spanX, info.spanY);
2892                    }
2893
2894                    // update the item's position after drop
2895                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2896                    lp.cellX = lp.tmpCellX = mTargetCell[0];
2897                    lp.cellY = lp.tmpCellY = mTargetCell[1];
2898                    lp.cellHSpan = item.spanX;
2899                    lp.cellVSpan = item.spanY;
2900                    lp.isLockedToGrid = true;
2901
2902                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2903                            cell instanceof LauncherAppWidgetHostView) {
2904                        final CellLayout cellLayout = dropTargetLayout;
2905                        // We post this call so that the widget has a chance to be placed
2906                        // in its final location
2907
2908                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
2909                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2910                        if (pinfo != null &&
2911                                pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
2912                            final Runnable addResizeFrame = new Runnable() {
2913                                public void run() {
2914                                    DragLayer dragLayer = mLauncher.getDragLayer();
2915                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
2916                                }
2917                            };
2918                            resizeRunnable = (new Runnable() {
2919                                public void run() {
2920                                    if (!isPageMoving()) {
2921                                        addResizeFrame.run();
2922                                    } else {
2923                                        mDelayedResizeRunnable = addResizeFrame;
2924                                    }
2925                                }
2926                            });
2927                        }
2928                    }
2929
2930                    LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
2931                            lp.cellY, item.spanX, item.spanY);
2932                } else {
2933                    // If we can't find a drop location, we return the item to its original position
2934                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
2935                    mTargetCell[0] = lp.cellX;
2936                    mTargetCell[1] = lp.cellY;
2937                    CellLayout layout = (CellLayout) cell.getParent().getParent();
2938                    layout.markCellsAsOccupiedForView(cell);
2939                }
2940            }
2941
2942            final CellLayout parent = (CellLayout) cell.getParent().getParent();
2943            final Runnable finalResizeRunnable = resizeRunnable;
2944            // Prepare it to be animated into its new position
2945            // This must be called after the view has been re-parented
2946            final Runnable onCompleteRunnable = new Runnable() {
2947                @Override
2948                public void run() {
2949                    mAnimatingViewIntoPlace = false;
2950                    updateChildrenLayersEnabled(false);
2951                    if (finalResizeRunnable != null) {
2952                        finalResizeRunnable.run();
2953                    }
2954                    removeExtraEmptyScreen(true, null, 0, true);
2955                }
2956            };
2957            mAnimatingViewIntoPlace = true;
2958            if (d.dragView.hasDrawn()) {
2959                final ItemInfo info = (ItemInfo) cell.getTag();
2960                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
2961                    int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
2962                            ANIMATE_INTO_POSITION_AND_DISAPPEAR;
2963                    animateWidgetDrop(info, parent, d.dragView,
2964                            onCompleteRunnable, animationType, cell, false);
2965                } else {
2966                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
2967                    mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
2968                            onCompleteRunnable, this);
2969                }
2970            } else {
2971                d.deferDragViewCleanupPostAnimation = false;
2972                cell.setVisibility(VISIBLE);
2973            }
2974            parent.onDropChild(cell);
2975        }
2976    }
2977
2978    public void setFinalScrollForPageChange(int pageIndex) {
2979        CellLayout cl = (CellLayout) getChildAt(pageIndex);
2980        if (cl != null) {
2981            mSavedScrollX = getScrollX();
2982            mSavedTranslationX = cl.getTranslationX();
2983            mSavedRotationY = cl.getRotationY();
2984            final int newX = getScrollForPage(pageIndex);
2985            setScrollX(newX);
2986            cl.setTranslationX(0f);
2987            cl.setRotationY(0f);
2988        }
2989    }
2990
2991    public void resetFinalScrollForPageChange(int pageIndex) {
2992        if (pageIndex >= 0) {
2993            CellLayout cl = (CellLayout) getChildAt(pageIndex);
2994            setScrollX(mSavedScrollX);
2995            cl.setTranslationX(mSavedTranslationX);
2996            cl.setRotationY(mSavedRotationY);
2997        }
2998    }
2999
3000    public void getViewLocationRelativeToSelf(View v, int[] location) {
3001        getLocationInWindow(location);
3002        int x = location[0];
3003        int y = location[1];
3004
3005        v.getLocationInWindow(location);
3006        int vX = location[0];
3007        int vY = location[1];
3008
3009        location[0] = vX - x;
3010        location[1] = vY - y;
3011    }
3012
3013    public void onDragEnter(DragObject d) {
3014        mDragEnforcer.onDragEnter();
3015        mCreateUserFolderOnDrop = false;
3016        mAddToExistingFolderOnDrop = false;
3017
3018        mDropToLayout = null;
3019        CellLayout layout = getCurrentDropLayout();
3020        setCurrentDropLayout(layout);
3021        setCurrentDragOverlappingLayout(layout);
3022
3023        // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
3024        // don't need to show the outlines
3025        if (LauncherAppState.getInstance().isScreenLarge()) {
3026            showOutlines();
3027        }
3028    }
3029
3030    /** Return a rect that has the cellWidth/cellHeight (left, top), and
3031     * widthGap/heightGap (right, bottom) */
3032    static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
3033        LauncherAppState app = LauncherAppState.getInstance();
3034        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3035
3036        Resources res = launcher.getResources();
3037        Display display = launcher.getWindowManager().getDefaultDisplay();
3038        Point smallestSize = new Point();
3039        Point largestSize = new Point();
3040        display.getCurrentSizeRange(smallestSize, largestSize);
3041        int countX = (int) grid.numColumns;
3042        int countY = (int) grid.numRows;
3043        int constrainedLongEdge = largestSize.y;
3044        int constrainedShortEdge = smallestSize.y;
3045        if (orientation == CellLayout.LANDSCAPE) {
3046            if (mLandscapeCellLayoutMetrics == null) {
3047                Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
3048                int width = constrainedLongEdge - padding.left - padding.right;
3049                int height = constrainedShortEdge - padding.top - padding.bottom;
3050                mLandscapeCellLayoutMetrics = new Rect();
3051                mLandscapeCellLayoutMetrics.set(
3052                        grid.calculateCellWidth(width, countX),
3053                        grid.calculateCellHeight(height, countY), 0, 0);
3054            }
3055            return mLandscapeCellLayoutMetrics;
3056        } else if (orientation == CellLayout.PORTRAIT) {
3057            if (mPortraitCellLayoutMetrics == null) {
3058                Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
3059                int width = constrainedShortEdge - padding.left - padding.right;
3060                int height = constrainedLongEdge - padding.top - padding.bottom;
3061                mPortraitCellLayoutMetrics = new Rect();
3062                mPortraitCellLayoutMetrics.set(
3063                        grid.calculateCellWidth(width, countX),
3064                        grid.calculateCellHeight(height, countY), 0, 0);
3065            }
3066            return mPortraitCellLayoutMetrics;
3067        }
3068        return null;
3069    }
3070
3071    public void onDragExit(DragObject d) {
3072        mDragEnforcer.onDragExit();
3073
3074        // Here we store the final page that will be dropped to, if the workspace in fact
3075        // receives the drop
3076        if (mInScrollArea) {
3077            if (isPageMoving()) {
3078                // If the user drops while the page is scrolling, we should use that page as the
3079                // destination instead of the page that is being hovered over.
3080                mDropToLayout = (CellLayout) getPageAt(getNextPage());
3081            } else {
3082                mDropToLayout = mDragOverlappingLayout;
3083            }
3084        } else {
3085            mDropToLayout = mDragTargetLayout;
3086        }
3087
3088        if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
3089            mCreateUserFolderOnDrop = true;
3090        } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
3091            mAddToExistingFolderOnDrop = true;
3092        }
3093
3094        // Reset the scroll area and previous drag target
3095        onResetScrollArea();
3096        setCurrentDropLayout(null);
3097        setCurrentDragOverlappingLayout(null);
3098
3099        mSpringLoadedDragController.cancel();
3100
3101        if (!mIsPageMoving) {
3102            hideOutlines();
3103        }
3104    }
3105
3106    void setCurrentDropLayout(CellLayout layout) {
3107        if (mDragTargetLayout != null) {
3108            mDragTargetLayout.revertTempState();
3109            mDragTargetLayout.onDragExit();
3110        }
3111        mDragTargetLayout = layout;
3112        if (mDragTargetLayout != null) {
3113            mDragTargetLayout.onDragEnter();
3114        }
3115        cleanupReorder(true);
3116        cleanupFolderCreation();
3117        setCurrentDropOverCell(-1, -1);
3118    }
3119
3120    void setCurrentDragOverlappingLayout(CellLayout layout) {
3121        if (mDragOverlappingLayout != null) {
3122            mDragOverlappingLayout.setIsDragOverlapping(false);
3123        }
3124        mDragOverlappingLayout = layout;
3125        if (mDragOverlappingLayout != null) {
3126            mDragOverlappingLayout.setIsDragOverlapping(true);
3127        }
3128        invalidate();
3129    }
3130
3131    void setCurrentDropOverCell(int x, int y) {
3132        if (x != mDragOverX || y != mDragOverY) {
3133            mDragOverX = x;
3134            mDragOverY = y;
3135            setDragMode(DRAG_MODE_NONE);
3136        }
3137    }
3138
3139    void setDragMode(int dragMode) {
3140        if (dragMode != mDragMode) {
3141            if (dragMode == DRAG_MODE_NONE) {
3142                cleanupAddToFolder();
3143                // We don't want to cancel the re-order alarm every time the target cell changes
3144                // as this feels to slow / unresponsive.
3145                cleanupReorder(false);
3146                cleanupFolderCreation();
3147            } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
3148                cleanupReorder(true);
3149                cleanupFolderCreation();
3150            } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
3151                cleanupAddToFolder();
3152                cleanupReorder(true);
3153            } else if (dragMode == DRAG_MODE_REORDER) {
3154                cleanupAddToFolder();
3155                cleanupFolderCreation();
3156            }
3157            mDragMode = dragMode;
3158        }
3159    }
3160
3161    private void cleanupFolderCreation() {
3162        if (mDragFolderRingAnimator != null) {
3163            mDragFolderRingAnimator.animateToNaturalState();
3164            mDragFolderRingAnimator = null;
3165        }
3166        mFolderCreationAlarm.setOnAlarmListener(null);
3167        mFolderCreationAlarm.cancelAlarm();
3168    }
3169
3170    private void cleanupAddToFolder() {
3171        if (mDragOverFolderIcon != null) {
3172            mDragOverFolderIcon.onDragExit(null);
3173            mDragOverFolderIcon = null;
3174        }
3175    }
3176
3177    private void cleanupReorder(boolean cancelAlarm) {
3178        // Any pending reorders are canceled
3179        if (cancelAlarm) {
3180            mReorderAlarm.cancelAlarm();
3181        }
3182        mLastReorderX = -1;
3183        mLastReorderY = -1;
3184    }
3185
3186   /*
3187    *
3188    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
3189    * coordinate space. The argument xy is modified with the return result.
3190    *
3191    * if cachedInverseMatrix is not null, this method will just use that matrix instead of
3192    * computing it itself; we use this to avoid redundant matrix inversions in
3193    * findMatchingPageForDragOver
3194    *
3195    */
3196   void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
3197       xy[0] = xy[0] - v.getLeft();
3198       xy[1] = xy[1] - v.getTop();
3199   }
3200
3201   boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
3202       if (r == null) {
3203           r = new Rect();
3204       }
3205       mTempPt[0] = x;
3206       mTempPt[1] = y;
3207       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3208
3209       LauncherAppState app = LauncherAppState.getInstance();
3210       DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3211       r = grid.getHotseatRect();
3212       if (r.contains(mTempPt[0], mTempPt[1])) {
3213           return true;
3214       }
3215       return false;
3216   }
3217
3218   void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
3219       mTempPt[0] = (int) xy[0];
3220       mTempPt[1] = (int) xy[1];
3221       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3222       mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
3223
3224       xy[0] = mTempPt[0];
3225       xy[1] = mTempPt[1];
3226   }
3227
3228   /*
3229    *
3230    * Convert the 2D coordinate xy from this CellLayout's coordinate space to
3231    * the parent View's coordinate space. The argument xy is modified with the return result.
3232    *
3233    */
3234   void mapPointFromChildToSelf(View v, float[] xy) {
3235       xy[0] += v.getLeft();
3236       xy[1] += v.getTop();
3237   }
3238
3239   static private float squaredDistance(float[] point1, float[] point2) {
3240        float distanceX = point1[0] - point2[0];
3241        float distanceY = point2[1] - point2[1];
3242        return distanceX * distanceX + distanceY * distanceY;
3243   }
3244
3245    /*
3246     *
3247     * This method returns the CellLayout that is currently being dragged to. In order to drag
3248     * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
3249     * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
3250     *
3251     * Return null if no CellLayout is currently being dragged over
3252     *
3253     */
3254    private CellLayout findMatchingPageForDragOver(
3255            DragView dragView, float originX, float originY, boolean exact) {
3256        // We loop through all the screens (ie CellLayouts) and see which ones overlap
3257        // with the item being dragged and then choose the one that's closest to the touch point
3258        final int screenCount = getChildCount();
3259        CellLayout bestMatchingScreen = null;
3260        float smallestDistSoFar = Float.MAX_VALUE;
3261
3262        for (int i = 0; i < screenCount; i++) {
3263            // The custom content screen is not a valid drag over option
3264            if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
3265                continue;
3266            }
3267
3268            CellLayout cl = (CellLayout) getChildAt(i);
3269
3270            final float[] touchXy = {originX, originY};
3271            // Transform the touch coordinates to the CellLayout's local coordinates
3272            // If the touch point is within the bounds of the cell layout, we can return immediately
3273            cl.getMatrix().invert(mTempInverseMatrix);
3274            mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
3275
3276            if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
3277                    touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
3278                return cl;
3279            }
3280
3281            if (!exact) {
3282                // Get the center of the cell layout in screen coordinates
3283                final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
3284                cellLayoutCenter[0] = cl.getWidth()/2;
3285                cellLayoutCenter[1] = cl.getHeight()/2;
3286                mapPointFromChildToSelf(cl, cellLayoutCenter);
3287
3288                touchXy[0] = originX;
3289                touchXy[1] = originY;
3290
3291                // Calculate the distance between the center of the CellLayout
3292                // and the touch point
3293                float dist = squaredDistance(touchXy, cellLayoutCenter);
3294
3295                if (dist < smallestDistSoFar) {
3296                    smallestDistSoFar = dist;
3297                    bestMatchingScreen = cl;
3298                }
3299            }
3300        }
3301        return bestMatchingScreen;
3302    }
3303
3304    // This is used to compute the visual center of the dragView. This point is then
3305    // used to visualize drop locations and determine where to drop an item. The idea is that
3306    // the visual center represents the user's interpretation of where the item is, and hence
3307    // is the appropriate point to use when determining drop location.
3308    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
3309            DragView dragView, float[] recycle) {
3310        float res[];
3311        if (recycle == null) {
3312            res = new float[2];
3313        } else {
3314            res = recycle;
3315        }
3316
3317        // First off, the drag view has been shifted in a way that is not represented in the
3318        // x and y values or the x/yOffsets. Here we account for that shift.
3319        x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
3320        y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
3321
3322        // These represent the visual top and left of drag view if a dragRect was provided.
3323        // If a dragRect was not provided, then they correspond to the actual view left and
3324        // top, as the dragRect is in that case taken to be the entire dragView.
3325        // R.dimen.dragViewOffsetY.
3326        int left = x - xOffset;
3327        int top = y - yOffset;
3328
3329        // In order to find the visual center, we shift by half the dragRect
3330        res[0] = left + dragView.getDragRegion().width() / 2;
3331        res[1] = top + dragView.getDragRegion().height() / 2;
3332
3333        return res;
3334    }
3335
3336    private boolean isDragWidget(DragObject d) {
3337        return (d.dragInfo instanceof LauncherAppWidgetInfo ||
3338                d.dragInfo instanceof PendingAddWidgetInfo);
3339    }
3340    private boolean isExternalDragWidget(DragObject d) {
3341        return d.dragSource != this && isDragWidget(d);
3342    }
3343
3344    public void onDragOver(DragObject d) {
3345        // Skip drag over events while we are dragging over side pages
3346        if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
3347
3348        Rect r = new Rect();
3349        CellLayout layout = null;
3350        ItemInfo item = (ItemInfo) d.dragInfo;
3351
3352        // Ensure that we have proper spans for the item that we are dropping
3353        if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
3354        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
3355            d.dragView, mDragViewVisualCenter);
3356
3357        final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3358        // Identify whether we have dragged over a side page
3359        if (isSmall()) {
3360            if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
3361                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3362                    layout = mLauncher.getHotseat().getLayout();
3363                }
3364            }
3365            if (layout == null) {
3366                layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
3367            }
3368            if (layout != mDragTargetLayout) {
3369                setCurrentDropLayout(layout);
3370                setCurrentDragOverlappingLayout(layout);
3371
3372                boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
3373                if (isInSpringLoadedMode) {
3374                    if (mLauncher.isHotseatLayout(layout)) {
3375                        mSpringLoadedDragController.cancel();
3376                    } else {
3377                        mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3378                    }
3379                }
3380            }
3381        } else {
3382            // Test to see if we are over the hotseat otherwise just use the current page
3383            if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
3384                if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3385                    layout = mLauncher.getHotseat().getLayout();
3386                }
3387            }
3388            if (layout == null) {
3389                layout = getCurrentDropLayout();
3390            }
3391            if (layout != mDragTargetLayout) {
3392                setCurrentDropLayout(layout);
3393                setCurrentDragOverlappingLayout(layout);
3394            }
3395        }
3396
3397        // Handle the drag over
3398        if (mDragTargetLayout != null) {
3399            // We want the point to be mapped to the dragTarget.
3400            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3401                mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3402            } else {
3403                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
3404            }
3405
3406            ItemInfo info = (ItemInfo) d.dragInfo;
3407
3408            int minSpanX = item.spanX;
3409            int minSpanY = item.spanY;
3410            if (item.minSpanX > 0 && item.minSpanY > 0) {
3411                minSpanX = item.minSpanX;
3412                minSpanY = item.minSpanY;
3413            }
3414
3415            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3416                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
3417                    mDragTargetLayout, mTargetCell);
3418            int reorderX = mTargetCell[0];
3419            int reorderY = mTargetCell[1];
3420
3421            setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3422
3423            float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
3424                    mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
3425
3426            final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
3427                    mTargetCell[1]);
3428
3429            manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
3430                    targetCellDistance, dragOverView);
3431
3432            boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
3433                    mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
3434                    item.spanY, child, mTargetCell);
3435
3436            if (!nearestDropOccupied) {
3437                mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3438                        (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3439                        mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
3440                        d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
3441            } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
3442                    && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
3443                    mLastReorderY != reorderY)) {
3444
3445                // Otherwise, if we aren't adding to or creating a folder and there's no pending
3446                // reorder, then we schedule a reorder
3447                ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
3448                        minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
3449                mReorderAlarm.setOnAlarmListener(listener);
3450                mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3451            }
3452
3453            if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
3454                    !nearestDropOccupied) {
3455                if (mDragTargetLayout != null) {
3456                    mDragTargetLayout.revertTempState();
3457                }
3458            }
3459        }
3460    }
3461
3462    private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
3463            int[] targetCell, float distance, View dragOverView) {
3464        boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
3465                false);
3466
3467        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3468                !mFolderCreationAlarm.alarmPending()) {
3469            mFolderCreationAlarm.setOnAlarmListener(new
3470                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
3471            mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3472            return;
3473        }
3474
3475        boolean willAddToFolder =
3476                willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
3477
3478        if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3479            mDragOverFolderIcon = ((FolderIcon) dragOverView);
3480            mDragOverFolderIcon.onDragEnter(info);
3481            if (targetLayout != null) {
3482                targetLayout.clearDragOutlines();
3483            }
3484            setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3485            return;
3486        }
3487
3488        if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3489            setDragMode(DRAG_MODE_NONE);
3490        }
3491        if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3492            setDragMode(DRAG_MODE_NONE);
3493        }
3494
3495        return;
3496    }
3497
3498    class FolderCreationAlarmListener implements OnAlarmListener {
3499        CellLayout layout;
3500        int cellX;
3501        int cellY;
3502
3503        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3504            this.layout = layout;
3505            this.cellX = cellX;
3506            this.cellY = cellY;
3507        }
3508
3509        public void onAlarm(Alarm alarm) {
3510            if (mDragFolderRingAnimator != null) {
3511                // This shouldn't happen ever, but just in case, make sure we clean up the mess.
3512                mDragFolderRingAnimator.animateToNaturalState();
3513            }
3514            mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3515            mDragFolderRingAnimator.setCell(cellX, cellY);
3516            mDragFolderRingAnimator.setCellLayout(layout);
3517            mDragFolderRingAnimator.animateToAcceptState();
3518            layout.showFolderAccept(mDragFolderRingAnimator);
3519            layout.clearDragOutlines();
3520            setDragMode(DRAG_MODE_CREATE_FOLDER);
3521        }
3522    }
3523
3524    class ReorderAlarmListener implements OnAlarmListener {
3525        float[] dragViewCenter;
3526        int minSpanX, minSpanY, spanX, spanY;
3527        DragView dragView;
3528        View child;
3529
3530        public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3531                int spanY, DragView dragView, View child) {
3532            this.dragViewCenter = dragViewCenter;
3533            this.minSpanX = minSpanX;
3534            this.minSpanY = minSpanY;
3535            this.spanX = spanX;
3536            this.spanY = spanY;
3537            this.child = child;
3538            this.dragView = dragView;
3539        }
3540
3541        public void onAlarm(Alarm alarm) {
3542            int[] resultSpan = new int[2];
3543            mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3544                    (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
3545                    mTargetCell);
3546            mLastReorderX = mTargetCell[0];
3547            mLastReorderY = mTargetCell[1];
3548
3549            mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
3550                (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3551                child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3552
3553            if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3554                mDragTargetLayout.revertTempState();
3555            } else {
3556                setDragMode(DRAG_MODE_REORDER);
3557            }
3558
3559            boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3560            mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3561                (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3562                mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
3563                dragView.getDragVisualizeOffset(), dragView.getDragRegion());
3564        }
3565    }
3566
3567    @Override
3568    public void getHitRectRelativeToDragLayer(Rect outRect) {
3569        // We want the workspace to have the whole area of the display (it will find the correct
3570        // cell layout to drop to in the existing drag/drop logic.
3571        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3572    }
3573
3574    /**
3575     * Add the item specified by dragInfo to the given layout.
3576     * @return true if successful
3577     */
3578    public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3579        if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3580            onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3581            return true;
3582        }
3583        mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3584        return false;
3585    }
3586
3587    private void onDropExternal(int[] touchXY, Object dragInfo,
3588            CellLayout cellLayout, boolean insertAtFirst) {
3589        onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3590    }
3591
3592    /**
3593     * Drop an item that didn't originate on one of the workspace screens.
3594     * It may have come from Launcher (e.g. from all apps or customize), or it may have
3595     * come from another app altogether.
3596     *
3597     * NOTE: This can also be called when we are outside of a drag event, when we want
3598     * to add an item to one of the workspace screens.
3599     */
3600    private void onDropExternal(final int[] touchXY, final Object dragInfo,
3601            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3602        final Runnable exitSpringLoadedRunnable = new Runnable() {
3603            @Override
3604            public void run() {
3605                removeExtraEmptyScreen(false, new Runnable() {
3606                    @Override
3607                    public void run() {
3608                        mLauncher.exitSpringLoadedDragModeDelayed(true,
3609                                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
3610                    }
3611                });
3612            }
3613        };
3614
3615        ItemInfo info = (ItemInfo) dragInfo;
3616        int spanX = info.spanX;
3617        int spanY = info.spanY;
3618        if (mDragInfo != null) {
3619            spanX = mDragInfo.spanX;
3620            spanY = mDragInfo.spanY;
3621        }
3622
3623        final long container = mLauncher.isHotseatLayout(cellLayout) ?
3624                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3625                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
3626        final long screenId = getIdForScreen(cellLayout);
3627        if (!mLauncher.isHotseatLayout(cellLayout)
3628                && screenId != getScreenIdForPageIndex(mCurrentPage)
3629                && mState != State.SPRING_LOADED) {
3630            snapToScreenId(screenId, null);
3631        }
3632
3633        if (info instanceof PendingAddItemInfo) {
3634            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3635
3636            boolean findNearestVacantCell = true;
3637            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3638                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3639                        cellLayout, mTargetCell);
3640                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3641                        mDragViewVisualCenter[1], mTargetCell);
3642                if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3643                        distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3644                                cellLayout, mTargetCell, distance)) {
3645                    findNearestVacantCell = false;
3646                }
3647            }
3648
3649            final ItemInfo item = (ItemInfo) d.dragInfo;
3650            boolean updateWidgetSize = false;
3651            if (findNearestVacantCell) {
3652                int minSpanX = item.spanX;
3653                int minSpanY = item.spanY;
3654                if (item.minSpanX > 0 && item.minSpanY > 0) {
3655                    minSpanX = item.minSpanX;
3656                    minSpanY = item.minSpanY;
3657                }
3658                int[] resultSpan = new int[2];
3659                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3660                        (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3661                        null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3662
3663                if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3664                    updateWidgetSize = true;
3665                }
3666                item.spanX = resultSpan[0];
3667                item.spanY = resultSpan[1];
3668            }
3669
3670            Runnable onAnimationCompleteRunnable = new Runnable() {
3671                @Override
3672                public void run() {
3673                    // When dragging and dropping from customization tray, we deal with creating
3674                    // widgets/shortcuts/folders in a slightly different way
3675                    switch (pendingInfo.itemType) {
3676                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3677                        int span[] = new int[2];
3678                        span[0] = item.spanX;
3679                        span[1] = item.spanY;
3680                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3681                                container, screenId, mTargetCell, span, null);
3682                        break;
3683                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3684                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3685                                container, screenId, mTargetCell, null);
3686                        break;
3687                    default:
3688                        throw new IllegalStateException("Unknown item type: " +
3689                                pendingInfo.itemType);
3690                    }
3691                }
3692            };
3693            View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3694                    ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3695
3696            if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
3697                AppWidgetHostView awhv = (AppWidgetHostView) finalView;
3698                AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
3699                        item.spanY);
3700            }
3701
3702            int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3703            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3704                    ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3705                animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3706            }
3707            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3708                    animationStyle, finalView, true);
3709        } else {
3710            // This is for other drag/drop cases, like dragging from All Apps
3711            View view = null;
3712
3713            switch (info.itemType) {
3714            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3715            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3716                if (info.container == NO_ID && info instanceof AppInfo) {
3717                    // Came from all apps -- make a copy
3718                    info = new ShortcutInfo((AppInfo) info);
3719                }
3720                view = mLauncher.createShortcut(R.layout.application, cellLayout,
3721                        (ShortcutInfo) info);
3722                break;
3723            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3724                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3725                        (FolderInfo) info, mIconCache);
3726                break;
3727            default:
3728                throw new IllegalStateException("Unknown item type: " + info.itemType);
3729            }
3730
3731            // First we find the cell nearest to point at which the item is
3732            // dropped, without any consideration to whether there is an item there.
3733            if (touchXY != null) {
3734                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3735                        cellLayout, mTargetCell);
3736                float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3737                        mDragViewVisualCenter[1], mTargetCell);
3738                d.postAnimationRunnable = exitSpringLoadedRunnable;
3739                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3740                        true, d.dragView, d.postAnimationRunnable)) {
3741                    return;
3742                }
3743                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3744                        true)) {
3745                    return;
3746                }
3747            }
3748
3749            if (touchXY != null) {
3750                // when dragging and dropping, just find the closest free spot
3751                mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
3752                        (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3753                        null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3754            } else {
3755                cellLayout.findCellForSpan(mTargetCell, 1, 1);
3756            }
3757            addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
3758                    info.spanY, insertAtFirst);
3759            cellLayout.onDropChild(view);
3760            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
3761            cellLayout.getShortcutsAndWidgets().measureChild(view);
3762
3763            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
3764                    lp.cellX, lp.cellY);
3765
3766            if (d.dragView != null) {
3767                // We wrap the animation call in the temporary set and reset of the current
3768                // cellLayout to its final transform -- this means we animate the drag view to
3769                // the correct final location.
3770                setFinalTransitionTransform(cellLayout);
3771                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3772                        exitSpringLoadedRunnable, this);
3773                resetTransitionTransform(cellLayout);
3774            }
3775        }
3776    }
3777
3778    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3779        int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
3780                widgetInfo.spanY, widgetInfo, false);
3781        int visibility = layout.getVisibility();
3782        layout.setVisibility(VISIBLE);
3783
3784        int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3785        int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3786        Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
3787                Bitmap.Config.ARGB_8888);
3788        Canvas c = new Canvas(b);
3789
3790        layout.measure(width, height);
3791        layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3792        layout.draw(c);
3793        c.setBitmap(null);
3794        layout.setVisibility(visibility);
3795        return b;
3796    }
3797
3798    private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3799            DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
3800            boolean external, boolean scale) {
3801        // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3802        // location and size on the home screen.
3803        int spanX = info.spanX;
3804        int spanY = info.spanY;
3805
3806        Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
3807        loc[0] = r.left;
3808        loc[1] = r.top;
3809
3810        setFinalTransitionTransform(layout);
3811        float cellLayoutScale =
3812                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
3813        resetTransitionTransform(layout);
3814
3815        float dragViewScaleX;
3816        float dragViewScaleY;
3817        if (scale) {
3818            dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3819            dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3820        } else {
3821            dragViewScaleX = 1f;
3822            dragViewScaleY = 1f;
3823        }
3824
3825        // The animation will scale the dragView about its center, so we need to center about
3826        // the final location.
3827        loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
3828        loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3829
3830        scaleXY[0] = dragViewScaleX * cellLayoutScale;
3831        scaleXY[1] = dragViewScaleY * cellLayoutScale;
3832    }
3833
3834    public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
3835            final Runnable onCompleteRunnable, int animationType, final View finalView,
3836            boolean external) {
3837        Rect from = new Rect();
3838        mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3839
3840        int[] finalPos = new int[2];
3841        float scaleXY[] = new float[2];
3842        boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
3843        getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3844                external, scalePreview);
3845
3846        Resources res = mLauncher.getResources();
3847        final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3848
3849        // In the case where we've prebound the widget, we remove it from the DragLayer
3850        if (finalView instanceof AppWidgetHostView && external) {
3851            Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
3852            mLauncher.getDragLayer().removeView(finalView);
3853        }
3854        if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3855            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3856            dragView.setCrossFadeBitmap(crossFadeBitmap);
3857            dragView.crossFade((int) (duration * 0.8f));
3858        } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
3859            scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
3860        }
3861
3862        DragLayer dragLayer = mLauncher.getDragLayer();
3863        if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3864            mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3865                    DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3866        } else {
3867            int endStyle;
3868            if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3869                endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3870            } else {
3871                endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
3872            }
3873
3874            Runnable onComplete = new Runnable() {
3875                @Override
3876                public void run() {
3877                    if (finalView != null) {
3878                        finalView.setVisibility(VISIBLE);
3879                    }
3880                    if (onCompleteRunnable != null) {
3881                        onCompleteRunnable.run();
3882                    }
3883                }
3884            };
3885            dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3886                    finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3887                    duration, this);
3888        }
3889    }
3890
3891    public void setFinalTransitionTransform(CellLayout layout) {
3892        if (isSwitchingState()) {
3893            mCurrentScale = getScaleX();
3894            setScaleX(mNewScale);
3895            setScaleY(mNewScale);
3896        }
3897    }
3898    public void resetTransitionTransform(CellLayout layout) {
3899        if (isSwitchingState()) {
3900            setScaleX(mCurrentScale);
3901            setScaleY(mCurrentScale);
3902        }
3903    }
3904
3905    /**
3906     * Return the current {@link CellLayout}, correctly picking the destination
3907     * screen while a scroll is in progress.
3908     */
3909    public CellLayout getCurrentDropLayout() {
3910        return (CellLayout) getChildAt(getNextPage());
3911    }
3912
3913    /**
3914     * Return the current CellInfo describing our current drag; this method exists
3915     * so that Launcher can sync this object with the correct info when the activity is created/
3916     * destroyed
3917     *
3918     */
3919    public CellLayout.CellInfo getDragInfo() {
3920        return mDragInfo;
3921    }
3922
3923    public int getRestorePage() {
3924        return getNextPage() - numCustomPages();
3925    }
3926
3927    /**
3928     * Calculate the nearest cell where the given object would be dropped.
3929     *
3930     * pixelX and pixelY should be in the coordinate system of layout
3931     */
3932    private int[] findNearestArea(int pixelX, int pixelY,
3933            int spanX, int spanY, CellLayout layout, int[] recycle) {
3934        return layout.findNearestArea(
3935                pixelX, pixelY, spanX, spanY, recycle);
3936    }
3937
3938    void setup(DragController dragController) {
3939        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3940        mDragController = dragController;
3941
3942        // hardware layers on children are enabled on startup, but should be disabled until
3943        // needed
3944        updateChildrenLayersEnabled(false);
3945        setWallpaperDimension();
3946    }
3947
3948    /**
3949     * Called at the end of a drag which originated on the workspace.
3950     */
3951    public void onDropCompleted(final View target, final DragObject d,
3952            final boolean isFlingToDelete, final boolean success) {
3953        if (mDeferDropAfterUninstall) {
3954            mDeferredAction = new Runnable() {
3955                public void run() {
3956                    onDropCompleted(target, d, isFlingToDelete, success);
3957                    mDeferredAction = null;
3958                }
3959            };
3960            return;
3961        }
3962
3963        boolean beingCalledAfterUninstall = mDeferredAction != null;
3964
3965        if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
3966            if (target != this && mDragInfo != null) {
3967                CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
3968                if (parentCell != null) {
3969                    parentCell.removeView(mDragInfo.cell);
3970                }
3971                if (mDragInfo.cell instanceof DropTarget) {
3972                    mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
3973                }
3974                // If we move the item to anything not on the Workspace, check if any empty
3975                // screens need to be removed. If we dropped back on the workspace, this will
3976                // be done post drop animation.
3977                removeExtraEmptyScreen(true, null, 0, true);
3978            }
3979        } else if (mDragInfo != null) {
3980            CellLayout cellLayout;
3981            if (mLauncher.isHotseatLayout(target)) {
3982                cellLayout = mLauncher.getHotseat().getLayout();
3983            } else {
3984                cellLayout = getScreenWithId(mDragInfo.screenId);
3985            }
3986            cellLayout.onDropChild(mDragInfo.cell);
3987        }
3988        if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
3989                && mDragInfo.cell != null) {
3990            mDragInfo.cell.setVisibility(VISIBLE);
3991        }
3992        mDragOutline = null;
3993        mDragInfo = null;
3994    }
3995
3996    public void deferCompleteDropAfterUninstallActivity() {
3997        mDeferDropAfterUninstall = true;
3998    }
3999
4000    /// maybe move this into a smaller part
4001    public void onUninstallActivityReturned(boolean success) {
4002        mDeferDropAfterUninstall = false;
4003        mUninstallSuccessful = success;
4004        if (mDeferredAction != null) {
4005            mDeferredAction.run();
4006        }
4007    }
4008
4009    void updateItemLocationsInDatabase(CellLayout cl) {
4010        int count = cl.getShortcutsAndWidgets().getChildCount();
4011
4012        long screenId = getIdForScreen(cl);
4013        int container = Favorites.CONTAINER_DESKTOP;
4014
4015        if (mLauncher.isHotseatLayout(cl)) {
4016            screenId = -1;
4017            container = Favorites.CONTAINER_HOTSEAT;
4018        }
4019
4020        for (int i = 0; i < count; i++) {
4021            View v = cl.getShortcutsAndWidgets().getChildAt(i);
4022            ItemInfo info = (ItemInfo) v.getTag();
4023            // Null check required as the AllApps button doesn't have an item info
4024            if (info != null && info.requiresDbUpdate) {
4025                info.requiresDbUpdate = false;
4026                LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
4027                        info.cellY, info.spanX, info.spanY);
4028            }
4029        }
4030    }
4031
4032    ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplicates) {
4033        ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
4034        getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, false);
4035        int count = getChildCount();
4036        for (int i = 0; i < count; i++) {
4037            CellLayout cl = (CellLayout) getChildAt(i);
4038            getUniqueIntents(cl, uniqueIntents, duplicates, false);
4039        }
4040        return uniqueIntents;
4041    }
4042
4043    void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
4044            ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
4045        int count = cl.getShortcutsAndWidgets().getChildCount();
4046
4047        ArrayList<View> children = new ArrayList<View>();
4048        for (int i = 0; i < count; i++) {
4049            View v = cl.getShortcutsAndWidgets().getChildAt(i);
4050            children.add(v);
4051        }
4052
4053        for (int i = 0; i < count; i++) {
4054            View v = children.get(i);
4055            ItemInfo info = (ItemInfo) v.getTag();
4056            // Null check required as the AllApps button doesn't have an item info
4057            if (info instanceof ShortcutInfo) {
4058                ShortcutInfo si = (ShortcutInfo) info;
4059                ComponentName cn = si.intent.getComponent();
4060
4061                Uri dataUri = si.intent.getData();
4062                // If dataUri is not null / empty or if this component isn't one that would
4063                // have previously showed up in the AllApps list, then this is a widget-type
4064                // shortcut, so ignore it.
4065                if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4066                    continue;
4067                }
4068
4069                if (!uniqueIntents.contains(cn)) {
4070                    uniqueIntents.add(cn);
4071                } else {
4072                    if (stripDuplicates) {
4073                        cl.removeViewInLayout(v);
4074                        LauncherModel.deleteItemFromDatabase(mLauncher, si);
4075                    }
4076                    if (duplicates != null) {
4077                        duplicates.add(cn);
4078                    }
4079                }
4080            }
4081            if (v instanceof FolderIcon) {
4082                FolderIcon fi = (FolderIcon) v;
4083                ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
4084                for (int j = 0; j < items.size(); j++) {
4085                    if (items.get(j).getTag() instanceof ShortcutInfo) {
4086                        ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
4087                        ComponentName cn = si.intent.getComponent();
4088
4089                        Uri dataUri = si.intent.getData();
4090                        // If dataUri is not null / empty or if this component isn't one that would
4091                        // have previously showed up in the AllApps list, then this is a widget-type
4092                        // shortcut, so ignore it.
4093                        if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4094                            continue;
4095                        }
4096
4097                        if (!uniqueIntents.contains(cn)) {
4098                            uniqueIntents.add(cn);
4099                        }  else {
4100                            if (stripDuplicates) {
4101                                fi.getFolderInfo().remove(si);
4102                                LauncherModel.deleteItemFromDatabase(mLauncher, si);
4103                            }
4104                            if (duplicates != null) {
4105                                duplicates.add(cn);
4106                            }
4107                        }
4108                    }
4109                }
4110            }
4111        }
4112    }
4113
4114    void saveWorkspaceToDb() {
4115        saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
4116        int count = getChildCount();
4117        for (int i = 0; i < count; i++) {
4118            CellLayout cl = (CellLayout) getChildAt(i);
4119            saveWorkspaceScreenToDb(cl);
4120        }
4121    }
4122
4123    void saveWorkspaceScreenToDb(CellLayout cl) {
4124        int count = cl.getShortcutsAndWidgets().getChildCount();
4125
4126        long screenId = getIdForScreen(cl);
4127        int container = Favorites.CONTAINER_DESKTOP;
4128
4129        Hotseat hotseat = mLauncher.getHotseat();
4130        if (mLauncher.isHotseatLayout(cl)) {
4131            screenId = -1;
4132            container = Favorites.CONTAINER_HOTSEAT;
4133        }
4134
4135        for (int i = 0; i < count; i++) {
4136            View v = cl.getShortcutsAndWidgets().getChildAt(i);
4137            ItemInfo info = (ItemInfo) v.getTag();
4138            // Null check required as the AllApps button doesn't have an item info
4139            if (info != null) {
4140                int cellX = info.cellX;
4141                int cellY = info.cellY;
4142                if (container == Favorites.CONTAINER_HOTSEAT) {
4143                    cellX = hotseat.getCellXFromOrder((int) info.screenId);
4144                    cellY = hotseat.getCellYFromOrder((int) info.screenId);
4145                }
4146                LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
4147                        cellY, false);
4148            }
4149            if (v instanceof FolderIcon) {
4150                FolderIcon fi = (FolderIcon) v;
4151                fi.getFolder().addItemLocationsInDatabase();
4152            }
4153        }
4154    }
4155
4156    @Override
4157    public boolean supportsFlingToDelete() {
4158        return true;
4159    }
4160
4161    @Override
4162    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
4163        // Do nothing
4164    }
4165
4166    @Override
4167    public void onFlingToDeleteCompleted() {
4168        // Do nothing
4169    }
4170
4171    public boolean isDropEnabled() {
4172        return true;
4173    }
4174
4175    @Override
4176    protected void onRestoreInstanceState(Parcelable state) {
4177        super.onRestoreInstanceState(state);
4178        Launcher.setScreen(mCurrentPage);
4179    }
4180
4181    @Override
4182    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
4183        // We don't dispatch restoreInstanceState to our children using this code path.
4184        // Some pages will be restored immediately as their items are bound immediately, and
4185        // others we will need to wait until after their items are bound.
4186        mSavedStates = container;
4187    }
4188
4189    public void restoreInstanceStateForChild(int child) {
4190        if (mSavedStates != null) {
4191            mRestoredPages.add(child);
4192            CellLayout cl = (CellLayout) getChildAt(child);
4193            cl.restoreInstanceState(mSavedStates);
4194        }
4195    }
4196
4197    public void restoreInstanceStateForRemainingPages() {
4198        int count = getChildCount();
4199        for (int i = 0; i < count; i++) {
4200            if (!mRestoredPages.contains(i)) {
4201                restoreInstanceStateForChild(i);
4202            }
4203        }
4204        mRestoredPages.clear();
4205        mSavedStates = null;
4206    }
4207
4208    @Override
4209    public void scrollLeft() {
4210        if (!isSmall() && !mIsSwitchingState) {
4211            super.scrollLeft();
4212        }
4213        Folder openFolder = getOpenFolder();
4214        if (openFolder != null) {
4215            openFolder.completeDragExit();
4216        }
4217    }
4218
4219    @Override
4220    public void scrollRight() {
4221        if (!isSmall() && !mIsSwitchingState) {
4222            super.scrollRight();
4223        }
4224        Folder openFolder = getOpenFolder();
4225        if (openFolder != null) {
4226            openFolder.completeDragExit();
4227        }
4228    }
4229
4230    @Override
4231    public boolean onEnterScrollArea(int x, int y, int direction) {
4232        // Ignore the scroll area if we are dragging over the hot seat
4233        boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
4234        if (mLauncher.getHotseat() != null && isPortrait) {
4235            Rect r = new Rect();
4236            mLauncher.getHotseat().getHitRect(r);
4237            if (r.contains(x, y)) {
4238                return false;
4239            }
4240        }
4241
4242        boolean result = false;
4243        if (!isSmall() && !mIsSwitchingState && getOpenFolder() == null) {
4244            mInScrollArea = true;
4245
4246            final int page = getNextPage() +
4247                       (direction == DragController.SCROLL_LEFT ? -1 : 1);
4248            // We always want to exit the current layout to ensure parity of enter / exit
4249            setCurrentDropLayout(null);
4250
4251            if (0 <= page && page < getChildCount()) {
4252                // Ensure that we are not dragging over to the custom content screen
4253                if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
4254                    return false;
4255                }
4256
4257                CellLayout layout = (CellLayout) getChildAt(page);
4258                setCurrentDragOverlappingLayout(layout);
4259
4260                // Workspace is responsible for drawing the edge glow on adjacent pages,
4261                // so we need to redraw the workspace when this may have changed.
4262                invalidate();
4263                result = true;
4264            }
4265        }
4266        return result;
4267    }
4268
4269    @Override
4270    public boolean onExitScrollArea() {
4271        boolean result = false;
4272        if (mInScrollArea) {
4273            invalidate();
4274            CellLayout layout = getCurrentDropLayout();
4275            setCurrentDropLayout(layout);
4276            setCurrentDragOverlappingLayout(layout);
4277
4278            result = true;
4279            mInScrollArea = false;
4280        }
4281        return result;
4282    }
4283
4284    private void onResetScrollArea() {
4285        setCurrentDragOverlappingLayout(null);
4286        mInScrollArea = false;
4287    }
4288
4289    /**
4290     * Returns a specific CellLayout
4291     */
4292    CellLayout getParentCellLayoutForView(View v) {
4293        ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
4294        for (CellLayout layout : layouts) {
4295            if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
4296                return layout;
4297            }
4298        }
4299        return null;
4300    }
4301
4302    /**
4303     * Returns a list of all the CellLayouts in the workspace.
4304     */
4305    ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
4306        ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
4307        int screenCount = getChildCount();
4308        for (int screen = 0; screen < screenCount; screen++) {
4309            layouts.add(((CellLayout) getChildAt(screen)));
4310        }
4311        if (mLauncher.getHotseat() != null) {
4312            layouts.add(mLauncher.getHotseat().getLayout());
4313        }
4314        return layouts;
4315    }
4316
4317    /**
4318     * We should only use this to search for specific children.  Do not use this method to modify
4319     * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
4320     * the hotseat and workspace pages
4321     */
4322    ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
4323        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4324                new ArrayList<ShortcutAndWidgetContainer>();
4325        int screenCount = getChildCount();
4326        for (int screen = 0; screen < screenCount; screen++) {
4327            childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
4328        }
4329        if (mLauncher.getHotseat() != null) {
4330            childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
4331        }
4332        return childrenLayouts;
4333    }
4334
4335    public Folder getFolderForTag(Object tag) {
4336        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4337                getAllShortcutAndWidgetContainers();
4338        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4339            int count = layout.getChildCount();
4340            for (int i = 0; i < count; i++) {
4341                View child = layout.getChildAt(i);
4342                if (child instanceof Folder) {
4343                    Folder f = (Folder) child;
4344                    if (f.getInfo() == tag && f.getInfo().opened) {
4345                        return f;
4346                    }
4347                }
4348            }
4349        }
4350        return null;
4351    }
4352
4353    public View getViewForTag(Object tag) {
4354        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4355                getAllShortcutAndWidgetContainers();
4356        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4357            int count = layout.getChildCount();
4358            for (int i = 0; i < count; i++) {
4359                View child = layout.getChildAt(i);
4360                if (child.getTag() == tag) {
4361                    return child;
4362                }
4363            }
4364        }
4365        return null;
4366    }
4367
4368    void clearDropTargets() {
4369        ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4370                getAllShortcutAndWidgetContainers();
4371        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4372            int childCount = layout.getChildCount();
4373            for (int j = 0; j < childCount; j++) {
4374                View v = layout.getChildAt(j);
4375                if (v instanceof DropTarget) {
4376                    mDragController.removeDropTarget((DropTarget) v);
4377                }
4378            }
4379        }
4380    }
4381
4382    // Removes ALL items that match a given package name, this is usually called when a package
4383    // has been removed and we want to remove all components (widgets, shortcuts, apps) that
4384    // belong to that package.
4385    void removeItemsByPackageName(final ArrayList<String> packages) {
4386        final HashSet<String> packageNames = new HashSet<String>();
4387        packageNames.addAll(packages);
4388
4389        // Filter out all the ItemInfos that this is going to affect
4390        final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
4391        final HashSet<ComponentName> cns = new HashSet<ComponentName>();
4392        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4393        for (CellLayout layoutParent : cellLayouts) {
4394            ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4395            int childCount = layout.getChildCount();
4396            for (int i = 0; i < childCount; ++i) {
4397                View view = layout.getChildAt(i);
4398                infos.add((ItemInfo) view.getTag());
4399            }
4400        }
4401        LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4402            @Override
4403            public boolean filterItem(ItemInfo parent, ItemInfo info,
4404                                      ComponentName cn) {
4405                if (packageNames.contains(cn.getPackageName())) {
4406                    cns.add(cn);
4407                    return true;
4408                }
4409                return false;
4410            }
4411        };
4412        LauncherModel.filterItemInfos(infos, filter);
4413
4414        // Remove the affected components
4415        removeItemsByComponentName(cns);
4416    }
4417
4418    // Removes items that match the application info specified, when applications are removed
4419    // as a part of an update, this is called to ensure that other widgets and application
4420    // shortcuts are not removed.
4421    void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos) {
4422        // Just create a hash table of all the specific components that this will affect
4423        HashSet<ComponentName> cns = new HashSet<ComponentName>();
4424        for (AppInfo info : appInfos) {
4425            cns.add(info.componentName);
4426        }
4427
4428        // Remove all the things
4429        removeItemsByComponentName(cns);
4430    }
4431
4432    void removeItemsByComponentName(final HashSet<ComponentName> componentNames) {
4433        ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4434        for (final CellLayout layoutParent: cellLayouts) {
4435            final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4436
4437            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4438            for (int j = 0; j < layout.getChildCount(); j++) {
4439                final View view = layout.getChildAt(j);
4440                children.put((ItemInfo) view.getTag(), view);
4441            }
4442
4443            final ArrayList<View> childrenToRemove = new ArrayList<View>();
4444            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
4445                    new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
4446            LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4447                @Override
4448                public boolean filterItem(ItemInfo parent, ItemInfo info,
4449                                          ComponentName cn) {
4450                    if (parent instanceof FolderInfo) {
4451                        if (componentNames.contains(cn)) {
4452                            FolderInfo folder = (FolderInfo) parent;
4453                            ArrayList<ShortcutInfo> appsToRemove;
4454                            if (folderAppsToRemove.containsKey(folder)) {
4455                                appsToRemove = folderAppsToRemove.get(folder);
4456                            } else {
4457                                appsToRemove = new ArrayList<ShortcutInfo>();
4458                                folderAppsToRemove.put(folder, appsToRemove);
4459                            }
4460                            appsToRemove.add((ShortcutInfo) info);
4461                            return true;
4462                        }
4463                    } else {
4464                        if (componentNames.contains(cn)) {
4465                            childrenToRemove.add(children.get(info));
4466                            return true;
4467                        }
4468                    }
4469                    return false;
4470                }
4471            };
4472            LauncherModel.filterItemInfos(children.keySet(), filter);
4473
4474            // Remove all the apps from their folders
4475            for (FolderInfo folder : folderAppsToRemove.keySet()) {
4476                ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4477                for (ShortcutInfo info : appsToRemove) {
4478                    folder.remove(info);
4479                }
4480            }
4481
4482            // Remove all the other children
4483            for (View child : childrenToRemove) {
4484                // Note: We can not remove the view directly from CellLayoutChildren as this
4485                // does not re-mark the spaces as unoccupied.
4486                layoutParent.removeViewInLayout(child);
4487                if (child instanceof DropTarget) {
4488                    mDragController.removeDropTarget((DropTarget) child);
4489                }
4490            }
4491
4492            if (childrenToRemove.size() > 0) {
4493                layout.requestLayout();
4494                layout.invalidate();
4495            }
4496        }
4497
4498        // Strip all the empty screens
4499        stripEmptyScreens();
4500    }
4501
4502    private void updateShortcut(HashMap<ComponentName, AppInfo> appsMap, ItemInfo info,
4503                                View child) {
4504        ComponentName cn = info.getIntent().getComponent();
4505        if (cn != null) {
4506            AppInfo appInfo = appsMap.get(info.getIntent().getComponent());
4507            if ((appInfo != null) && LauncherModel.isShortcutInfoUpdateable(info)) {
4508                ShortcutInfo shortcutInfo = (ShortcutInfo) info;
4509                BubbleTextView shortcut = (BubbleTextView) child;
4510                shortcutInfo.updateIcon(mIconCache);
4511                shortcutInfo.title = appInfo.title.toString();
4512                shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache);
4513            }
4514        }
4515    }
4516
4517    void updateShortcuts(ArrayList<AppInfo> apps) {
4518        // Create a map of the apps to test against
4519        final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>();
4520        for (AppInfo ai : apps) {
4521            appsMap.put(ai.componentName, ai);
4522        }
4523
4524        ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
4525        for (ShortcutAndWidgetContainer layout: childrenLayouts) {
4526            // Update all the children shortcuts
4527            final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4528            for (int j = 0; j < layout.getChildCount(); j++) {
4529                View v = layout.getChildAt(j);
4530                ItemInfo info = (ItemInfo) v.getTag();
4531                if (info instanceof FolderInfo && v instanceof FolderIcon) {
4532                    FolderIcon folder = (FolderIcon) v;
4533                    ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
4534                    for (View fv : folderChildren) {
4535                        info = (ItemInfo) fv.getTag();
4536                        updateShortcut(appsMap, info, fv);
4537                    }
4538                    folder.invalidate();
4539                } else if (info instanceof ShortcutInfo) {
4540                    updateShortcut(appsMap, info, v);
4541                }
4542            }
4543        }
4544    }
4545
4546    private void moveToScreen(int page, boolean animate) {
4547        if (!isSmall()) {
4548            if (animate) {
4549                snapToPage(page);
4550            } else {
4551                setCurrentPage(page);
4552            }
4553        }
4554        View child = getChildAt(page);
4555        if (child != null) {
4556            child.requestFocus();
4557        }
4558    }
4559
4560    void moveToDefaultScreen(boolean animate) {
4561        moveToScreen(mDefaultPage, animate);
4562    }
4563
4564    void moveToCustomContentScreen(boolean animate) {
4565        if (hasCustomContent()) {
4566            int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
4567            if (animate) {
4568                snapToPage(ccIndex);
4569            } else {
4570                setCurrentPage(ccIndex);
4571            }
4572            View child = getChildAt(ccIndex);
4573            if (child != null) {
4574                child.requestFocus();
4575            }
4576         }
4577        exitWidgetResizeMode();
4578    }
4579
4580    @Override
4581    protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
4582        long screenId = getScreenIdForPageIndex(pageIndex);
4583        if (screenId == EXTRA_EMPTY_SCREEN_ID) {
4584            int count = mScreenOrder.size() - numCustomPages();
4585            if (count > 1) {
4586                return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
4587                        R.drawable.ic_pageindicator_add);
4588            }
4589        }
4590
4591        return super.getPageIndicatorMarker(pageIndex);
4592    }
4593
4594    @Override
4595    public void syncPages() {
4596    }
4597
4598    @Override
4599    public void syncPageItems(int page, boolean immediate) {
4600    }
4601
4602    protected String getPageIndicatorDescription() {
4603        String settings = getResources().getString(R.string.settings_button_text);
4604        return getCurrentPageDescription() + ", " + settings;
4605    }
4606
4607    protected String getCurrentPageDescription() {
4608        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
4609        int delta = numCustomPages();
4610        if (hasCustomContent() && getNextPage() == 0) {
4611            return mCustomContentDescription;
4612        }
4613        return String.format(getContext().getString(R.string.workspace_scroll_format),
4614                page + 1 - delta, getChildCount() - delta);
4615    }
4616
4617    public void getLocationInDragLayer(int[] loc) {
4618        mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
4619    }
4620}
4621