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