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