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