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