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