summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/Workspace.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher3/Workspace.java')
-rw-r--r--src/com/android/launcher3/Workspace.java3894
1 files changed, 3894 insertions, 0 deletions
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
new file mode 100644
index 000000000..4f1fb0809
--- /dev/null
+++ b/src/com/android/launcher3/Workspace.java
@@ -0,0 +1,3894 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.WallpaperManager;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
+import android.graphics.drawable.Drawable;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.FolderIcon.FolderRingAnimator;
+import com.android.launcher3.LauncherSettings.Favorites;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * The workspace is a wide area with a wallpaper and a finite number of pages.
+ * Each page contains a number of icons, folders or widgets the user can
+ * interact with. A workspace is meant to be used with a fixed width only.
+ */
+public class Workspace extends SmoothPagedView
+ implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
+ DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener {
+ private static final String TAG = "Launcher.Workspace";
+
+ // Y rotation to apply to the workspace screens
+ private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
+
+ private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
+ private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
+ private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
+
+ private static final int BACKGROUND_FADE_OUT_DURATION = 350;
+ private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
+ private static final int FLING_THRESHOLD_VELOCITY = 500;
+
+ // These animators are used to fade the children's outlines
+ private ObjectAnimator mChildrenOutlineFadeInAnimation;
+ private ObjectAnimator mChildrenOutlineFadeOutAnimation;
+ private float mChildrenOutlineAlpha = 0;
+
+ // These properties refer to the background protection gradient used for AllApps and Customize
+ private ValueAnimator mBackgroundFadeInAnimation;
+ private ValueAnimator mBackgroundFadeOutAnimation;
+ private Drawable mBackground;
+ boolean mDrawBackground = true;
+ private float mBackgroundAlpha = 0;
+
+ private float mWallpaperScrollRatio = 1.0f;
+ private int mOriginalPageSpacing;
+
+ private final WallpaperManager mWallpaperManager;
+ private IBinder mWindowToken;
+ private static final float WALLPAPER_SCREENS_SPAN = 2f;
+
+ private int mDefaultPage;
+
+ /**
+ * CellInfo for the cell that is currently being dragged
+ */
+ private CellLayout.CellInfo mDragInfo;
+
+ /**
+ * Target drop area calculated during last acceptDrop call.
+ */
+ private int[] mTargetCell = new int[2];
+ private int mDragOverX = -1;
+ private int mDragOverY = -1;
+
+ static Rect mLandscapeCellLayoutMetrics = null;
+ static Rect mPortraitCellLayoutMetrics = null;
+
+ /**
+ * The CellLayout that is currently being dragged over
+ */
+ private CellLayout mDragTargetLayout = null;
+ /**
+ * The CellLayout that we will show as glowing
+ */
+ private CellLayout mDragOverlappingLayout = null;
+
+ /**
+ * The CellLayout which will be dropped to
+ */
+ private CellLayout mDropToLayout = null;
+
+ private Launcher mLauncher;
+ private IconCache mIconCache;
+ private DragController mDragController;
+
+ // These are temporary variables to prevent having to allocate a new object just to
+ // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
+ private int[] mTempCell = new int[2];
+ private int[] mTempEstimate = new int[2];
+ private float[] mDragViewVisualCenter = new float[2];
+ private float[] mTempDragCoordinates = new float[2];
+ private float[] mTempCellLayoutCenterCoordinates = new float[2];
+ private float[] mTempDragBottomRightCoordinates = new float[2];
+ private Matrix mTempInverseMatrix = new Matrix();
+
+ private SpringLoadedDragController mSpringLoadedDragController;
+ private float mSpringLoadedShrinkFactor;
+
+ private static final int DEFAULT_CELL_COUNT_X = 4;
+ private static final int DEFAULT_CELL_COUNT_Y = 4;
+
+ // State variable that indicates whether the pages are small (ie when you're
+ // in all apps or customize mode)
+
+ enum State { NORMAL, SPRING_LOADED, SMALL };
+ private State mState = State.NORMAL;
+ private boolean mIsSwitchingState = false;
+
+ boolean mAnimatingViewIntoPlace = false;
+ boolean mIsDragOccuring = false;
+ boolean mChildrenLayersEnabled = true;
+
+ /** Is the user is dragging an item near the edge of a page? */
+ private boolean mInScrollArea = false;
+
+ private final HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper();
+ private Bitmap mDragOutline = null;
+ private final Rect mTempRect = new Rect();
+ private final int[] mTempXY = new int[2];
+ private int[] mTempVisiblePagesRange = new int[2];
+ private float mOverscrollFade = 0;
+ private boolean mOverscrollTransformsSet;
+ public static final int DRAG_BITMAP_PADDING = 2;
+ private boolean mWorkspaceFadeInAdjacentScreens;
+
+ enum WallpaperVerticalOffset { TOP, MIDDLE, BOTTOM };
+ int mWallpaperWidth;
+ int mWallpaperHeight;
+ WallpaperOffsetInterpolator mWallpaperOffset;
+ boolean mUpdateWallpaperOffsetImmediately = false;
+ private Runnable mDelayedResizeRunnable;
+ private Runnable mDelayedSnapToPageRunnable;
+ private Point mDisplaySize = new Point();
+ private boolean mIsStaticWallpaper;
+ private int mWallpaperTravelWidth;
+ private int mSpringLoadedPageSpacing;
+ private int mCameraDistance;
+
+ // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
+ private static final int FOLDER_CREATION_TIMEOUT = 0;
+ private static final int REORDER_TIMEOUT = 250;
+ private final Alarm mFolderCreationAlarm = new Alarm();
+ private final Alarm mReorderAlarm = new Alarm();
+ private FolderRingAnimator mDragFolderRingAnimator = null;
+ private FolderIcon mDragOverFolderIcon = null;
+ private boolean mCreateUserFolderOnDrop = false;
+ private boolean mAddToExistingFolderOnDrop = false;
+ private DropTarget.DragEnforcer mDragEnforcer;
+ private float mMaxDistanceForFolderCreation;
+
+ // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
+ private float mXDown;
+ private float mYDown;
+ final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
+ final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
+ final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
+
+ // Relating to the animation of items being dropped externally
+ public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
+ public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
+ public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
+ public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
+ public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
+
+ // Related to dragging, folder creation and reordering
+ private static final int DRAG_MODE_NONE = 0;
+ private static final int DRAG_MODE_CREATE_FOLDER = 1;
+ private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
+ private static final int DRAG_MODE_REORDER = 3;
+ private int mDragMode = DRAG_MODE_NONE;
+ private int mLastReorderX = -1;
+ private int mLastReorderY = -1;
+
+ private SparseArray<Parcelable> mSavedStates;
+ private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
+
+ // These variables are used for storing the initial and final values during workspace animations
+ private int mSavedScrollX;
+ private float mSavedRotationY;
+ private float mSavedTranslationX;
+ private float mCurrentScaleX;
+ private float mCurrentScaleY;
+ private float mCurrentRotationY;
+ private float mCurrentTranslationX;
+ private float mCurrentTranslationY;
+ private float[] mOldTranslationXs;
+ private float[] mOldTranslationYs;
+ private float[] mOldScaleXs;
+ private float[] mOldScaleYs;
+ private float[] mOldBackgroundAlphas;
+ private float[] mOldAlphas;
+ private float[] mNewTranslationXs;
+ private float[] mNewTranslationYs;
+ private float[] mNewScaleXs;
+ private float[] mNewScaleYs;
+ private float[] mNewBackgroundAlphas;
+ private float[] mNewAlphas;
+ private float[] mNewRotationYs;
+ private float mTransitionProgress;
+
+ private final Runnable mBindPages = new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.getModel().bindRemainingSynchronousPages();
+ }
+ };
+
+ /**
+ * Used to inflate the Workspace from XML.
+ *
+ * @param context The application's context.
+ * @param attrs The attributes set containing the Workspace's customization values.
+ */
+ public Workspace(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Used to inflate the Workspace from XML.
+ *
+ * @param context The application's context.
+ * @param attrs The attributes set containing the Workspace's customization values.
+ * @param defStyle Unused.
+ */
+ public Workspace(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mContentIsRefreshable = false;
+ mOriginalPageSpacing = mPageSpacing;
+
+ mDragEnforcer = new DropTarget.DragEnforcer(context);
+ // With workspace, data is available straight from the get-go
+ setDataIsReady();
+
+ mLauncher = (Launcher) context;
+ final Resources res = getResources();
+ mWorkspaceFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens);
+ mFadeInAdjacentScreens = false;
+ mWallpaperManager = WallpaperManager.getInstance(context);
+
+ int cellCountX = DEFAULT_CELL_COUNT_X;
+ int cellCountY = DEFAULT_CELL_COUNT_Y;
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.Workspace, defStyle, 0);
+
+ if (LauncherApplication.isScreenLarge()) {
+ // Determine number of rows/columns dynamically
+ // TODO: This code currently fails on tablets with an aspect ratio < 1.3.
+ // Around that ratio we should make cells the same size in portrait and
+ // landscape
+ TypedArray actionBarSizeTypedArray =
+ context.obtainStyledAttributes(new int[] { android.R.attr.actionBarSize });
+ final float actionBarHeight = actionBarSizeTypedArray.getDimension(0, 0f);
+
+ Point minDims = new Point();
+ Point maxDims = new Point();
+ mLauncher.getWindowManager().getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
+
+ cellCountX = 1;
+ while (CellLayout.widthInPortrait(res, cellCountX + 1) <= minDims.x) {
+ cellCountX++;
+ }
+
+ cellCountY = 1;
+ while (actionBarHeight + CellLayout.heightInLandscape(res, cellCountY + 1)
+ <= minDims.y) {
+ cellCountY++;
+ }
+ }
+
+ mSpringLoadedShrinkFactor =
+ res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
+ mSpringLoadedPageSpacing =
+ res.getDimensionPixelSize(R.dimen.workspace_spring_loaded_page_spacing);
+ mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
+
+ // if the value is manually specified, use that instead
+ cellCountX = a.getInt(R.styleable.Workspace_cellCountX, cellCountX);
+ cellCountY = a.getInt(R.styleable.Workspace_cellCountY, cellCountY);
+ mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
+ a.recycle();
+
+ setOnHierarchyChangeListener(this);
+
+ LauncherModel.updateWorkspaceLayoutCells(cellCountX, cellCountY);
+ setHapticFeedbackEnabled(false);
+
+ initWorkspace();
+
+ // Disable multitouch across the workspace/all apps/customize tray
+ setMotionEventSplittingEnabled(true);
+
+ // Unless otherwise specified this view is important for accessibility.
+ if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+ }
+
+ // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
+ // dimension if unsuccessful
+ public int[] estimateItemSize(int hSpan, int vSpan,
+ ItemInfo itemInfo, boolean springLoaded) {
+ int[] size = new int[2];
+ if (getChildCount() > 0) {
+ CellLayout cl = (CellLayout) mLauncher.getWorkspace().getChildAt(0);
+ Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
+ size[0] = r.width();
+ size[1] = r.height();
+ if (springLoaded) {
+ size[0] *= mSpringLoadedShrinkFactor;
+ size[1] *= mSpringLoadedShrinkFactor;
+ }
+ return size;
+ } else {
+ size[0] = Integer.MAX_VALUE;
+ size[1] = Integer.MAX_VALUE;
+ return size;
+ }
+ }
+ public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
+ int hCell, int vCell, int hSpan, int vSpan) {
+ Rect r = new Rect();
+ cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
+ return r;
+ }
+
+ public void onDragStart(DragSource source, Object info, int dragAction) {
+ mIsDragOccuring = true;
+ updateChildrenLayersEnabled(false);
+ mLauncher.lockScreenOrientation();
+ setChildrenBackgroundAlphaMultipliers(1f);
+ // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
+ InstallShortcutReceiver.enableInstallQueue();
+ UninstallShortcutReceiver.enableUninstallQueue();
+ }
+
+ public void onDragEnd() {
+ mIsDragOccuring = false;
+ updateChildrenLayersEnabled(false);
+ mLauncher.unlockScreenOrientation(false);
+
+ // Re-enable any Un/InstallShortcutReceiver and now process any queued items
+ InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
+ UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
+ }
+
+ /**
+ * Initializes various states for this workspace.
+ */
+ protected void initWorkspace() {
+ Context context = getContext();
+ mCurrentPage = mDefaultPage;
+ Launcher.setScreen(mCurrentPage);
+ LauncherApplication app = (LauncherApplication)context.getApplicationContext();
+ mIconCache = app.getIconCache();
+ setWillNotDraw(false);
+ setClipChildren(false);
+ setClipToPadding(false);
+ setChildrenDrawnWithCacheEnabled(true);
+
+ final Resources res = getResources();
+ try {
+ mBackground = res.getDrawable(R.drawable.apps_customize_bg);
+ } catch (Resources.NotFoundException e) {
+ // In this case, we will skip drawing background protection
+ }
+
+ mWallpaperOffset = new WallpaperOffsetInterpolator();
+ Display display = mLauncher.getWindowManager().getDefaultDisplay();
+ display.getSize(mDisplaySize);
+ mWallpaperTravelWidth = (int) (mDisplaySize.x *
+ wallpaperTravelToScreenWidthRatio(mDisplaySize.x, mDisplaySize.y));
+
+ mMaxDistanceForFolderCreation = (0.55f * res.getDimensionPixelSize(R.dimen.app_icon_size));
+ mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
+ }
+
+ @Override
+ protected int getScrollMode() {
+ return SmoothPagedView.X_LARGE_MODE;
+ }
+
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ if (!(child instanceof CellLayout)) {
+ throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
+ }
+ CellLayout cl = ((CellLayout) child);
+ cl.setOnInterceptTouchListener(this);
+ cl.setClickable(true);
+ cl.setContentDescription(getContext().getString(
+ R.string.workspace_description_format, getChildCount()));
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ }
+
+ protected boolean shouldDrawChild(View child) {
+ final CellLayout cl = (CellLayout) child;
+ return super.shouldDrawChild(child) &&
+ (cl.getShortcutsAndWidgets().getAlpha() > 0 ||
+ cl.getBackgroundAlpha() > 0);
+ }
+
+ /**
+ * @return The open folder on the current screen, or null if there is none
+ */
+ Folder getOpenFolder() {
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ int count = dragLayer.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = dragLayer.getChildAt(i);
+ if (child instanceof Folder) {
+ Folder folder = (Folder) child;
+ if (folder.getInfo().opened)
+ return folder;
+ }
+ }
+ return null;
+ }
+
+ boolean isTouchActive() {
+ return mTouchState != TOUCH_STATE_REST;
+ }
+
+ /**
+ * Adds the specified child in the specified screen. The position and dimension of
+ * the child are defined by x, y, spanX and spanY.
+ *
+ * @param child The child to add in one of the workspace's screens.
+ * @param screen The screen in which to add the child.
+ * @param x The X position of the child in the screen's grid.
+ * @param y The Y position of the child in the screen's grid.
+ * @param spanX The number of cells spanned horizontally by the child.
+ * @param spanY The number of cells spanned vertically by the child.
+ */
+ void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY) {
+ addInScreen(child, container, screen, x, y, spanX, spanY, false);
+ }
+
+ /**
+ * Adds the specified child in the specified screen. The position and dimension of
+ * the child are defined by x, y, spanX and spanY.
+ *
+ * @param child The child to add in one of the workspace's screens.
+ * @param screen The screen in which to add the child.
+ * @param x The X position of the child in the screen's grid.
+ * @param y The Y position of the child in the screen's grid.
+ * @param spanX The number of cells spanned horizontally by the child.
+ * @param spanY The number of cells spanned vertically by the child.
+ * @param insert When true, the child is inserted at the beginning of the children list.
+ */
+ void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
+ boolean insert) {
+ if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (screen < 0 || screen >= getChildCount()) {
+ Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
+ + " (was " + screen + "); skipping child");
+ return;
+ }
+ }
+
+ final CellLayout layout;
+ if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ layout = mLauncher.getHotseat().getLayout();
+ child.setOnKeyListener(null);
+
+ // Hide folder title in the hotseat
+ if (child instanceof FolderIcon) {
+ ((FolderIcon) child).setTextVisible(false);
+ }
+
+ if (screen < 0) {
+ screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
+ } else {
+ // Note: We do this to ensure that the hotseat is always laid out in the orientation
+ // of the hotseat in order regardless of which orientation they were added
+ x = mLauncher.getHotseat().getCellXFromOrder(screen);
+ y = mLauncher.getHotseat().getCellYFromOrder(screen);
+ }
+ } else {
+ // Show folder title if not in the hotseat
+ if (child instanceof FolderIcon) {
+ ((FolderIcon) child).setTextVisible(true);
+ }
+
+ layout = (CellLayout) getChildAt(screen);
+ child.setOnKeyListener(new IconKeyEventListener());
+ }
+
+ LayoutParams genericLp = child.getLayoutParams();
+ CellLayout.LayoutParams lp;
+ if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
+ lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
+ } else {
+ lp = (CellLayout.LayoutParams) genericLp;
+ lp.cellX = x;
+ lp.cellY = y;
+ lp.cellHSpan = spanX;
+ lp.cellVSpan = spanY;
+ }
+
+ if (spanX < 0 && spanY < 0) {
+ lp.isLockedToGrid = false;
+ }
+
+ // Get the canonical child id to uniquely represent this view in this screen
+ int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY);
+ boolean markCellsAsOccupied = !(child instanceof Folder);
+ if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
+ // TODO: This branch occurs when the workspace is adding views
+ // outside of the defined grid
+ // maybe we should be deleting these items from the LauncherModel?
+ Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
+ }
+
+ if (!(child instanceof Folder)) {
+ child.setHapticFeedbackEnabled(false);
+ child.setOnLongClickListener(mLongClickListener);
+ }
+ if (child instanceof DropTarget) {
+ mDragController.addDropTarget((DropTarget) child);
+ }
+ }
+
+ /**
+ * Check if the point (x, y) hits a given page.
+ */
+ private boolean hitsPage(int index, float x, float y) {
+ final View page = getChildAt(index);
+ if (page != null) {
+ float[] localXY = { x, y };
+ mapPointFromSelfToChild(page, localXY);
+ return (localXY[0] >= 0 && localXY[0] < page.getWidth()
+ && localXY[1] >= 0 && localXY[1] < page.getHeight());
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean hitsPreviousPage(float x, float y) {
+ // mNextPage is set to INVALID_PAGE whenever we are stationary.
+ // Calculating "next page" this way ensures that you scroll to whatever page you tap on
+ final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage;
+
+ // Only allow tap to next page on large devices, where there's significant margin outside
+ // the active workspace
+ return LauncherApplication.isScreenLarge() && hitsPage(current - 1, x, y);
+ }
+
+ @Override
+ protected boolean hitsNextPage(float x, float y) {
+ // mNextPage is set to INVALID_PAGE whenever we are stationary.
+ // Calculating "next page" this way ensures that you scroll to whatever page you tap on
+ final int current = (mNextPage == INVALID_PAGE) ? mCurrentPage : mNextPage;
+
+ // Only allow tap to next page on large devices, where there's significant margin outside
+ // the active workspace
+ return LauncherApplication.isScreenLarge() && hitsPage(current + 1, x, y);
+ }
+
+ /**
+ * Called directly from a CellLayout (not by the framework), after we've been added as a
+ * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
+ * that it should intercept touch events, which is not something that is normally supported.
+ */
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return (isSmall() || !isFinishedSwitchingState());
+ }
+
+ public boolean isSwitchingState() {
+ return mIsSwitchingState;
+ }
+
+ /** This differs from isSwitchingState in that we take into account how far the transition
+ * has completed. */
+ public boolean isFinishedSwitchingState() {
+ return !mIsSwitchingState || (mTransitionProgress > 0.5f);
+ }
+
+ protected void onWindowVisibilityChanged (int visibility) {
+ mLauncher.onWindowVisibilityChanged(visibility);
+ }
+
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ if (isSmall() || !isFinishedSwitchingState()) {
+ // when the home screens are shrunken, shouldn't allow side-scrolling
+ return false;
+ }
+ return super.dispatchUnhandledMove(focused, direction);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ switch (ev.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ mXDown = ev.getX();
+ mYDown = ev.getY();
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_UP:
+ if (mTouchState == TOUCH_STATE_REST) {
+ final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
+ if (!currentPage.lastDownOnOccupiedCell()) {
+ onWallpaperTap(ev);
+ }
+ }
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ protected void reinflateWidgetsIfNecessary() {
+ final int clCount = getChildCount();
+ for (int i = 0; i < clCount; i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
+ final int itemCount = swc.getChildCount();
+ for (int j = 0; j < itemCount; j++) {
+ View v = swc.getChildAt(j);
+
+ if (v.getTag() instanceof LauncherAppWidgetInfo) {
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
+ LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
+ if (lahv != null && lahv.orientationChangedSincedInflation()) {
+ mLauncher.removeAppWidget(info);
+ // Remove the current widget which is inflated with the wrong orientation
+ cl.removeView(lahv);
+ mLauncher.bindAppWidget(info);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void determineScrollingStart(MotionEvent ev) {
+ if (isSmall()) return;
+ if (!isFinishedSwitchingState()) return;
+
+ float deltaX = Math.abs(ev.getX() - mXDown);
+ float deltaY = Math.abs(ev.getY() - mYDown);
+
+ if (Float.compare(deltaX, 0f) == 0) return;
+
+ float slope = deltaY / deltaX;
+ float theta = (float) Math.atan(slope);
+
+ if (deltaX > mTouchSlop || deltaY > mTouchSlop) {
+ cancelCurrentPageLongPress();
+ }
+
+ if (theta > MAX_SWIPE_ANGLE) {
+ // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
+ return;
+ } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
+ // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
+ // increase the touch slop to make it harder to begin scrolling the workspace. This
+ // results in vertically scrolling widgets to more easily. The higher the angle, the
+ // more we increase touch slop.
+ theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
+ float extraRatio = (float)
+ Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
+ super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
+ } else {
+ // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
+ super.determineScrollingStart(ev);
+ }
+ }
+
+ protected void onPageBeginMoving() {
+ super.onPageBeginMoving();
+
+ if (isHardwareAccelerated()) {
+ updateChildrenLayersEnabled(false);
+ } else {
+ if (mNextPage != INVALID_PAGE) {
+ // we're snapping to a particular screen
+ enableChildrenCache(mCurrentPage, mNextPage);
+ } else {
+ // this is when user is actively dragging a particular screen, they might
+ // swipe it either left or right (but we won't advance by more than one screen)
+ enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
+ }
+ }
+
+ // Only show page outlines as we pan if we are on large screen
+ if (LauncherApplication.isScreenLarge()) {
+ showOutlines();
+ mIsStaticWallpaper = mWallpaperManager.getWallpaperInfo() == null;
+ }
+
+ // If we are not fading in adjacent screens, we still need to restore the alpha in case the
+ // user scrolls while we are transitioning (should not affect dispatchDraw optimizations)
+ if (!mWorkspaceFadeInAdjacentScreens) {
+ for (int i = 0; i < getChildCount(); ++i) {
+ ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f);
+ }
+ }
+
+ // Show the scroll indicator as you pan the page
+ showScrollingIndicator(false);
+ }
+
+ protected void onPageEndMoving() {
+ super.onPageEndMoving();
+
+ if (isHardwareAccelerated()) {
+ updateChildrenLayersEnabled(false);
+ } else {
+ clearChildrenCache();
+ }
+
+
+ if (mDragController.isDragging()) {
+ if (isSmall()) {
+ // If we are in springloaded mode, then force an event to check if the current touch
+ // is under a new page (to scroll to)
+ mDragController.forceTouchMove();
+ }
+ } else {
+ // If we are not mid-dragging, hide the page outlines if we are on a large screen
+ if (LauncherApplication.isScreenLarge()) {
+ hideOutlines();
+ }
+
+ // Hide the scroll indicator as you pan the page
+ if (!mDragController.isDragging()) {
+ hideScrollingIndicator(false);
+ }
+ }
+
+ if (mDelayedResizeRunnable != null) {
+ mDelayedResizeRunnable.run();
+ mDelayedResizeRunnable = null;
+ }
+
+ if (mDelayedSnapToPageRunnable != null) {
+ mDelayedSnapToPageRunnable.run();
+ mDelayedSnapToPageRunnable = null;
+ }
+ }
+
+ @Override
+ protected void notifyPageSwitchListener() {
+ super.notifyPageSwitchListener();
+ Launcher.setScreen(mCurrentPage);
+ };
+
+ // As a ratio of screen height, the total distance we want the parallax effect to span
+ // horizontally
+ private float wallpaperTravelToScreenWidthRatio(int width, int height) {
+ float aspectRatio = width / (float) height;
+
+ // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
+ // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
+ // We will use these two data points to extrapolate how much the wallpaper parallax effect
+ // to span (ie travel) at any aspect ratio:
+
+ final float ASPECT_RATIO_LANDSCAPE = 16/10f;
+ final float ASPECT_RATIO_PORTRAIT = 10/16f;
+ final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
+ final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
+
+ // To find out the desired width at different aspect ratios, we use the following two
+ // formulas, where the coefficient on x is the aspect ratio (width/height):
+ // (16/10)x + y = 1.5
+ // (10/16)x + y = 1.2
+ // We solve for x and y and end up with a final formula:
+ final float x =
+ (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
+ (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
+ final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
+ return x * aspectRatio + y;
+ }
+
+ // The range of scroll values for Workspace
+ private int getScrollRange() {
+ return getChildOffset(getChildCount() - 1) - getChildOffset(0);
+ }
+
+ protected void setWallpaperDimension() {
+ Point minDims = new Point();
+ Point maxDims = new Point();
+ mLauncher.getWindowManager().getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
+
+ final int maxDim = Math.max(maxDims.x, maxDims.y);
+ final int minDim = Math.min(minDims.x, minDims.y);
+
+ // We need to ensure that there is enough extra space in the wallpaper for the intended
+ // parallax effects
+ if (LauncherApplication.isScreenLarge()) {
+ mWallpaperWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
+ mWallpaperHeight = maxDim;
+ } else {
+ mWallpaperWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
+ mWallpaperHeight = maxDim;
+ }
+ new Thread("setWallpaperDimension") {
+ public void run() {
+ mWallpaperManager.suggestDesiredDimensions(mWallpaperWidth, mWallpaperHeight);
+ }
+ }.start();
+ }
+
+ private float wallpaperOffsetForCurrentScroll() {
+ // Set wallpaper offset steps (1 / (number of screens - 1))
+ mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f);
+
+ // For the purposes of computing the scrollRange and overScrollOffset, we assume
+ // that mLayoutScale is 1. This means that when we're in spring-loaded mode,
+ // there's no discrepancy between the wallpaper offset for a given page.
+ float layoutScale = mLayoutScale;
+ mLayoutScale = 1f;
+ int scrollRange = getScrollRange();
+
+ // Again, we adjust the wallpaper offset to be consistent between values of mLayoutScale
+ float adjustedScrollX = Math.max(0, Math.min(getScrollX(), mMaxScrollX));
+ adjustedScrollX *= mWallpaperScrollRatio;
+ mLayoutScale = layoutScale;
+
+ float scrollProgress =
+ adjustedScrollX / (float) scrollRange;
+
+ if (LauncherApplication.isScreenLarge() && mIsStaticWallpaper) {
+ // The wallpaper travel width is how far, from left to right, the wallpaper will move
+ // at this orientation. On tablets in portrait mode we don't move all the way to the
+ // edges of the wallpaper, or otherwise the parallax effect would be too strong.
+ int wallpaperTravelWidth = Math.min(mWallpaperTravelWidth, mWallpaperWidth);
+
+ float offsetInDips = wallpaperTravelWidth * scrollProgress +
+ (mWallpaperWidth - wallpaperTravelWidth) / 2; // center it
+ float offset = offsetInDips / (float) mWallpaperWidth;
+ return offset;
+ } else {
+ return scrollProgress;
+ }
+ }
+
+ private void syncWallpaperOffsetWithScroll() {
+ final boolean enableWallpaperEffects = isHardwareAccelerated();
+ if (enableWallpaperEffects) {
+ mWallpaperOffset.setFinalX(wallpaperOffsetForCurrentScroll());
+ }
+ }
+
+ public void updateWallpaperOffsetImmediately() {
+ mUpdateWallpaperOffsetImmediately = true;
+ }
+
+ private void updateWallpaperOffsets() {
+ boolean updateNow = false;
+ boolean keepUpdating = true;
+ if (mUpdateWallpaperOffsetImmediately) {
+ updateNow = true;
+ keepUpdating = false;
+ mWallpaperOffset.jumpToFinal();
+ mUpdateWallpaperOffsetImmediately = false;
+ } else {
+ updateNow = keepUpdating = mWallpaperOffset.computeScrollOffset();
+ }
+ if (updateNow) {
+ if (mWindowToken != null) {
+ mWallpaperManager.setWallpaperOffsets(mWindowToken,
+ mWallpaperOffset.getCurrX(), mWallpaperOffset.getCurrY());
+ }
+ }
+ if (keepUpdating) {
+ invalidate();
+ }
+ }
+
+ @Override
+ protected void updateCurrentPageScroll() {
+ super.updateCurrentPageScroll();
+ computeWallpaperScrollRatio(mCurrentPage);
+ }
+
+ @Override
+ protected void snapToPage(int whichPage) {
+ super.snapToPage(whichPage);
+ computeWallpaperScrollRatio(whichPage);
+ }
+
+ @Override
+ protected void snapToPage(int whichPage, int duration) {
+ super.snapToPage(whichPage, duration);
+ computeWallpaperScrollRatio(whichPage);
+ }
+
+ protected void snapToPage(int whichPage, Runnable r) {
+ if (mDelayedSnapToPageRunnable != null) {
+ mDelayedSnapToPageRunnable.run();
+ }
+ mDelayedSnapToPageRunnable = r;
+ snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION);
+ }
+
+ private void computeWallpaperScrollRatio(int page) {
+ // Here, we determine what the desired scroll would be with and without a layout scale,
+ // and compute a ratio between the two. This allows us to adjust the wallpaper offset
+ // as though there is no layout scale.
+ float layoutScale = mLayoutScale;
+ int scaled = getChildOffset(page) - getRelativeChildOffset(page);
+ mLayoutScale = 1.0f;
+ float unscaled = getChildOffset(page) - getRelativeChildOffset(page);
+ mLayoutScale = layoutScale;
+ if (scaled > 0) {
+ mWallpaperScrollRatio = (1.0f * unscaled) / scaled;
+ } else {
+ mWallpaperScrollRatio = 1f;
+ }
+ }
+
+ class WallpaperOffsetInterpolator {
+ float mFinalHorizontalWallpaperOffset = 0.0f;
+ float mFinalVerticalWallpaperOffset = 0.5f;
+ float mHorizontalWallpaperOffset = 0.0f;
+ float mVerticalWallpaperOffset = 0.5f;
+ long mLastWallpaperOffsetUpdateTime;
+ boolean mIsMovingFast;
+ boolean mOverrideHorizontalCatchupConstant;
+ float mHorizontalCatchupConstant = 0.35f;
+ float mVerticalCatchupConstant = 0.35f;
+
+ public WallpaperOffsetInterpolator() {
+ }
+
+ public void setOverrideHorizontalCatchupConstant(boolean override) {
+ mOverrideHorizontalCatchupConstant = override;
+ }
+
+ public void setHorizontalCatchupConstant(float f) {
+ mHorizontalCatchupConstant = f;
+ }
+
+ public void setVerticalCatchupConstant(float f) {
+ mVerticalCatchupConstant = f;
+ }
+
+ public boolean computeScrollOffset() {
+ if (Float.compare(mHorizontalWallpaperOffset, mFinalHorizontalWallpaperOffset) == 0 &&
+ Float.compare(mVerticalWallpaperOffset, mFinalVerticalWallpaperOffset) == 0) {
+ mIsMovingFast = false;
+ return false;
+ }
+ boolean isLandscape = mDisplaySize.x > mDisplaySize.y;
+
+ long currentTime = System.currentTimeMillis();
+ long timeSinceLastUpdate = currentTime - mLastWallpaperOffsetUpdateTime;
+ timeSinceLastUpdate = Math.min((long) (1000/30f), timeSinceLastUpdate);
+ timeSinceLastUpdate = Math.max(1L, timeSinceLastUpdate);
+
+ float xdiff = Math.abs(mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset);
+ if (!mIsMovingFast && xdiff > 0.07) {
+ mIsMovingFast = true;
+ }
+
+ float fractionToCatchUpIn1MsHorizontal;
+ if (mOverrideHorizontalCatchupConstant) {
+ fractionToCatchUpIn1MsHorizontal = mHorizontalCatchupConstant;
+ } else if (mIsMovingFast) {
+ fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.5f : 0.75f;
+ } else {
+ // slow
+ fractionToCatchUpIn1MsHorizontal = isLandscape ? 0.27f : 0.5f;
+ }
+ float fractionToCatchUpIn1MsVertical = mVerticalCatchupConstant;
+
+ fractionToCatchUpIn1MsHorizontal /= 33f;
+ fractionToCatchUpIn1MsVertical /= 33f;
+
+ final float UPDATE_THRESHOLD = 0.00001f;
+ float hOffsetDelta = mFinalHorizontalWallpaperOffset - mHorizontalWallpaperOffset;
+ float vOffsetDelta = mFinalVerticalWallpaperOffset - mVerticalWallpaperOffset;
+ boolean jumpToFinalValue = Math.abs(hOffsetDelta) < UPDATE_THRESHOLD &&
+ Math.abs(vOffsetDelta) < UPDATE_THRESHOLD;
+
+ // Don't have any lag between workspace and wallpaper on non-large devices
+ if (!LauncherApplication.isScreenLarge() || jumpToFinalValue) {
+ mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset;
+ mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset;
+ } else {
+ float percentToCatchUpVertical =
+ Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsVertical);
+ float percentToCatchUpHorizontal =
+ Math.min(1.0f, timeSinceLastUpdate * fractionToCatchUpIn1MsHorizontal);
+ mHorizontalWallpaperOffset += percentToCatchUpHorizontal * hOffsetDelta;
+ mVerticalWallpaperOffset += percentToCatchUpVertical * vOffsetDelta;
+ }
+
+ mLastWallpaperOffsetUpdateTime = System.currentTimeMillis();
+ return true;
+ }
+
+ public float getCurrX() {
+ return mHorizontalWallpaperOffset;
+ }
+
+ public float getFinalX() {
+ return mFinalHorizontalWallpaperOffset;
+ }
+
+ public float getCurrY() {
+ return mVerticalWallpaperOffset;
+ }
+
+ public float getFinalY() {
+ return mFinalVerticalWallpaperOffset;
+ }
+
+ public void setFinalX(float x) {
+ mFinalHorizontalWallpaperOffset = Math.max(0f, Math.min(x, 1.0f));
+ }
+
+ public void setFinalY(float y) {
+ mFinalVerticalWallpaperOffset = Math.max(0f, Math.min(y, 1.0f));
+ }
+
+ public void jumpToFinal() {
+ mHorizontalWallpaperOffset = mFinalHorizontalWallpaperOffset;
+ mVerticalWallpaperOffset = mFinalVerticalWallpaperOffset;
+ }
+ }
+
+ @Override
+ public void computeScroll() {
+ super.computeScroll();
+ syncWallpaperOffsetWithScroll();
+ }
+
+ void showOutlines() {
+ if (!isSmall() && !mIsSwitchingState) {
+ if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+ if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+ mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0f);
+ mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
+ mChildrenOutlineFadeInAnimation.start();
+ }
+ }
+
+ void hideOutlines() {
+ if (!isSmall() && !mIsSwitchingState) {
+ if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+ if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+ mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.0f);
+ mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
+ mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
+ mChildrenOutlineFadeOutAnimation.start();
+ }
+ }
+
+ public void showOutlinesTemporarily() {
+ if (!mIsPageMoving && !isTouchActive()) {
+ snapToPage(mCurrentPage);
+ }
+ }
+
+ public void setChildrenOutlineAlpha(float alpha) {
+ mChildrenOutlineAlpha = alpha;
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ cl.setBackgroundAlpha(alpha);
+ }
+ }
+
+ public float getChildrenOutlineAlpha() {
+ return mChildrenOutlineAlpha;
+ }
+
+ void disableBackground() {
+ mDrawBackground = false;
+ }
+ void enableBackground() {
+ mDrawBackground = true;
+ }
+
+ private void animateBackgroundGradient(float finalAlpha, boolean animated) {
+ if (mBackground == null) return;
+ if (mBackgroundFadeInAnimation != null) {
+ mBackgroundFadeInAnimation.cancel();
+ mBackgroundFadeInAnimation = null;
+ }
+ if (mBackgroundFadeOutAnimation != null) {
+ mBackgroundFadeOutAnimation.cancel();
+ mBackgroundFadeOutAnimation = null;
+ }
+ float startAlpha = getBackgroundAlpha();
+ if (finalAlpha != startAlpha) {
+ if (animated) {
+ mBackgroundFadeOutAnimation =
+ LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
+ mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue());
+ }
+ });
+ mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
+ mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
+ mBackgroundFadeOutAnimation.start();
+ } else {
+ setBackgroundAlpha(finalAlpha);
+ }
+ }
+ }
+
+ public void setBackgroundAlpha(float alpha) {
+ if (alpha != mBackgroundAlpha) {
+ mBackgroundAlpha = alpha;
+ invalidate();
+ }
+ }
+
+ public float getBackgroundAlpha() {
+ return mBackgroundAlpha;
+ }
+
+ float backgroundAlphaInterpolator(float r) {
+ float pivotA = 0.1f;
+ float pivotB = 0.4f;
+ if (r < pivotA) {
+ return 0;
+ } else if (r > pivotB) {
+ return 1.0f;
+ } else {
+ return (r - pivotA)/(pivotB - pivotA);
+ }
+ }
+
+ private void updatePageAlphaValues(int screenCenter) {
+ boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
+ if (mWorkspaceFadeInAdjacentScreens &&
+ mState == State.NORMAL &&
+ !mIsSwitchingState &&
+ !isInOverscroll) {
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout child = (CellLayout) getChildAt(i);
+ if (child != null) {
+ float scrollProgress = getScrollProgress(screenCenter, child, i);
+ float alpha = 1 - Math.abs(scrollProgress);
+ child.getShortcutsAndWidgets().setAlpha(alpha);
+ if (!mIsDragOccuring) {
+ child.setBackgroundAlphaMultiplier(
+ backgroundAlphaInterpolator(Math.abs(scrollProgress)));
+ } else {
+ child.setBackgroundAlphaMultiplier(1f);
+ }
+ }
+ }
+ }
+ }
+
+ private void setChildrenBackgroundAlphaMultipliers(float a) {
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout child = (CellLayout) getChildAt(i);
+ child.setBackgroundAlphaMultiplier(a);
+ }
+ }
+
+ @Override
+ protected void screenScrolled(int screenCenter) {
+ final boolean isRtl = isLayoutRtl();
+ super.screenScrolled(screenCenter);
+
+ updatePageAlphaValues(screenCenter);
+ enableHwLayersOnVisiblePages();
+
+ if (mOverScrollX < 0 || mOverScrollX > mMaxScrollX) {
+ int index = 0;
+ float pivotX = 0f;
+ final float leftBiasedPivot = 0.25f;
+ final float rightBiasedPivot = 0.75f;
+ final int lowerIndex = 0;
+ final int upperIndex = getChildCount() - 1;
+ if (isRtl) {
+ index = mOverScrollX < 0 ? upperIndex : lowerIndex;
+ pivotX = (index == 0 ? leftBiasedPivot : rightBiasedPivot);
+ } else {
+ index = mOverScrollX < 0 ? lowerIndex : upperIndex;
+ pivotX = (index == 0 ? rightBiasedPivot : leftBiasedPivot);
+ }
+
+ CellLayout cl = (CellLayout) getChildAt(index);
+ float scrollProgress = getScrollProgress(screenCenter, cl, index);
+ final boolean isLeftPage = (isRtl ? index > 0 : index == 0);
+ cl.setOverScrollAmount(Math.abs(scrollProgress), isLeftPage);
+ float rotation = -WORKSPACE_OVERSCROLL_ROTATION * scrollProgress;
+ cl.setRotationY(rotation);
+ setFadeForOverScroll(Math.abs(scrollProgress));
+ if (!mOverscrollTransformsSet) {
+ mOverscrollTransformsSet = true;
+ cl.setCameraDistance(mDensity * mCameraDistance);
+ cl.setPivotX(cl.getMeasuredWidth() * pivotX);
+ cl.setPivotY(cl.getMeasuredHeight() * 0.5f);
+ cl.setOverscrollTransformsDirty(true);
+ }
+ } else {
+ if (mOverscrollFade != 0) {
+ setFadeForOverScroll(0);
+ }
+ if (mOverscrollTransformsSet) {
+ mOverscrollTransformsSet = false;
+ ((CellLayout) getChildAt(0)).resetOverscrollTransforms();
+ ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms();
+ }
+ }
+ }
+
+ @Override
+ protected void overScroll(float amount) {
+ acceleratedOverScroll(amount);
+ }
+
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mWindowToken = getWindowToken();
+ computeScroll();
+ mDragController.setWindowToken(mWindowToken);
+ }
+
+ protected void onDetachedFromWindow() {
+ mWindowToken = null;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+ mUpdateWallpaperOffsetImmediately = true;
+ }
+ super.onLayout(changed, left, top, right, bottom);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ updateWallpaperOffsets();
+
+ // Draw the background gradient if necessary
+ if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) {
+ int alpha = (int) (mBackgroundAlpha * 255);
+ mBackground.setAlpha(alpha);
+ mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(),
+ getMeasuredHeight());
+ mBackground.draw(canvas);
+ }
+
+ super.onDraw(canvas);
+
+ // Call back to LauncherModel to finish binding after the first draw
+ post(mBindPages);
+ }
+
+ boolean isDrawingBackgroundGradient() {
+ return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground);
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ if (!mLauncher.isAllAppsVisible()) {
+ final Folder openFolder = getOpenFolder();
+ if (openFolder != null) {
+ return openFolder.requestFocus(direction, previouslyFocusedRect);
+ } else {
+ return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getDescendantFocusability() {
+ if (isSmall()) {
+ return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
+ }
+ return super.getDescendantFocusability();
+ }
+
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if (!mLauncher.isAllAppsVisible()) {
+ final Folder openFolder = getOpenFolder();
+ if (openFolder != null) {
+ openFolder.addFocusables(views, direction);
+ } else {
+ super.addFocusables(views, direction, focusableMode);
+ }
+ }
+ }
+
+ public boolean isSmall() {
+ return mState == State.SMALL || mState == State.SPRING_LOADED;
+ }
+
+ void enableChildrenCache(int fromPage, int toPage) {
+ if (fromPage > toPage) {
+ final int temp = fromPage;
+ fromPage = toPage;
+ toPage = temp;
+ }
+
+ final int screenCount = getChildCount();
+
+ fromPage = Math.max(fromPage, 0);
+ toPage = Math.min(toPage, screenCount - 1);
+
+ for (int i = fromPage; i <= toPage; i++) {
+ final CellLayout layout = (CellLayout) getChildAt(i);
+ layout.setChildrenDrawnWithCacheEnabled(true);
+ layout.setChildrenDrawingCacheEnabled(true);
+ }
+ }
+
+ void clearChildrenCache() {
+ final int screenCount = getChildCount();
+ for (int i = 0; i < screenCount; i++) {
+ final CellLayout layout = (CellLayout) getChildAt(i);
+ layout.setChildrenDrawnWithCacheEnabled(false);
+ // In software mode, we don't want the items to continue to be drawn into bitmaps
+ if (!isHardwareAccelerated()) {
+ layout.setChildrenDrawingCacheEnabled(false);
+ }
+ }
+ }
+
+
+ private void updateChildrenLayersEnabled(boolean force) {
+ boolean small = mState == State.SMALL || mIsSwitchingState;
+ boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
+
+ if (enableChildrenLayers != mChildrenLayersEnabled) {
+ mChildrenLayersEnabled = enableChildrenLayers;
+ if (mChildrenLayersEnabled) {
+ enableHwLayersOnVisiblePages();
+ } else {
+ for (int i = 0; i < getPageCount(); i++) {
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ cl.disableHardwareLayers();
+ }
+ }
+ }
+ }
+
+ private void enableHwLayersOnVisiblePages() {
+ if (mChildrenLayersEnabled) {
+ final int screenCount = getChildCount();
+ getVisiblePages(mTempVisiblePagesRange);
+ int leftScreen = mTempVisiblePagesRange[0];
+ int rightScreen = mTempVisiblePagesRange[1];
+ if (leftScreen == rightScreen) {
+ // make sure we're caching at least two pages always
+ if (rightScreen < screenCount - 1) {
+ rightScreen++;
+ } else if (leftScreen > 0) {
+ leftScreen--;
+ }
+ }
+ for (int i = 0; i < screenCount; i++) {
+ final CellLayout layout = (CellLayout) getPageAt(i);
+ if (!(leftScreen <= i && i <= rightScreen && shouldDrawChild(layout))) {
+ layout.disableHardwareLayers();
+ }
+ }
+ for (int i = 0; i < screenCount; i++) {
+ final CellLayout layout = (CellLayout) getPageAt(i);
+ if (leftScreen <= i && i <= rightScreen && shouldDrawChild(layout)) {
+ layout.enableHardwareLayers();
+ }
+ }
+ }
+ }
+
+ public void buildPageHardwareLayers() {
+ // force layers to be enabled just for the call to buildLayer
+ updateChildrenLayersEnabled(true);
+ if (getWindowToken() != null) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ cl.buildHardwareLayer();
+ }
+ }
+ updateChildrenLayersEnabled(false);
+ }
+
+ protected void onWallpaperTap(MotionEvent ev) {
+ final int[] position = mTempCell;
+ getLocationOnScreen(position);
+
+ int pointerIndex = ev.getActionIndex();
+ position[0] += (int) ev.getX(pointerIndex);
+ position[1] += (int) ev.getY(pointerIndex);
+
+ mWallpaperManager.sendWallpaperCommand(getWindowToken(),
+ ev.getAction() == MotionEvent.ACTION_UP
+ ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
+ position[0], position[1], 0, null);
+ }
+
+ /*
+ * This interpolator emulates the rate at which the perceived scale of an object changes
+ * as its distance from a camera increases. When this interpolator is applied to a scale
+ * animation on a view, it evokes the sense that the object is shrinking due to moving away
+ * from the camera.
+ */
+ static class ZInterpolator implements TimeInterpolator {
+ private float focalLength;
+
+ public ZInterpolator(float foc) {
+ focalLength = foc;
+ }
+
+ public float getInterpolation(float input) {
+ return (1.0f - focalLength / (focalLength + input)) /
+ (1.0f - focalLength / (focalLength + 1.0f));
+ }
+ }
+
+ /*
+ * The exact reverse of ZInterpolator.
+ */
+ static class InverseZInterpolator implements TimeInterpolator {
+ private ZInterpolator zInterpolator;
+ public InverseZInterpolator(float foc) {
+ zInterpolator = new ZInterpolator(foc);
+ }
+ public float getInterpolation(float input) {
+ return 1 - zInterpolator.getInterpolation(1 - input);
+ }
+ }
+
+ /*
+ * ZInterpolator compounded with an ease-out.
+ */
+ static class ZoomOutInterpolator implements TimeInterpolator {
+ private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
+ private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
+
+ public float getInterpolation(float input) {
+ return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
+ }
+ }
+
+ /*
+ * InvereZInterpolator compounded with an ease-out.
+ */
+ static class ZoomInInterpolator implements TimeInterpolator {
+ private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
+ private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
+
+ public float getInterpolation(float input) {
+ return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
+ }
+ }
+
+ private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
+
+ /*
+ *
+ * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
+ * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
+ *
+ * These methods mark the appropriate pages as accepting drops (which alters their visual
+ * appearance).
+ *
+ */
+ public void onDragStartedWithItem(View v) {
+ final Canvas canvas = new Canvas();
+
+ // The outline is used to visualize where the item will land if dropped
+ mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
+ }
+
+ public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
+ final Canvas canvas = new Canvas();
+
+ int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
+
+ // The outline is used to visualize where the item will land if dropped
+ mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0],
+ size[1], clipAlpha);
+ }
+
+ public void exitWidgetResizeMode() {
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ dragLayer.clearAllResizeFrames();
+ }
+
+ private void initAnimationArrays() {
+ final int childCount = getChildCount();
+ if (mOldTranslationXs != null) return;
+ mOldTranslationXs = new float[childCount];
+ mOldTranslationYs = new float[childCount];
+ mOldScaleXs = new float[childCount];
+ mOldScaleYs = new float[childCount];
+ mOldBackgroundAlphas = new float[childCount];
+ mOldAlphas = new float[childCount];
+ mNewTranslationXs = new float[childCount];
+ mNewTranslationYs = new float[childCount];
+ mNewScaleXs = new float[childCount];
+ mNewScaleYs = new float[childCount];
+ mNewBackgroundAlphas = new float[childCount];
+ mNewAlphas = new float[childCount];
+ mNewRotationYs = new float[childCount];
+ }
+
+ Animator getChangeStateAnimation(final State state, boolean animated) {
+ return getChangeStateAnimation(state, animated, 0);
+ }
+
+ Animator getChangeStateAnimation(final State state, boolean animated, int delay) {
+ if (mState == state) {
+ return null;
+ }
+
+ // Initialize animation arrays for the first time if necessary
+ initAnimationArrays();
+
+ AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
+
+ // Stop any scrolling, move to the current page right away
+ setCurrentPage(getNextPage());
+
+ final State oldState = mState;
+ final boolean oldStateIsNormal = (oldState == State.NORMAL);
+ final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
+ final boolean oldStateIsSmall = (oldState == State.SMALL);
+ mState = state;
+ final boolean stateIsNormal = (state == State.NORMAL);
+ final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
+ final boolean stateIsSmall = (state == State.SMALL);
+ float finalScaleFactor = 1.0f;
+ float finalBackgroundAlpha = stateIsSpringLoaded ? 1.0f : 0f;
+ float translationX = 0;
+ float translationY = 0;
+ boolean zoomIn = true;
+
+ if (state != State.NORMAL) {
+ finalScaleFactor = mSpringLoadedShrinkFactor - (stateIsSmall ? 0.1f : 0);
+ setPageSpacing(mSpringLoadedPageSpacing);
+ if (oldStateIsNormal && stateIsSmall) {
+ zoomIn = false;
+ setLayoutScale(finalScaleFactor);
+ updateChildrenLayersEnabled(false);
+ } else {
+ finalBackgroundAlpha = 1.0f;
+ setLayoutScale(finalScaleFactor);
+ }
+ } else {
+ setPageSpacing(mOriginalPageSpacing);
+ setLayoutScale(1.0f);
+ }
+
+ final int duration = zoomIn ?
+ getResources().getInteger(R.integer.config_workspaceUnshrinkTime) :
+ getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
+ for (int i = 0; i < getChildCount(); i++) {
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ float finalAlpha = (!mWorkspaceFadeInAdjacentScreens || stateIsSpringLoaded ||
+ (i == mCurrentPage)) ? 1f : 0f;
+ float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
+ float initialAlpha = currentAlpha;
+
+ // Determine the pages alpha during the state transition
+ if ((oldStateIsSmall && stateIsNormal) ||
+ (oldStateIsNormal && stateIsSmall)) {
+ // To/from workspace - only show the current page unless the transition is not
+ // animated and the animation end callback below doesn't run;
+ // or, if we're in spring-loaded mode
+ if (i == mCurrentPage || !animated || oldStateIsSpringLoaded) {
+ finalAlpha = 1f;
+ } else {
+ initialAlpha = 0f;
+ finalAlpha = 0f;
+ }
+ }
+
+ mOldAlphas[i] = initialAlpha;
+ mNewAlphas[i] = finalAlpha;
+ if (animated) {
+ mOldTranslationXs[i] = cl.getTranslationX();
+ mOldTranslationYs[i] = cl.getTranslationY();
+ mOldScaleXs[i] = cl.getScaleX();
+ mOldScaleYs[i] = cl.getScaleY();
+ mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
+
+ mNewTranslationXs[i] = translationX;
+ mNewTranslationYs[i] = translationY;
+ mNewScaleXs[i] = finalScaleFactor;
+ mNewScaleYs[i] = finalScaleFactor;
+ mNewBackgroundAlphas[i] = finalBackgroundAlpha;
+ } else {
+ cl.setTranslationX(translationX);
+ cl.setTranslationY(translationY);
+ cl.setScaleX(finalScaleFactor);
+ cl.setScaleY(finalScaleFactor);
+ cl.setBackgroundAlpha(finalBackgroundAlpha);
+ cl.setShortcutAndWidgetAlpha(finalAlpha);
+ }
+ }
+
+ if (animated) {
+ for (int index = 0; index < getChildCount(); index++) {
+ final int i = index;
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
+ if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
+ cl.setTranslationX(mNewTranslationXs[i]);
+ cl.setTranslationY(mNewTranslationYs[i]);
+ cl.setScaleX(mNewScaleXs[i]);
+ cl.setScaleY(mNewScaleYs[i]);
+ cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
+ cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
+ cl.setRotationY(mNewRotationYs[i]);
+ } else {
+ LauncherViewPropertyAnimator a = new LauncherViewPropertyAnimator(cl);
+ a.translationX(mNewTranslationXs[i])
+ .translationY(mNewTranslationYs[i])
+ .scaleX(mNewScaleXs[i])
+ .scaleY(mNewScaleYs[i])
+ .setDuration(duration)
+ .setInterpolator(mZoomInInterpolator);
+ anim.play(a);
+
+ if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
+ LauncherViewPropertyAnimator alphaAnim =
+ new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
+ alphaAnim.alpha(mNewAlphas[i])
+ .setDuration(duration)
+ .setInterpolator(mZoomInInterpolator);
+ anim.play(alphaAnim);
+ }
+ if (mOldBackgroundAlphas[i] != 0 ||
+ mNewBackgroundAlphas[i] != 0) {
+ ValueAnimator bgAnim =
+ LauncherAnimUtils.ofFloat(cl, 0f, 1f).setDuration(duration);
+ bgAnim.setInterpolator(mZoomInInterpolator);
+ bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
+ public void onAnimationUpdate(float a, float b) {
+ cl.setBackgroundAlpha(
+ a * mOldBackgroundAlphas[i] +
+ b * mNewBackgroundAlphas[i]);
+ }
+ });
+ anim.play(bgAnim);
+ }
+ }
+ }
+ anim.setStartDelay(delay);
+ }
+
+ if (stateIsSpringLoaded) {
+ // Right now we're covered by Apps Customize
+ // Show the background gradient immediately, so the gradient will
+ // be showing once AppsCustomize disappears
+ animateBackgroundGradient(getResources().getInteger(
+ R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
+ } else {
+ // Fade the background gradient away
+ animateBackgroundGradient(0f, true);
+ }
+ return anim;
+ }
+
+ @Override
+ public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
+ mIsSwitchingState = true;
+ updateChildrenLayersEnabled(false);
+ cancelScrollingIndicatorAnimations();
+ }
+
+ @Override
+ public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+ }
+
+ @Override
+ public void onLauncherTransitionStep(Launcher l, float t) {
+ mTransitionProgress = t;
+ }
+
+ @Override
+ public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ mIsSwitchingState = false;
+ mWallpaperOffset.setOverrideHorizontalCatchupConstant(false);
+ updateChildrenLayersEnabled(false);
+ // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
+ // ensure that only the current page is visible during (and subsequently, after) the
+ // transition animation. If fade adjacent pages is disabled, then re-enable the page
+ // visibility after the transition animation.
+ if (!mWorkspaceFadeInAdjacentScreens) {
+ for (int i = 0; i < getChildCount(); i++) {
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ cl.setShortcutAndWidgetAlpha(1f);
+ }
+ }
+ }
+
+ @Override
+ public View getContent() {
+ return this;
+ }
+
+ /**
+ * Draw the View v into the given Canvas.
+ *
+ * @param v the view to draw
+ * @param destCanvas the canvas to draw on
+ * @param padding the horizontal and vertical padding to use when drawing
+ */
+ private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
+ final Rect clipRect = mTempRect;
+ v.getDrawingRect(clipRect);
+
+ boolean textVisible = false;
+
+ destCanvas.save();
+ if (v instanceof TextView && pruneToDrawable) {
+ Drawable d = ((TextView) v).getCompoundDrawables()[1];
+ clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
+ destCanvas.translate(padding / 2, padding / 2);
+ d.draw(destCanvas);
+ } else {
+ if (v instanceof FolderIcon) {
+ // For FolderIcons the text can bleed into the icon area, and so we need to
+ // hide the text completely (which can't be achieved by clipping).
+ if (((FolderIcon) v).getTextVisible()) {
+ ((FolderIcon) v).setTextVisible(false);
+ textVisible = true;
+ }
+ } else if (v instanceof BubbleTextView) {
+ final BubbleTextView tv = (BubbleTextView) v;
+ clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
+ tv.getLayout().getLineTop(0);
+ } else if (v instanceof TextView) {
+ final TextView tv = (TextView) v;
+ clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
+ tv.getLayout().getLineTop(0);
+ }
+ destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
+ destCanvas.clipRect(clipRect, Op.REPLACE);
+ v.draw(destCanvas);
+
+ // Restore text visibility of FolderIcon if necessary
+ if (textVisible) {
+ ((FolderIcon) v).setTextVisible(true);
+ }
+ }
+ destCanvas.restore();
+ }
+
+ /**
+ * Returns a new bitmap to show when the given View is being dragged around.
+ * Responsibility for the bitmap is transferred to the caller.
+ */
+ public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
+ Bitmap b;
+
+ if (v instanceof TextView) {
+ Drawable d = ((TextView) v).getCompoundDrawables()[1];
+ b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
+ d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
+ } else {
+ b = Bitmap.createBitmap(
+ v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
+ }
+
+ canvas.setBitmap(b);
+ drawDragView(v, canvas, padding, true);
+ canvas.setBitmap(null);
+
+ return b;
+ }
+
+ /**
+ * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+ * Responsibility for the bitmap is transferred to the caller.
+ */
+ private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
+ final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
+ final Bitmap b = Bitmap.createBitmap(
+ v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
+
+ canvas.setBitmap(b);
+ drawDragView(v, canvas, padding, true);
+ mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
+ canvas.setBitmap(null);
+ return b;
+ }
+
+ /**
+ * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+ * Responsibility for the bitmap is transferred to the caller.
+ */
+ private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
+ boolean clipAlpha) {
+ final int outlineColor = getResources().getColor(android.R.color.holo_blue_light);
+ final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ canvas.setBitmap(b);
+
+ Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
+ float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
+ (h - padding) / (float) orig.getHeight());
+ int scaledWidth = (int) (scaleFactor * orig.getWidth());
+ int scaledHeight = (int) (scaleFactor * orig.getHeight());
+ Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
+
+ // center the image
+ dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
+
+ canvas.drawBitmap(orig, src, dst, null);
+ mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
+ clipAlpha);
+ canvas.setBitmap(null);
+
+ return b;
+ }
+
+ void startDrag(CellLayout.CellInfo cellInfo) {
+ View child = cellInfo.cell;
+
+ // Make sure the drag was started by a long press as opposed to a long click.
+ if (!child.isInTouchMode()) {
+ return;
+ }
+
+ mDragInfo = cellInfo;
+ child.setVisibility(INVISIBLE);
+ CellLayout layout = (CellLayout) child.getParent().getParent();
+ layout.prepareChildForDrag(child);
+
+ child.clearFocus();
+ child.setPressed(false);
+
+ final Canvas canvas = new Canvas();
+
+ // The outline is used to visualize where the item will land if dropped
+ mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
+ beginDragShared(child, this);
+ }
+
+ public void beginDragShared(View child, DragSource source) {
+ Resources r = getResources();
+
+ // The drag bitmap follows the touch point around on the screen
+ final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
+
+ final int bmpWidth = b.getWidth();
+ final int bmpHeight = b.getHeight();
+
+ float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
+ int dragLayerX =
+ Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
+ int dragLayerY =
+ Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
+ - DRAG_BITMAP_PADDING / 2);
+
+ Point dragVisualizeOffset = null;
+ Rect dragRect = null;
+ if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
+ int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
+ int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
+ int top = child.getPaddingTop();
+ int left = (bmpWidth - iconSize) / 2;
+ int right = left + iconSize;
+ int bottom = top + iconSize;
+ dragLayerY += top;
+ // Note: The drag region is used to calculate drag layer offsets, but the
+ // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
+ dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2,
+ iconPaddingTop - DRAG_BITMAP_PADDING / 2);
+ dragRect = new Rect(left, top, right, bottom);
+ } else if (child instanceof FolderIcon) {
+ int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
+ dragRect = new Rect(0, 0, child.getWidth(), previewSize);
+ }
+
+ // Clear the pressed state if necessary
+ if (child instanceof BubbleTextView) {
+ BubbleTextView icon = (BubbleTextView) child;
+ icon.clearPressedOrFocusedBackground();
+ }
+
+ mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
+ DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+ b.recycle();
+
+ // Show the scrolling indicator when you pick up an item
+ showScrollingIndicator(false);
+ }
+
+ void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, int screen,
+ int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
+ View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
+
+ final int[] cellXY = new int[2];
+ target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
+ addInScreen(view, container, screen, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen, cellXY[0],
+ cellXY[1]);
+ }
+
+ public boolean transitionStateShouldAllowDrop() {
+ return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean acceptDrop(DragObject d) {
+ // If it's an external drop (e.g. from All Apps), check if it should be accepted
+ CellLayout dropTargetLayout = mDropToLayout;
+ if (d.dragSource != this) {
+ // Don't accept the drop if we're not over a screen at time of drop
+ if (dropTargetLayout == null) {
+ return false;
+ }
+ if (!transitionStateShouldAllowDrop()) return false;
+
+ mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
+ d.dragView, mDragViewVisualCenter);
+
+ // We want the point to be mapped to the dragTarget.
+ if (mLauncher.isHotseatLayout(dropTargetLayout)) {
+ mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
+ } else {
+ mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+ }
+
+ int spanX = 1;
+ int spanY = 1;
+ if (mDragInfo != null) {
+ final CellLayout.CellInfo dragCellInfo = mDragInfo;
+ spanX = dragCellInfo.spanX;
+ spanY = dragCellInfo.spanY;
+ } else {
+ final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
+ spanX = dragInfo.spanX;
+ spanY = dragInfo.spanY;
+ }
+
+ int minSpanX = spanX;
+ int minSpanY = spanY;
+ if (d.dragInfo instanceof PendingAddWidgetInfo) {
+ minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
+ minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
+ }
+
+ mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
+ mTargetCell);
+ float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
+ if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
+ mTargetCell, distance, true)) {
+ return true;
+ }
+ if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
+ mTargetCell, distance)) {
+ return true;
+ }
+
+ int[] resultSpan = new int[2];
+ mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
+ null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
+ boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
+
+ // Don't accept the drop if there's no room for the item
+ if (!foundCell) {
+ // Don't show the message if we are dropping on the AllApps button and the hotseat
+ // is full
+ boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
+ if (mTargetCell != null && isHotseat) {
+ Hotseat hotseat = mLauncher.getHotseat();
+ if (hotseat.isAllAppsButtonRank(
+ hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
+ return false;
+ }
+ }
+
+ mLauncher.showOutOfSpaceMessage(isHotseat);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
+ distance, boolean considerTimeout) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
+ View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+
+ if (dropOverView != null) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+ if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
+ return false;
+ }
+ }
+
+ boolean hasntMoved = false;
+ if (mDragInfo != null) {
+ hasntMoved = dropOverView == mDragInfo.cell;
+ }
+
+ if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
+ return false;
+ }
+
+ boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
+ boolean willBecomeShortcut =
+ (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+ info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
+
+ return (aboveShortcut && willBecomeShortcut);
+ }
+
+ boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
+ float distance) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
+ View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+
+ if (dropOverView != null) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+ if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
+ return false;
+ }
+ }
+
+ if (dropOverView instanceof FolderIcon) {
+ FolderIcon fi = (FolderIcon) dropOverView;
+ if (fi.acceptDrop(dragInfo)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
+ int[] targetCell, float distance, boolean external, DragView dragView,
+ Runnable postAnimationRunnable) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
+ View v = target.getChildAt(targetCell[0], targetCell[1]);
+
+ boolean hasntMoved = false;
+ if (mDragInfo != null) {
+ CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
+ hasntMoved = (mDragInfo.cellX == targetCell[0] &&
+ mDragInfo.cellY == targetCell[1]) && (cellParent == target);
+ }
+
+ if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
+ mCreateUserFolderOnDrop = false;
+ final int screen = (targetCell == null) ? mDragInfo.screen : indexOfChild(target);
+
+ boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
+ boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
+
+ if (aboveShortcut && willBecomeShortcut) {
+ ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
+ ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
+ // if the drag started here, we need to remove it from the workspace
+ if (!external) {
+ getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
+ }
+
+ Rect folderLocation = new Rect();
+ float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
+ target.removeView(v);
+
+ FolderIcon fi =
+ mLauncher.addFolder(target, container, screen, targetCell[0], targetCell[1]);
+ destInfo.cellX = -1;
+ destInfo.cellY = -1;
+ sourceInfo.cellX = -1;
+ sourceInfo.cellY = -1;
+
+ // If the dragView is null, we can't animate
+ boolean animate = dragView != null;
+ if (animate) {
+ fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
+ postAnimationRunnable);
+ } else {
+ fi.addItem(destInfo);
+ fi.addItem(sourceInfo);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
+ float distance, DragObject d, boolean external) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
+
+ View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+ if (!mAddToExistingFolderOnDrop) return false;
+ mAddToExistingFolderOnDrop = false;
+
+ if (dropOverView instanceof FolderIcon) {
+ FolderIcon fi = (FolderIcon) dropOverView;
+ if (fi.acceptDrop(d.dragInfo)) {
+ fi.onDrop(d);
+
+ // if the drag started here, we need to remove it from the workspace
+ if (!external) {
+ getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void onDrop(final DragObject d) {
+ mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
+ mDragViewVisualCenter);
+
+ CellLayout dropTargetLayout = mDropToLayout;
+
+ // We want the point to be mapped to the dragTarget.
+ if (dropTargetLayout != null) {
+ if (mLauncher.isHotseatLayout(dropTargetLayout)) {
+ mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
+ } else {
+ mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+ }
+ }
+
+ int snapScreen = -1;
+ boolean resizeOnDrop = false;
+ if (d.dragSource != this) {
+ final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1] };
+ onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
+ } else if (mDragInfo != null) {
+ final View cell = mDragInfo.cell;
+
+ Runnable resizeRunnable = null;
+ if (dropTargetLayout != null) {
+ // Move internally
+ boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
+ boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
+ long container = hasMovedIntoHotseat ?
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT :
+ LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ int screen = (mTargetCell[0] < 0) ?
+ mDragInfo.screen : indexOfChild(dropTargetLayout);
+ int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
+ int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
+ // First we find the cell nearest to point at which the item is
+ // dropped, without any consideration to whether there is an item there.
+
+ mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
+ mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
+ float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
+
+ // If the item being dropped is a shortcut and the nearest drop
+ // cell also contains a shortcut, then create a folder with the two shortcuts.
+ if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
+ dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
+ return;
+ }
+
+ if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
+ distance, d, false)) {
+ return;
+ }
+
+ // Aside from the special case where we're dropping a shortcut onto a shortcut,
+ // we need to find the nearest cell location that is vacant
+ ItemInfo item = (ItemInfo) d.dragInfo;
+ int minSpanX = item.spanX;
+ int minSpanY = item.spanY;
+ if (item.minSpanX > 0 && item.minSpanY > 0) {
+ minSpanX = item.minSpanX;
+ minSpanY = item.minSpanY;
+ }
+
+ int[] resultSpan = new int[2];
+ mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
+ mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
+
+ boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
+
+ // if the widget resizes on drop
+ if (foundCell && (cell instanceof AppWidgetHostView) &&
+ (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
+ resizeOnDrop = true;
+ item.spanX = resultSpan[0];
+ item.spanY = resultSpan[1];
+ AppWidgetHostView awhv = (AppWidgetHostView) cell;
+ AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
+ resultSpan[1]);
+ }
+
+ if (mCurrentPage != screen && !hasMovedIntoHotseat) {
+ snapScreen = screen;
+ snapToPage(screen);
+ }
+
+ if (foundCell) {
+ final ItemInfo info = (ItemInfo) cell.getTag();
+ if (hasMovedLayouts) {
+ // Reparent the view
+ getParentCellLayoutForView(cell).removeView(cell);
+ addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
+ info.spanX, info.spanY);
+ }
+
+ // update the item's position after drop
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+ lp.cellX = lp.tmpCellX = mTargetCell[0];
+ lp.cellY = lp.tmpCellY = mTargetCell[1];
+ lp.cellHSpan = item.spanX;
+ lp.cellVSpan = item.spanY;
+ lp.isLockedToGrid = true;
+ cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen,
+ mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
+
+ if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
+ cell instanceof LauncherAppWidgetHostView) {
+ final CellLayout cellLayout = dropTargetLayout;
+ // We post this call so that the widget has a chance to be placed
+ // in its final location
+
+ final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
+ AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
+ if (pinfo != null &&
+ pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
+ final Runnable addResizeFrame = new Runnable() {
+ public void run() {
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ dragLayer.addResizeFrame(info, hostView, cellLayout);
+ }
+ };
+ resizeRunnable = (new Runnable() {
+ public void run() {
+ if (!isPageMoving()) {
+ addResizeFrame.run();
+ } else {
+ mDelayedResizeRunnable = addResizeFrame;
+ }
+ }
+ });
+ }
+ }
+
+ LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,
+ lp.cellY);
+ } else {
+ // If we can't find a drop location, we return the item to its original position
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+ mTargetCell[0] = lp.cellX;
+ mTargetCell[1] = lp.cellY;
+ CellLayout layout = (CellLayout) cell.getParent().getParent();
+ layout.markCellsAsOccupiedForView(cell);
+ }
+ }
+
+ final CellLayout parent = (CellLayout) cell.getParent().getParent();
+ final Runnable finalResizeRunnable = resizeRunnable;
+ // Prepare it to be animated into its new position
+ // This must be called after the view has been re-parented
+ final Runnable onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mAnimatingViewIntoPlace = false;
+ updateChildrenLayersEnabled(false);
+ if (finalResizeRunnable != null) {
+ finalResizeRunnable.run();
+ }
+ }
+ };
+ mAnimatingViewIntoPlace = true;
+ if (d.dragView.hasDrawn()) {
+ final ItemInfo info = (ItemInfo) cell.getTag();
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
+ int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
+ ANIMATE_INTO_POSITION_AND_DISAPPEAR;
+ animateWidgetDrop(info, parent, d.dragView,
+ onCompleteRunnable, animationType, cell, false);
+ } else {
+ int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
+ mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
+ onCompleteRunnable, this);
+ }
+ } else {
+ d.deferDragViewCleanupPostAnimation = false;
+ cell.setVisibility(VISIBLE);
+ }
+ parent.onDropChild(cell);
+ }
+ }
+
+ public void setFinalScrollForPageChange(int screen) {
+ if (screen >= 0) {
+ mSavedScrollX = getScrollX();
+ CellLayout cl = (CellLayout) getChildAt(screen);
+ mSavedTranslationX = cl.getTranslationX();
+ mSavedRotationY = cl.getRotationY();
+ final int newX = getChildOffset(screen) - getRelativeChildOffset(screen);
+ setScrollX(newX);
+ cl.setTranslationX(0f);
+ cl.setRotationY(0f);
+ }
+ }
+
+ public void resetFinalScrollForPageChange(int screen) {
+ if (screen >= 0) {
+ CellLayout cl = (CellLayout) getChildAt(screen);
+ setScrollX(mSavedScrollX);
+ cl.setTranslationX(mSavedTranslationX);
+ cl.setRotationY(mSavedRotationY);
+ }
+ }
+
+ public void getViewLocationRelativeToSelf(View v, int[] location) {
+ getLocationInWindow(location);
+ int x = location[0];
+ int y = location[1];
+
+ v.getLocationInWindow(location);
+ int vX = location[0];
+ int vY = location[1];
+
+ location[0] = vX - x;
+ location[1] = vY - y;
+ }
+
+ public void onDragEnter(DragObject d) {
+ mDragEnforcer.onDragEnter();
+ mCreateUserFolderOnDrop = false;
+ mAddToExistingFolderOnDrop = false;
+
+ mDropToLayout = null;
+ CellLayout layout = getCurrentDropLayout();
+ setCurrentDropLayout(layout);
+ setCurrentDragOverlappingLayout(layout);
+
+ // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
+ // don't need to show the outlines
+ if (LauncherApplication.isScreenLarge()) {
+ showOutlines();
+ }
+ }
+
+ static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
+ Resources res = launcher.getResources();
+ Display display = launcher.getWindowManager().getDefaultDisplay();
+ Point smallestSize = new Point();
+ Point largestSize = new Point();
+ display.getCurrentSizeRange(smallestSize, largestSize);
+ if (orientation == CellLayout.LANDSCAPE) {
+ if (mLandscapeCellLayoutMetrics == null) {
+ int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land);
+ int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land);
+ int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land);
+ int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land);
+ int width = largestSize.x - paddingLeft - paddingRight;
+ int height = smallestSize.y - paddingTop - paddingBottom;
+ mLandscapeCellLayoutMetrics = new Rect();
+ CellLayout.getMetrics(mLandscapeCellLayoutMetrics, res,
+ width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(),
+ orientation);
+ }
+ return mLandscapeCellLayoutMetrics;
+ } else if (orientation == CellLayout.PORTRAIT) {
+ if (mPortraitCellLayoutMetrics == null) {
+ int paddingLeft = res.getDimensionPixelSize(R.dimen.workspace_left_padding_land);
+ int paddingRight = res.getDimensionPixelSize(R.dimen.workspace_right_padding_land);
+ int paddingTop = res.getDimensionPixelSize(R.dimen.workspace_top_padding_land);
+ int paddingBottom = res.getDimensionPixelSize(R.dimen.workspace_bottom_padding_land);
+ int width = smallestSize.x - paddingLeft - paddingRight;
+ int height = largestSize.y - paddingTop - paddingBottom;
+ mPortraitCellLayoutMetrics = new Rect();
+ CellLayout.getMetrics(mPortraitCellLayoutMetrics, res,
+ width, height, LauncherModel.getCellCountX(), LauncherModel.getCellCountY(),
+ orientation);
+ }
+ return mPortraitCellLayoutMetrics;
+ }
+ return null;
+ }
+
+ public void onDragExit(DragObject d) {
+ mDragEnforcer.onDragExit();
+
+ // Here we store the final page that will be dropped to, if the workspace in fact
+ // receives the drop
+ if (mInScrollArea) {
+ if (isPageMoving()) {
+ // If the user drops while the page is scrolling, we should use that page as the
+ // destination instead of the page that is being hovered over.
+ mDropToLayout = (CellLayout) getPageAt(getNextPage());
+ } else {
+ mDropToLayout = mDragOverlappingLayout;
+ }
+ } else {
+ mDropToLayout = mDragTargetLayout;
+ }
+
+ if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
+ mCreateUserFolderOnDrop = true;
+ } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
+ mAddToExistingFolderOnDrop = true;
+ }
+
+ // Reset the scroll area and previous drag target
+ onResetScrollArea();
+ setCurrentDropLayout(null);
+ setCurrentDragOverlappingLayout(null);
+
+ mSpringLoadedDragController.cancel();
+
+ if (!mIsPageMoving) {
+ hideOutlines();
+ }
+ }
+
+ void setCurrentDropLayout(CellLayout layout) {
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.revertTempState();
+ mDragTargetLayout.onDragExit();
+ }
+ mDragTargetLayout = layout;
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.onDragEnter();
+ }
+ cleanupReorder(true);
+ cleanupFolderCreation();
+ setCurrentDropOverCell(-1, -1);
+ }
+
+ void setCurrentDragOverlappingLayout(CellLayout layout) {
+ if (mDragOverlappingLayout != null) {
+ mDragOverlappingLayout.setIsDragOverlapping(false);
+ }
+ mDragOverlappingLayout = layout;
+ if (mDragOverlappingLayout != null) {
+ mDragOverlappingLayout.setIsDragOverlapping(true);
+ }
+ invalidate();
+ }
+
+ void setCurrentDropOverCell(int x, int y) {
+ if (x != mDragOverX || y != mDragOverY) {
+ mDragOverX = x;
+ mDragOverY = y;
+ setDragMode(DRAG_MODE_NONE);
+ }
+ }
+
+ void setDragMode(int dragMode) {
+ if (dragMode != mDragMode) {
+ if (dragMode == DRAG_MODE_NONE) {
+ cleanupAddToFolder();
+ // We don't want to cancel the re-order alarm every time the target cell changes
+ // as this feels to slow / unresponsive.
+ cleanupReorder(false);
+ cleanupFolderCreation();
+ } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
+ cleanupReorder(true);
+ cleanupFolderCreation();
+ } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
+ cleanupAddToFolder();
+ cleanupReorder(true);
+ } else if (dragMode == DRAG_MODE_REORDER) {
+ cleanupAddToFolder();
+ cleanupFolderCreation();
+ }
+ mDragMode = dragMode;
+ }
+ }
+
+ private void cleanupFolderCreation() {
+ if (mDragFolderRingAnimator != null) {
+ mDragFolderRingAnimator.animateToNaturalState();
+ }
+ mFolderCreationAlarm.cancelAlarm();
+ }
+
+ private void cleanupAddToFolder() {
+ if (mDragOverFolderIcon != null) {
+ mDragOverFolderIcon.onDragExit(null);
+ mDragOverFolderIcon = null;
+ }
+ }
+
+ private void cleanupReorder(boolean cancelAlarm) {
+ // Any pending reorders are canceled
+ if (cancelAlarm) {
+ mReorderAlarm.cancelAlarm();
+ }
+ mLastReorderX = -1;
+ mLastReorderY = -1;
+ }
+
+ public DropTarget getDropTargetDelegate(DragObject d) {
+ return null;
+ }
+
+ /*
+ *
+ * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
+ * coordinate space. The argument xy is modified with the return result.
+ *
+ */
+ void mapPointFromSelfToChild(View v, float[] xy) {
+ mapPointFromSelfToChild(v, xy, null);
+ }
+
+ /*
+ *
+ * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
+ * coordinate space. The argument xy is modified with the return result.
+ *
+ * if cachedInverseMatrix is not null, this method will just use that matrix instead of
+ * computing it itself; we use this to avoid redundant matrix inversions in
+ * findMatchingPageForDragOver
+ *
+ */
+ void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
+ if (cachedInverseMatrix == null) {
+ v.getMatrix().invert(mTempInverseMatrix);
+ cachedInverseMatrix = mTempInverseMatrix;
+ }
+ int scrollX = getScrollX();
+ if (mNextPage != INVALID_PAGE) {
+ scrollX = mScroller.getFinalX();
+ }
+ xy[0] = xy[0] + scrollX - v.getLeft();
+ xy[1] = xy[1] + getScrollY() - v.getTop();
+ cachedInverseMatrix.mapPoints(xy);
+ }
+
+
+ void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
+ hotseat.getLayout().getMatrix().invert(mTempInverseMatrix);
+ xy[0] = xy[0] - hotseat.getLeft() - hotseat.getLayout().getLeft();
+ xy[1] = xy[1] - hotseat.getTop() - hotseat.getLayout().getTop();
+ mTempInverseMatrix.mapPoints(xy);
+ }
+
+ /*
+ *
+ * Convert the 2D coordinate xy from this CellLayout's coordinate space to
+ * the parent View's coordinate space. The argument xy is modified with the return result.
+ *
+ */
+ void mapPointFromChildToSelf(View v, float[] xy) {
+ v.getMatrix().mapPoints(xy);
+ int scrollX = getScrollX();
+ if (mNextPage != INVALID_PAGE) {
+ scrollX = mScroller.getFinalX();
+ }
+ xy[0] -= (scrollX - v.getLeft());
+ xy[1] -= (getScrollY() - v.getTop());
+ }
+
+ static private float squaredDistance(float[] point1, float[] point2) {
+ float distanceX = point1[0] - point2[0];
+ float distanceY = point2[1] - point2[1];
+ return distanceX * distanceX + distanceY * distanceY;
+ }
+
+ /*
+ *
+ * Returns true if the passed CellLayout cl overlaps with dragView
+ *
+ */
+ boolean overlaps(CellLayout cl, DragView dragView,
+ int dragViewX, int dragViewY, Matrix cachedInverseMatrix) {
+ // Transform the coordinates of the item being dragged to the CellLayout's coordinates
+ final float[] draggedItemTopLeft = mTempDragCoordinates;
+ draggedItemTopLeft[0] = dragViewX;
+ draggedItemTopLeft[1] = dragViewY;
+ final float[] draggedItemBottomRight = mTempDragBottomRightCoordinates;
+ draggedItemBottomRight[0] = draggedItemTopLeft[0] + dragView.getDragRegionWidth();
+ draggedItemBottomRight[1] = draggedItemTopLeft[1] + dragView.getDragRegionHeight();
+
+ // Transform the dragged item's top left coordinates
+ // to the CellLayout's local coordinates
+ mapPointFromSelfToChild(cl, draggedItemTopLeft, cachedInverseMatrix);
+ float overlapRegionLeft = Math.max(0f, draggedItemTopLeft[0]);
+ float overlapRegionTop = Math.max(0f, draggedItemTopLeft[1]);
+
+ if (overlapRegionLeft <= cl.getWidth() && overlapRegionTop >= 0) {
+ // Transform the dragged item's bottom right coordinates
+ // to the CellLayout's local coordinates
+ mapPointFromSelfToChild(cl, draggedItemBottomRight, cachedInverseMatrix);
+ float overlapRegionRight = Math.min(cl.getWidth(), draggedItemBottomRight[0]);
+ float overlapRegionBottom = Math.min(cl.getHeight(), draggedItemBottomRight[1]);
+
+ if (overlapRegionRight >= 0 && overlapRegionBottom <= cl.getHeight()) {
+ float overlap = (overlapRegionRight - overlapRegionLeft) *
+ (overlapRegionBottom - overlapRegionTop);
+ if (overlap > 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /*
+ *
+ * This method returns the CellLayout that is currently being dragged to. In order to drag
+ * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
+ * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
+ *
+ * Return null if no CellLayout is currently being dragged over
+ *
+ */
+ private CellLayout findMatchingPageForDragOver(
+ DragView dragView, float originX, float originY, boolean exact) {
+ // We loop through all the screens (ie CellLayouts) and see which ones overlap
+ // with the item being dragged and then choose the one that's closest to the touch point
+ final int screenCount = getChildCount();
+ CellLayout bestMatchingScreen = null;
+ float smallestDistSoFar = Float.MAX_VALUE;
+
+ for (int i = 0; i < screenCount; i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+
+ final float[] touchXy = {originX, originY};
+ // Transform the touch coordinates to the CellLayout's local coordinates
+ // If the touch point is within the bounds of the cell layout, we can return immediately
+ cl.getMatrix().invert(mTempInverseMatrix);
+ mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
+
+ if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
+ touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
+ return cl;
+ }
+
+ if (!exact) {
+ // Get the center of the cell layout in screen coordinates
+ final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
+ cellLayoutCenter[0] = cl.getWidth()/2;
+ cellLayoutCenter[1] = cl.getHeight()/2;
+ mapPointFromChildToSelf(cl, cellLayoutCenter);
+
+ touchXy[0] = originX;
+ touchXy[1] = originY;
+
+ // Calculate the distance between the center of the CellLayout
+ // and the touch point
+ float dist = squaredDistance(touchXy, cellLayoutCenter);
+
+ if (dist < smallestDistSoFar) {
+ smallestDistSoFar = dist;
+ bestMatchingScreen = cl;
+ }
+ }
+ }
+ return bestMatchingScreen;
+ }
+
+ // This is used to compute the visual center of the dragView. This point is then
+ // used to visualize drop locations and determine where to drop an item. The idea is that
+ // the visual center represents the user's interpretation of where the item is, and hence
+ // is the appropriate point to use when determining drop location.
+ private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
+ DragView dragView, float[] recycle) {
+ float res[];
+ if (recycle == null) {
+ res = new float[2];
+ } else {
+ res = recycle;
+ }
+
+ // First off, the drag view has been shifted in a way that is not represented in the
+ // x and y values or the x/yOffsets. Here we account for that shift.
+ x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
+ y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
+
+ // These represent the visual top and left of drag view if a dragRect was provided.
+ // If a dragRect was not provided, then they correspond to the actual view left and
+ // top, as the dragRect is in that case taken to be the entire dragView.
+ // R.dimen.dragViewOffsetY.
+ int left = x - xOffset;
+ int top = y - yOffset;
+
+ // In order to find the visual center, we shift by half the dragRect
+ res[0] = left + dragView.getDragRegion().width() / 2;
+ res[1] = top + dragView.getDragRegion().height() / 2;
+
+ return res;
+ }
+
+ private boolean isDragWidget(DragObject d) {
+ return (d.dragInfo instanceof LauncherAppWidgetInfo ||
+ d.dragInfo instanceof PendingAddWidgetInfo);
+ }
+ private boolean isExternalDragWidget(DragObject d) {
+ return d.dragSource != this && isDragWidget(d);
+ }
+
+ public void onDragOver(DragObject d) {
+ // Skip drag over events while we are dragging over side pages
+ if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
+
+ Rect r = new Rect();
+ CellLayout layout = null;
+ ItemInfo item = (ItemInfo) d.dragInfo;
+
+ // Ensure that we have proper spans for the item that we are dropping
+ if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
+ mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
+ d.dragView, mDragViewVisualCenter);
+
+ final View child = (mDragInfo == null) ? null : mDragInfo.cell;
+ // Identify whether we have dragged over a side page
+ if (isSmall()) {
+ if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
+ mLauncher.getHotseat().getHitRect(r);
+ if (r.contains(d.x, d.y)) {
+ layout = mLauncher.getHotseat().getLayout();
+ }
+ }
+ if (layout == null) {
+ layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
+ }
+ if (layout != mDragTargetLayout) {
+
+ setCurrentDropLayout(layout);
+ setCurrentDragOverlappingLayout(layout);
+
+ boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
+ if (isInSpringLoadedMode) {
+ if (mLauncher.isHotseatLayout(layout)) {
+ mSpringLoadedDragController.cancel();
+ } else {
+ mSpringLoadedDragController.setAlarm(mDragTargetLayout);
+ }
+ }
+ }
+ } else {
+ // Test to see if we are over the hotseat otherwise just use the current page
+ if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
+ mLauncher.getHotseat().getHitRect(r);
+ if (r.contains(d.x, d.y)) {
+ layout = mLauncher.getHotseat().getLayout();
+ }
+ }
+ if (layout == null) {
+ layout = getCurrentDropLayout();
+ }
+ if (layout != mDragTargetLayout) {
+ setCurrentDropLayout(layout);
+ setCurrentDragOverlappingLayout(layout);
+ }
+ }
+
+ // Handle the drag over
+ if (mDragTargetLayout != null) {
+ // We want the point to be mapped to the dragTarget.
+ if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
+ mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
+ } else {
+ mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
+ }
+
+ ItemInfo info = (ItemInfo) d.dragInfo;
+
+ mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], item.spanX, item.spanY,
+ mDragTargetLayout, mTargetCell);
+
+ setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
+
+ float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
+ mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
+
+ final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
+ mTargetCell[1]);
+
+ manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
+ targetCellDistance, dragOverView);
+
+ int minSpanX = item.spanX;
+ int minSpanY = item.spanY;
+ if (item.minSpanX > 0 && item.minSpanY > 0) {
+ minSpanX = item.minSpanX;
+ minSpanY = item.minSpanY;
+ }
+
+ boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
+ mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
+ item.spanY, child, mTargetCell);
+
+ if (!nearestDropOccupied) {
+ mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
+ (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
+ mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
+ d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
+ } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
+ && !mReorderAlarm.alarmPending() && (mLastReorderX != mTargetCell[0] ||
+ mLastReorderY != mTargetCell[1])) {
+
+ // Otherwise, if we aren't adding to or creating a folder and there's no pending
+ // reorder, then we schedule a reorder
+ ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
+ minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
+ mReorderAlarm.setOnAlarmListener(listener);
+ mReorderAlarm.setAlarm(REORDER_TIMEOUT);
+ }
+
+ if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
+ !nearestDropOccupied) {
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.revertTempState();
+ }
+ }
+ }
+ }
+
+ private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
+ int[] targetCell, float distance, View dragOverView) {
+ boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
+ false);
+
+ if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
+ !mFolderCreationAlarm.alarmPending()) {
+ mFolderCreationAlarm.setOnAlarmListener(new
+ FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
+ mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
+ return;
+ }
+
+ boolean willAddToFolder =
+ willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
+
+ if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
+ mDragOverFolderIcon = ((FolderIcon) dragOverView);
+ mDragOverFolderIcon.onDragEnter(info);
+ if (targetLayout != null) {
+ targetLayout.clearDragOutlines();
+ }
+ setDragMode(DRAG_MODE_ADD_TO_FOLDER);
+ return;
+ }
+
+ if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
+ setDragMode(DRAG_MODE_NONE);
+ }
+ if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
+ setDragMode(DRAG_MODE_NONE);
+ }
+
+ return;
+ }
+
+ class FolderCreationAlarmListener implements OnAlarmListener {
+ CellLayout layout;
+ int cellX;
+ int cellY;
+
+ public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
+ this.layout = layout;
+ this.cellX = cellX;
+ this.cellY = cellY;
+ }
+
+ public void onAlarm(Alarm alarm) {
+ if (mDragFolderRingAnimator == null) {
+ mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
+ }
+ mDragFolderRingAnimator.setCell(cellX, cellY);
+ mDragFolderRingAnimator.setCellLayout(layout);
+ mDragFolderRingAnimator.animateToAcceptState();
+ layout.showFolderAccept(mDragFolderRingAnimator);
+ layout.clearDragOutlines();
+ setDragMode(DRAG_MODE_CREATE_FOLDER);
+ }
+ }
+
+ class ReorderAlarmListener implements OnAlarmListener {
+ float[] dragViewCenter;
+ int minSpanX, minSpanY, spanX, spanY;
+ DragView dragView;
+ View child;
+
+ public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
+ int spanY, DragView dragView, View child) {
+ this.dragViewCenter = dragViewCenter;
+ this.minSpanX = minSpanX;
+ this.minSpanY = minSpanY;
+ this.spanX = spanX;
+ this.spanY = spanY;
+ this.child = child;
+ this.dragView = dragView;
+ }
+
+ public void onAlarm(Alarm alarm) {
+ int[] resultSpan = new int[2];
+ mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], spanX, spanY, mDragTargetLayout, mTargetCell);
+ mLastReorderX = mTargetCell[0];
+ mLastReorderY = mTargetCell[1];
+
+ mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
+ child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
+
+ if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
+ mDragTargetLayout.revertTempState();
+ } else {
+ setDragMode(DRAG_MODE_REORDER);
+ }
+
+ boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
+ mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
+ (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
+ mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
+ dragView.getDragVisualizeOffset(), dragView.getDragRegion());
+ }
+ }
+
+ @Override
+ public void getHitRect(Rect outRect) {
+ // We want the workspace to have the whole area of the display (it will find the correct
+ // cell layout to drop to in the existing drag/drop logic.
+ outRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
+ }
+
+ /**
+ * Add the item specified by dragInfo to the given layout.
+ * @return true if successful
+ */
+ public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
+ if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
+ onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
+ return true;
+ }
+ mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
+ return false;
+ }
+
+ private void onDropExternal(int[] touchXY, Object dragInfo,
+ CellLayout cellLayout, boolean insertAtFirst) {
+ onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
+ }
+
+ /**
+ * Drop an item that didn't originate on one of the workspace screens.
+ * It may have come from Launcher (e.g. from all apps or customize), or it may have
+ * come from another app altogether.
+ *
+ * NOTE: This can also be called when we are outside of a drag event, when we want
+ * to add an item to one of the workspace screens.
+ */
+ private void onDropExternal(final int[] touchXY, final Object dragInfo,
+ final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
+ final Runnable exitSpringLoadedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
+ }
+ };
+
+ ItemInfo info = (ItemInfo) dragInfo;
+ int spanX = info.spanX;
+ int spanY = info.spanY;
+ if (mDragInfo != null) {
+ spanX = mDragInfo.spanX;
+ spanY = mDragInfo.spanY;
+ }
+
+ final long container = mLauncher.isHotseatLayout(cellLayout) ?
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT :
+ LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ final int screen = indexOfChild(cellLayout);
+ if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage
+ && mState != State.SPRING_LOADED) {
+ snapToPage(screen);
+ }
+
+ if (info instanceof PendingAddItemInfo) {
+ final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
+
+ boolean findNearestVacantCell = true;
+ if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+ mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
+ cellLayout, mTargetCell);
+ float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
+ if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
+ distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
+ cellLayout, mTargetCell, distance)) {
+ findNearestVacantCell = false;
+ }
+ }
+
+ final ItemInfo item = (ItemInfo) d.dragInfo;
+ boolean updateWidgetSize = false;
+ if (findNearestVacantCell) {
+ int minSpanX = item.spanX;
+ int minSpanY = item.spanY;
+ if (item.minSpanX > 0 && item.minSpanY > 0) {
+ minSpanX = item.minSpanX;
+ minSpanY = item.minSpanY;
+ }
+ int[] resultSpan = new int[2];
+ mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
+ null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
+
+ if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
+ updateWidgetSize = true;
+ }
+ item.spanX = resultSpan[0];
+ item.spanY = resultSpan[1];
+ }
+
+ Runnable onAnimationCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // When dragging and dropping from customization tray, we deal with creating
+ // widgets/shortcuts/folders in a slightly different way
+ switch (pendingInfo.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ int span[] = new int[2];
+ span[0] = item.spanX;
+ span[1] = item.spanY;
+ mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
+ container, screen, mTargetCell, span, null);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ mLauncher.processShortcutFromDrop(pendingInfo.componentName,
+ container, screen, mTargetCell, null);
+ break;
+ default:
+ throw new IllegalStateException("Unknown item type: " +
+ pendingInfo.itemType);
+ }
+ }
+ };
+ View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+ ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
+
+ if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
+ AppWidgetHostView awhv = (AppWidgetHostView) finalView;
+ AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
+ item.spanY);
+ }
+
+ int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
+ if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
+ ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
+ animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
+ }
+ animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
+ animationStyle, finalView, true);
+ } else {
+ // This is for other drag/drop cases, like dragging from All Apps
+ View view = null;
+
+ switch (info.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ if (info.container == NO_ID && info instanceof ApplicationInfo) {
+ // Came from all apps -- make a copy
+ info = new ShortcutInfo((ApplicationInfo) info);
+ }
+ view = mLauncher.createShortcut(R.layout.application, cellLayout,
+ (ShortcutInfo) info);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
+ (FolderInfo) info, mIconCache);
+ break;
+ default:
+ throw new IllegalStateException("Unknown item type: " + info.itemType);
+ }
+
+ // First we find the cell nearest to point at which the item is
+ // dropped, without any consideration to whether there is an item there.
+ if (touchXY != null) {
+ mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
+ cellLayout, mTargetCell);
+ float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
+ d.postAnimationRunnable = exitSpringLoadedRunnable;
+ if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
+ true, d.dragView, d.postAnimationRunnable)) {
+ return;
+ }
+ if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
+ true)) {
+ return;
+ }
+ }
+
+ if (touchXY != null) {
+ // when dragging and dropping, just find the closest free spot
+ mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
+ null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
+ } else {
+ cellLayout.findCellForSpan(mTargetCell, 1, 1);
+ }
+ addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX,
+ info.spanY, insertAtFirst);
+ cellLayout.onDropChild(view);
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+ cellLayout.getShortcutsAndWidgets().measureChild(view);
+
+
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen,
+ lp.cellX, lp.cellY);
+
+ if (d.dragView != null) {
+ // We wrap the animation call in the temporary set and reset of the current
+ // cellLayout to its final transform -- this means we animate the drag view to
+ // the correct final location.
+ setFinalTransitionTransform(cellLayout);
+ mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
+ exitSpringLoadedRunnable);
+ resetTransitionTransform(cellLayout);
+ }
+ }
+ }
+
+ public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
+ int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
+ widgetInfo.spanY, widgetInfo, false);
+ int visibility = layout.getVisibility();
+ layout.setVisibility(VISIBLE);
+
+ int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
+ int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
+ Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+
+ layout.measure(width, height);
+ layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
+ layout.draw(c);
+ c.setBitmap(null);
+ layout.setVisibility(visibility);
+ return b;
+ }
+
+ private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
+ DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
+ boolean external, boolean scale) {
+ // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
+ // location and size on the home screen.
+ int spanX = info.spanX;
+ int spanY = info.spanY;
+
+ Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
+ loc[0] = r.left;
+ loc[1] = r.top;
+
+ setFinalTransitionTransform(layout);
+ float cellLayoutScale =
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc);
+ resetTransitionTransform(layout);
+
+ float dragViewScaleX;
+ float dragViewScaleY;
+ if (scale) {
+ dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
+ dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
+ } else {
+ dragViewScaleX = 1f;
+ dragViewScaleY = 1f;
+ }
+
+ // The animation will scale the dragView about its center, so we need to center about
+ // the final location.
+ loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
+ loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
+
+ scaleXY[0] = dragViewScaleX * cellLayoutScale;
+ scaleXY[1] = dragViewScaleY * cellLayoutScale;
+ }
+
+ public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
+ final Runnable onCompleteRunnable, int animationType, final View finalView,
+ boolean external) {
+ Rect from = new Rect();
+ mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
+
+ int[] finalPos = new int[2];
+ float scaleXY[] = new float[2];
+ boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
+ getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
+ external, scalePreview);
+
+ Resources res = mLauncher.getResources();
+ int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
+
+ // In the case where we've prebound the widget, we remove it from the DragLayer
+ if (finalView instanceof AppWidgetHostView && external) {
+ Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
+ mLauncher.getDragLayer().removeView(finalView);
+ }
+ if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
+ Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
+ dragView.setCrossFadeBitmap(crossFadeBitmap);
+ dragView.crossFade((int) (duration * 0.8f));
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
+ scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
+ }
+
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
+ mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
+ DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
+ } else {
+ int endStyle;
+ if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
+ endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
+ } else {
+ endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
+ }
+
+ Runnable onComplete = new Runnable() {
+ @Override
+ public void run() {
+ if (finalView != null) {
+ finalView.setVisibility(VISIBLE);
+ }
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ }
+ };
+ dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
+ finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
+ duration, this);
+ }
+ }
+
+ public void setFinalTransitionTransform(CellLayout layout) {
+ if (isSwitchingState()) {
+ int index = indexOfChild(layout);
+ mCurrentScaleX = layout.getScaleX();
+ mCurrentScaleY = layout.getScaleY();
+ mCurrentTranslationX = layout.getTranslationX();
+ mCurrentTranslationY = layout.getTranslationY();
+ mCurrentRotationY = layout.getRotationY();
+ layout.setScaleX(mNewScaleXs[index]);
+ layout.setScaleY(mNewScaleYs[index]);
+ layout.setTranslationX(mNewTranslationXs[index]);
+ layout.setTranslationY(mNewTranslationYs[index]);
+ layout.setRotationY(mNewRotationYs[index]);
+ }
+ }
+ public void resetTransitionTransform(CellLayout layout) {
+ if (isSwitchingState()) {
+ mCurrentScaleX = layout.getScaleX();
+ mCurrentScaleY = layout.getScaleY();
+ mCurrentTranslationX = layout.getTranslationX();
+ mCurrentTranslationY = layout.getTranslationY();
+ mCurrentRotationY = layout.getRotationY();
+ layout.setScaleX(mCurrentScaleX);
+ layout.setScaleY(mCurrentScaleY);
+ layout.setTranslationX(mCurrentTranslationX);
+ layout.setTranslationY(mCurrentTranslationY);
+ layout.setRotationY(mCurrentRotationY);
+ }
+ }
+
+ /**
+ * Return the current {@link CellLayout}, correctly picking the destination
+ * screen while a scroll is in progress.
+ */
+ public CellLayout getCurrentDropLayout() {
+ return (CellLayout) getChildAt(getNextPage());
+ }
+
+ /**
+ * Return the current CellInfo describing our current drag; this method exists
+ * so that Launcher can sync this object with the correct info when the activity is created/
+ * destroyed
+ *
+ */
+ public CellLayout.CellInfo getDragInfo() {
+ return mDragInfo;
+ }
+
+ /**
+ * Calculate the nearest cell where the given object would be dropped.
+ *
+ * pixelX and pixelY should be in the coordinate system of layout
+ */
+ private int[] findNearestArea(int pixelX, int pixelY,
+ int spanX, int spanY, CellLayout layout, int[] recycle) {
+ return layout.findNearestArea(
+ pixelX, pixelY, spanX, spanY, recycle);
+ }
+
+ void setup(DragController dragController) {
+ mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
+ mDragController = dragController;
+
+ // hardware layers on children are enabled on startup, but should be disabled until
+ // needed
+ updateChildrenLayersEnabled(false);
+ setWallpaperDimension();
+ }
+
+ /**
+ * Called at the end of a drag which originated on the workspace.
+ */
+ public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
+ boolean success) {
+ if (success) {
+ if (target != this) {
+ if (mDragInfo != null) {
+ getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
+ if (mDragInfo.cell instanceof DropTarget) {
+ mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
+ }
+ }
+ }
+ } else if (mDragInfo != null) {
+ CellLayout cellLayout;
+ if (mLauncher.isHotseatLayout(target)) {
+ cellLayout = mLauncher.getHotseat().getLayout();
+ } else {
+ cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
+ }
+ cellLayout.onDropChild(mDragInfo.cell);
+ }
+ if (d.cancelled && mDragInfo.cell != null) {
+ mDragInfo.cell.setVisibility(VISIBLE);
+ }
+ mDragOutline = null;
+ mDragInfo = null;
+
+ // Hide the scrolling indicator after you pick up an item
+ hideScrollingIndicator(false);
+ }
+
+ void updateItemLocationsInDatabase(CellLayout cl) {
+ int count = cl.getShortcutsAndWidgets().getChildCount();
+
+ int screen = indexOfChild(cl);
+ int container = Favorites.CONTAINER_DESKTOP;
+
+ if (mLauncher.isHotseatLayout(cl)) {
+ screen = -1;
+ container = Favorites.CONTAINER_HOTSEAT;
+ }
+
+ for (int i = 0; i < count; i++) {
+ View v = cl.getShortcutsAndWidgets().getChildAt(i);
+ ItemInfo info = (ItemInfo) v.getTag();
+ // Null check required as the AllApps button doesn't have an item info
+ if (info != null && info.requiresDbUpdate) {
+ info.requiresDbUpdate = false;
+ LauncherModel.modifyItemInDatabase(mLauncher, info, container, screen, info.cellX,
+ info.cellY, info.spanX, info.spanY);
+ }
+ }
+ }
+
+ @Override
+ public boolean supportsFlingToDelete() {
+ return true;
+ }
+
+ @Override
+ public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+ // Do nothing
+ }
+
+ @Override
+ public void onFlingToDeleteCompleted() {
+ // Do nothing
+ }
+
+ public boolean isDropEnabled() {
+ return true;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ super.onRestoreInstanceState(state);
+ Launcher.setScreen(mCurrentPage);
+ }
+
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ // We don't dispatch restoreInstanceState to our children using this code path.
+ // Some pages will be restored immediately as their items are bound immediately, and
+ // others we will need to wait until after their items are bound.
+ mSavedStates = container;
+ }
+
+ public void restoreInstanceStateForChild(int child) {
+ if (mSavedStates != null) {
+ mRestoredPages.add(child);
+ CellLayout cl = (CellLayout) getChildAt(child);
+ cl.restoreInstanceState(mSavedStates);
+ }
+ }
+
+ public void restoreInstanceStateForRemainingPages() {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ if (!mRestoredPages.contains(i)) {
+ restoreInstanceStateForChild(i);
+ }
+ }
+ mRestoredPages.clear();
+ }
+
+ @Override
+ public void scrollLeft() {
+ if (!isSmall() && !mIsSwitchingState) {
+ super.scrollLeft();
+ }
+ Folder openFolder = getOpenFolder();
+ if (openFolder != null) {
+ openFolder.completeDragExit();
+ }
+ }
+
+ @Override
+ public void scrollRight() {
+ if (!isSmall() && !mIsSwitchingState) {
+ super.scrollRight();
+ }
+ Folder openFolder = getOpenFolder();
+ if (openFolder != null) {
+ openFolder.completeDragExit();
+ }
+ }
+
+ @Override
+ public boolean onEnterScrollArea(int x, int y, int direction) {
+ // Ignore the scroll area if we are dragging over the hot seat
+ boolean isPortrait = !LauncherApplication.isScreenLandscape(getContext());
+ if (mLauncher.getHotseat() != null && isPortrait) {
+ Rect r = new Rect();
+ mLauncher.getHotseat().getHitRect(r);
+ if (r.contains(x, y)) {
+ return false;
+ }
+ }
+
+ boolean result = false;
+ if (!isSmall() && !mIsSwitchingState) {
+ mInScrollArea = true;
+
+ final int page = getNextPage() +
+ (direction == DragController.SCROLL_LEFT ? -1 : 1);
+
+ // We always want to exit the current layout to ensure parity of enter / exit
+ setCurrentDropLayout(null);
+
+ if (0 <= page && page < getChildCount()) {
+ CellLayout layout = (CellLayout) getChildAt(page);
+ setCurrentDragOverlappingLayout(layout);
+
+ // Workspace is responsible for drawing the edge glow on adjacent pages,
+ // so we need to redraw the workspace when this may have changed.
+ invalidate();
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean onExitScrollArea() {
+ boolean result = false;
+ if (mInScrollArea) {
+ invalidate();
+ CellLayout layout = getCurrentDropLayout();
+ setCurrentDropLayout(layout);
+ setCurrentDragOverlappingLayout(layout);
+
+ result = true;
+ mInScrollArea = false;
+ }
+ return result;
+ }
+
+ private void onResetScrollArea() {
+ setCurrentDragOverlappingLayout(null);
+ mInScrollArea = false;
+ }
+
+ /**
+ * Returns a specific CellLayout
+ */
+ CellLayout getParentCellLayoutForView(View v) {
+ ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
+ for (CellLayout layout : layouts) {
+ if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
+ return layout;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a list of all the CellLayouts in the workspace.
+ */
+ ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
+ ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
+ int screenCount = getChildCount();
+ for (int screen = 0; screen < screenCount; screen++) {
+ layouts.add(((CellLayout) getChildAt(screen)));
+ }
+ if (mLauncher.getHotseat() != null) {
+ layouts.add(mLauncher.getHotseat().getLayout());
+ }
+ return layouts;
+ }
+
+ /**
+ * We should only use this to search for specific children. Do not use this method to modify
+ * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
+ * the hotseat and workspace pages
+ */
+ ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ new ArrayList<ShortcutAndWidgetContainer>();
+ int screenCount = getChildCount();
+ for (int screen = 0; screen < screenCount; screen++) {
+ childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
+ }
+ if (mLauncher.getHotseat() != null) {
+ childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
+ }
+ return childrenLayouts;
+ }
+
+ public Folder getFolderForTag(Object tag) {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+ int count = layout.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = layout.getChildAt(i);
+ if (child instanceof Folder) {
+ Folder f = (Folder) child;
+ if (f.getInfo() == tag && f.getInfo().opened) {
+ return f;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public View getViewForTag(Object tag) {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+ int count = layout.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = layout.getChildAt(i);
+ if (child.getTag() == tag) {
+ return child;
+ }
+ }
+ }
+ return null;
+ }
+
+ void clearDropTargets() {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+ int childCount = layout.getChildCount();
+ for (int j = 0; j < childCount; j++) {
+ View v = layout.getChildAt(j);
+ if (v instanceof DropTarget) {
+ mDragController.removeDropTarget((DropTarget) v);
+ }
+ }
+ }
+ }
+
+ // Removes ALL items that match a given package name, this is usually called when a package
+ // has been removed and we want to remove all components (widgets, shortcuts, apps) that
+ // belong to that package.
+ void removeItemsByPackageName(final ArrayList<String> packages) {
+ HashSet<String> packageNames = new HashSet<String>();
+ packageNames.addAll(packages);
+
+ // Just create a hash table of all the specific components that this will affect
+ HashSet<ComponentName> cns = new HashSet<ComponentName>();
+ ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
+ for (CellLayout layoutParent : cellLayouts) {
+ ViewGroup layout = layoutParent.getShortcutsAndWidgets();
+ int childCount = layout.getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ View view = layout.getChildAt(i);
+ Object tag = view.getTag();
+
+ if (tag instanceof ShortcutInfo) {
+ ShortcutInfo info = (ShortcutInfo) tag;
+ ComponentName cn = info.intent.getComponent();
+ if ((cn != null) && packageNames.contains(cn.getPackageName())) {
+ cns.add(cn);
+ }
+ } else if (tag instanceof FolderInfo) {
+ FolderInfo info = (FolderInfo) tag;
+ for (ShortcutInfo s : info.contents) {
+ ComponentName cn = s.intent.getComponent();
+ if ((cn != null) && packageNames.contains(cn.getPackageName())) {
+ cns.add(cn);
+ }
+ }
+ } else if (tag instanceof LauncherAppWidgetInfo) {
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
+ ComponentName cn = info.providerName;
+ if ((cn != null) && packageNames.contains(cn.getPackageName())) {
+ cns.add(cn);
+ }
+ }
+ }
+ }
+
+ // Remove all the things
+ removeItemsByComponentName(cns);
+ }
+
+ // Removes items that match the application info specified, when applications are removed
+ // as a part of an update, this is called to ensure that other widgets and application
+ // shortcuts are not removed.
+ void removeItemsByApplicationInfo(final ArrayList<ApplicationInfo> appInfos) {
+ // Just create a hash table of all the specific components that this will affect
+ HashSet<ComponentName> cns = new HashSet<ComponentName>();
+ for (ApplicationInfo info : appInfos) {
+ cns.add(info.componentName);
+ }
+
+ // Remove all the things
+ removeItemsByComponentName(cns);
+ }
+
+ void removeItemsByComponentName(final HashSet<ComponentName> componentNames) {
+ ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
+ for (final CellLayout layoutParent: cellLayouts) {
+ final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
+
+ // Avoid ANRs by treating each screen separately
+ post(new Runnable() {
+ public void run() {
+ final ArrayList<View> childrenToRemove = new ArrayList<View>();
+ childrenToRemove.clear();
+
+ int childCount = layout.getChildCount();
+ for (int j = 0; j < childCount; j++) {
+ final View view = layout.getChildAt(j);
+ Object tag = view.getTag();
+
+ if (tag instanceof ShortcutInfo) {
+ final ShortcutInfo info = (ShortcutInfo) tag;
+ final Intent intent = info.intent;
+ final ComponentName name = intent.getComponent();
+
+ if (name != null) {
+ if (componentNames.contains(name)) {
+ LauncherModel.deleteItemFromDatabase(mLauncher, info);
+ childrenToRemove.add(view);
+ }
+ }
+ } else if (tag instanceof FolderInfo) {
+ final FolderInfo info = (FolderInfo) tag;
+ final ArrayList<ShortcutInfo> contents = info.contents;
+ final int contentsCount = contents.size();
+ final ArrayList<ShortcutInfo> appsToRemoveFromFolder =
+ new ArrayList<ShortcutInfo>();
+
+ for (int k = 0; k < contentsCount; k++) {
+ final ShortcutInfo appInfo = contents.get(k);
+ final Intent intent = appInfo.intent;
+ final ComponentName name = intent.getComponent();
+
+ if (name != null) {
+ if (componentNames.contains(name)) {
+ appsToRemoveFromFolder.add(appInfo);
+ }
+ }
+ }
+ for (ShortcutInfo item: appsToRemoveFromFolder) {
+ info.remove(item);
+ LauncherModel.deleteItemFromDatabase(mLauncher, item);
+ }
+ } else if (tag instanceof LauncherAppWidgetInfo) {
+ final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) tag;
+ final ComponentName provider = info.providerName;
+ if (provider != null) {
+ if (componentNames.contains(provider)) {
+ LauncherModel.deleteItemFromDatabase(mLauncher, info);
+ childrenToRemove.add(view);
+ }
+ }
+ }
+ }
+
+ childCount = childrenToRemove.size();
+ for (int j = 0; j < childCount; j++) {
+ View child = childrenToRemove.get(j);
+ // Note: We can not remove the view directly from CellLayoutChildren as this
+ // does not re-mark the spaces as unoccupied.
+ layoutParent.removeViewInLayout(child);
+ if (child instanceof DropTarget) {
+ mDragController.removeDropTarget((DropTarget)child);
+ }
+ }
+
+ if (childCount > 0) {
+ layout.requestLayout();
+ layout.invalidate();
+ }
+ }
+ });
+ }
+
+ // Clean up new-apps animation list
+ final Context context = getContext();
+ post(new Runnable() {
+ @Override
+ public void run() {
+ String spKey = LauncherApplication.getSharedPreferencesKey();
+ SharedPreferences sp = context.getSharedPreferences(spKey,
+ Context.MODE_PRIVATE);
+ Set<String> newApps = sp.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY,
+ null);
+
+ // Remove all queued items that match the same package
+ if (newApps != null) {
+ synchronized (newApps) {
+ Iterator<String> iter = newApps.iterator();
+ while (iter.hasNext()) {
+ try {
+ Intent intent = Intent.parseUri(iter.next(), 0);
+ if (componentNames.contains(intent.getComponent())) {
+ iter.remove();
+ }
+
+ // It is possible that we've queued an item to be loaded, yet it has
+ // not been added to the workspace, so remove those items as well.
+ ArrayList<ItemInfo> shortcuts;
+ shortcuts = LauncherModel.getWorkspaceShortcutItemInfosWithIntent(
+ intent);
+ for (ItemInfo info : shortcuts) {
+ LauncherModel.deleteItemFromDatabase(context, info);
+ }
+ } catch (URISyntaxException e) {}
+ }
+ }
+ }
+ }
+ });
+ }
+
+ void updateShortcuts(ArrayList<ApplicationInfo> apps) {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+ int childCount = layout.getChildCount();
+ for (int j = 0; j < childCount; j++) {
+ final View view = layout.getChildAt(j);
+ Object tag = view.getTag();
+ if (tag instanceof ShortcutInfo) {
+ ShortcutInfo info = (ShortcutInfo) tag;
+ // We need to check for ACTION_MAIN otherwise getComponent() might
+ // return null for some shortcuts (for instance, for shortcuts to
+ // web pages.)
+ final Intent intent = info.intent;
+ final ComponentName name = intent.getComponent();
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
+ Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
+ final int appCount = apps.size();
+ for (int k = 0; k < appCount; k++) {
+ ApplicationInfo app = apps.get(k);
+ if (app.componentName.equals(name)) {
+ BubbleTextView shortcut = (BubbleTextView) view;
+ info.updateIcon(mIconCache);
+ info.title = app.title.toString();
+ shortcut.applyFromShortcutInfo(info, mIconCache);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void moveToDefaultScreen(boolean animate) {
+ if (!isSmall()) {
+ if (animate) {
+ snapToPage(mDefaultPage);
+ } else {
+ setCurrentPage(mDefaultPage);
+ }
+ }
+ getChildAt(mDefaultPage).requestFocus();
+ }
+
+ @Override
+ public void syncPages() {
+ }
+
+ @Override
+ public void syncPageItems(int page, boolean immediate) {
+ }
+
+ @Override
+ protected String getCurrentPageDescription() {
+ int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+ return String.format(getContext().getString(R.string.workspace_scroll_format),
+ page + 1, getChildCount());
+ }
+
+ public void getLocationInDragLayer(int[] loc) {
+ mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
+ }
+
+ void setFadeForOverScroll(float fade) {
+ if (!isScrollingIndicatorEnabled()) return;
+
+ mOverscrollFade = fade;
+ float reducedFade = 0.5f + 0.5f * (1 - fade);
+ final ViewGroup parent = (ViewGroup) getParent();
+ final ImageView qsbDivider = (ImageView) (parent.findViewById(R.id.qsb_divider));
+ final ImageView dockDivider = (ImageView) (parent.findViewById(R.id.dock_divider));
+ final View scrollIndicator = getScrollingIndicator();
+
+ cancelScrollingIndicatorAnimations();
+ if (qsbDivider != null) qsbDivider.setAlpha(reducedFade);
+ if (dockDivider != null) dockDivider.setAlpha(reducedFade);
+ scrollIndicator.setAlpha(1 - fade);
+ }
+}