From d4844c3e731b00547a31f23a00f8bd4a271e2b62 Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Fri, 18 Feb 2011 19:25:06 -0800 Subject: [PATCH] Implementing resizable widgets in launcher Change-Id: I18598493dcf34fc6089fa330a4b66803f658b773 --- res/drawable-mdpi/h_handle.png | Bin 0 -> 3668 bytes res/drawable-mdpi/resize_frame.9.png | Bin 0 -> 1683 bytes res/drawable-mdpi/v_handle.png | Bin 0 -> 3723 bytes .../launcher2/AppWidgetResizeFrame.java | 298 ++++++++++++++++++ src/com/android/launcher2/CellLayout.java | 120 ++++++- .../android/launcher2/CellLayoutChildren.java | 110 ++++++- src/com/android/launcher2/DragLayer.java | 13 + .../launcher2/LauncherAppWidgetHostView.java | 6 +- src/com/android/launcher2/LauncherModel.java | 27 ++ src/com/android/launcher2/Workspace.java | 20 +- 10 files changed, 573 insertions(+), 21 deletions(-) create mode 100644 res/drawable-mdpi/h_handle.png create mode 100644 res/drawable-mdpi/resize_frame.9.png create mode 100644 res/drawable-mdpi/v_handle.png create mode 100644 src/com/android/launcher2/AppWidgetResizeFrame.java diff --git a/res/drawable-mdpi/h_handle.png b/res/drawable-mdpi/h_handle.png new file mode 100644 index 0000000000000000000000000000000000000000..215ef05a582598287531554459934db3fca8c3a8 GIT binary patch literal 3668 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 getWidth() - BORDER_WIDTH) && horizontalActive; + mTopBorderActive = (y < BORDER_WIDTH) && verticalActive; + mBottomBorderActive = (y > getHeight() - BORDER_WIDTH) && verticalActive; + + boolean anyBordersActive = mLeftBorderActive || mRightBorderActive + || mTopBorderActive || mBottomBorderActive; + + mBaselineWidth = getMeasuredWidth(); + mBaselineHeight = getMeasuredHeight(); + mBaselineX = getLeft(); + mBaselineY = getTop(); + mRunningHInc = 0; + mRunningVInc = 0; + + if (anyBordersActive) { + mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : 0.5f); + mRightHandle.setAlpha(mRightBorderActive ? 1.0f : 0.5f); + mTopHandle.setAlpha(mTopBorderActive ? 1.0f : 0.5f); + mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : 0.5f); + } + mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability); + return anyBordersActive; + } + + public void updateDeltas(int deltaX, int deltaY) { + if (mLeftBorderActive) { + mDeltaX = Math.max(-mBaselineX, deltaX); + mDeltaX = Math.min(mBaselineWidth - 2*BORDER_WIDTH, mDeltaX); + } else if (mRightBorderActive) { + mDeltaX = Math.min(mCellLayout.getWidth() - (mBaselineX + mBaselineWidth), deltaX); + mDeltaX = Math.max(-mBaselineWidth + 2*BORDER_WIDTH, mDeltaX); + } + + if (mTopBorderActive) { + mDeltaY = Math.max(-mBaselineY, deltaY); + mDeltaY = Math.min(mBaselineHeight - 2*BORDER_WIDTH, mDeltaY); + } else if (mBottomBorderActive) { + mDeltaY = Math.min(mCellLayout.getHeight() - (mBaselineY + mBaselineHeight), deltaY); + mDeltaY = Math.max(-mBaselineHeight + 2*BORDER_WIDTH, mDeltaY); + } + } + + public void visualizeResizeForDelta(int deltaX, int deltaY) { + updateDeltas(deltaX, deltaY); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); + if (mLeftBorderActive) { + lp.x = mBaselineX + mDeltaX; + lp.width = mBaselineWidth - mDeltaX; + } else if (mRightBorderActive) { + lp.width = mBaselineWidth + mDeltaX; + } + + if (mTopBorderActive) { + lp.y = mBaselineY + mDeltaY; + lp.height = mBaselineHeight - mDeltaY; + } else if (mBottomBorderActive) { + lp.height = mBaselineHeight + mDeltaY; + } + + resizeWidgetIfNeeded(); + requestLayout(); + } + + private void resizeWidgetIfNeeded() { + // TODO: these computations probably aren't quite right... think about them + + //System.out.println("runningIncX before: " + mRunningHInc); + //System.out.println("runningIncY before: " + mRunningVInc); + + int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap(); + int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap(); + + int hSpanInc = (int) Math.round(1.0f * mDeltaX / xThreshold) - mRunningHInc; + int vSpanInc = (int) Math.round(1.0f * mDeltaY / yThreshold) - mRunningVInc; + int cellXInc = 0; + int cellYInc = 0; + + if (hSpanInc == 0 && vSpanInc == 0) return; + + // Before we change the widget, we clear the occupied cells associated with it. + // The new set of occupied cells is marked below, once the layout params are updated. + mCellLayout.markCellsAsUnoccupiedForView(mWidgetView); + + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams(); + if (mLeftBorderActive) { + cellXInc = Math.max(-mExpandability[0], hSpanInc); + cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc); + hSpanInc *= -1; + hSpanInc = Math.min(mExpandability[0], hSpanInc); + hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); + mRunningHInc -= hSpanInc; + } else if (mRightBorderActive) { + hSpanInc = Math.min(mExpandability[2], hSpanInc); + hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc); + mRunningHInc += hSpanInc; + } + + if (mTopBorderActive) { + cellYInc = Math.max(-mExpandability[1], vSpanInc); + cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc); + vSpanInc *= -1; + vSpanInc = Math.min(mExpandability[1], vSpanInc); + vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); + mRunningVInc -= vSpanInc; + } else if (mBottomBorderActive) { + vSpanInc = Math.min(mExpandability[3], vSpanInc); + vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc); + mRunningVInc += vSpanInc; + } + + // Update the widget's dimensions and position according to the deltas computed above + if (mLeftBorderActive || mRightBorderActive) { + lp.cellHSpan += hSpanInc; + lp.cellX += cellXInc; + } + + if (mTopBorderActive || mBottomBorderActive) { + lp.cellVSpan += vSpanInc; + lp.cellY += cellYInc; + } + + try { + mCellLayout.getExpandabilityArrayForView(mWidgetView, mExpandability); + } catch (Exception e) { + System.out.println("Problem!"); + } + + // Update the cells occupied by this widget + mCellLayout.markCellsAsOccupiedForView(mWidgetView); + } + + public void commitResizeForDelta(int deltaX, int deltaY) { + visualizeResizeForDelta(deltaX, deltaY); + + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams(); + LauncherModel.resizeItemInDatabase(getContext(), mItemInfo, lp.cellX, lp.cellY, + lp.cellHSpan, lp.cellVSpan); + mWidgetView.requestLayout(); + + // Once our widget resizes (hence the post), we want to snap the resize frame to it + post(new Runnable() { + public void run() { + snapToWidget(true); + } + }); + } + + public void snapToWidget(boolean animate) { + final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); + + final int newWidth = mWidgetView.getWidth() + 2 * FRAME_MARGIN; + final int newHeight = mWidgetView.getHeight() + 2 * FRAME_MARGIN; + final int newX = mWidgetView.getLeft() - FRAME_MARGIN; + final int newY = mWidgetView.getTop() - FRAME_MARGIN; + if (!animate) { + lp.width = newWidth; + lp.height = newHeight; + lp.x = newX; + lp.y = newY; + mLeftHandle.setAlpha(1.0f); + mRightHandle.setAlpha(1.0f); + mTopHandle.setAlpha(1.0f); + mBottomHandle.setAlpha(1.0f); + requestLayout(); + } else { + PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth); + PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height, newHeight); + PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX); + PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY); + ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); + ObjectAnimator leftOa = ObjectAnimator.ofFloat(mLeftHandle, "alpha", 1.0f); + ObjectAnimator rightOa = ObjectAnimator.ofFloat(mRightHandle, "alpha", 1.0f); + ObjectAnimator topOa = ObjectAnimator.ofFloat(mTopHandle, "alpha", 1.0f); + ObjectAnimator bottomOa = ObjectAnimator.ofFloat(mBottomHandle, "alpha", 1.0f); + oa.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + requestLayout(); + } + }); + AnimatorSet set = new AnimatorSet(); + if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) { + set.playTogether(oa, topOa, bottomOa); + } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) { + set.playTogether(oa, leftOa, rightOa); + } else { + set.playTogether(oa, leftOa, rightOa, topOa, bottomOa); + } + + set.setDuration(SNAP_DURATION); + set.start(); + } + } +} diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java index 6691e64477..a2a539e6b3 100644 --- a/src/com/android/launcher2/CellLayout.java +++ b/src/com/android/launcher2/CellLayout.java @@ -587,11 +587,11 @@ public class CellLayout extends ViewGroup { boolean found = false; for (int i = count - 1; i >= 0; i--) { final View child = mChildren.getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) { + if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) && lp.isLockedToGrid) { child.getHitRect(frame); if (frame.contains(x, y)) { - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); cellInfo.cell = child; cellInfo.cellX = lp.cellX; cellInfo.cellY = lp.cellY; @@ -703,6 +703,14 @@ public class CellLayout extends ViewGroup { return mCellHeight; } + int getWidthGap() { + return mWidthGap; + } + + int getHeightGap() { + return mHeightGap; + } + int getLeftPadding() { return mLeftPadding; } @@ -1332,19 +1340,68 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { } } + public void getExpandabilityArrayForView(View view, int[] expandability) { + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + boolean flag; + + // Left + expandability[0] = 0; + for (int x = lp.cellX - 1; x >= 0; x--) { + flag = false; + for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) { + if (mOccupied[x][y]) flag = true; + } + if (flag) break; + expandability[0]++; + } + + // Top + expandability[1] = 0; + for (int y = lp.cellY - 1; y >= 0; y--) { + flag = false; + for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) { + if (mOccupied[x][y]) flag = true; + } + if (flag) break; + expandability[1]++; + } + + // Right + expandability[2] = 0; + for (int x = lp.cellX + lp.cellHSpan; x < mCountX; x++) { + flag = false; + for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) { + if (mOccupied[x][y]) flag = true; + } + if (flag) break; + expandability[2]++; + } + + // Bottom + expandability[3] = 0; + for (int y = lp.cellY + lp.cellVSpan; y < mCountY; y++) { + flag = false; + for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) { + if (mOccupied[x][y]) flag = true; + } + if (flag) break; + expandability[3]++; + } + } + public void onMove(View view, int newCellX, int newCellY) { LayoutParams lp = (LayoutParams) view.getLayoutParams(); markCellsAsUnoccupiedForView(view); markCellsForView(newCellX, newCellY, lp.cellHSpan, lp.cellVSpan, true); } - private void markCellsAsOccupiedForView(View view) { + public void markCellsAsOccupiedForView(View view) { if (view == null || view.getParent() != mChildren) return; LayoutParams lp = (LayoutParams) view.getLayoutParams(); markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true); } - private void markCellsAsUnoccupiedForView(View view) { + public void markCellsAsUnoccupiedForView(View view) { if (view == null || view.getParent() != mChildren) return; LayoutParams lp = (LayoutParams) view.getLayoutParams(); markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false); @@ -1409,6 +1466,8 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { @ViewDebug.ExportedProperty public int cellVSpan; + public boolean isLockedToGrid = true; + /** * Is this item currently being dragged */ @@ -1467,19 +1526,52 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap, int hStartPadding, int vStartPadding) { + if (isLockedToGrid) { + final int myCellHSpan = cellHSpan; + final int myCellVSpan = cellVSpan; + final int myCellX = cellX; + final int myCellY = cellY; + + width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - + leftMargin - rightMargin; + height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - + topMargin - bottomMargin; + + x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; + y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; + } + } - final int myCellHSpan = cellHSpan; - final int myCellVSpan = cellVSpan; - final int myCellX = cellX; - final int myCellY = cellY; + public void setWidth(int width) { + this.width = width; + } - width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - - leftMargin - rightMargin; - height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - - topMargin - bottomMargin; + public int getWidth() { + return width; + } - x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; - y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; + public void setHeight(int height) { + this.height = height; + } + + public int getHeight() { + return height; + } + + public void setX(int x) { + this.x = x; + } + + public int getX() { + return x; + } + + public void setY(int y) { + this.y = y; + } + + public int getY() { + return y; } public String toString() { diff --git a/src/com/android/launcher2/CellLayoutChildren.java b/src/com/android/launcher2/CellLayoutChildren.java index 0d0a339072..2a08fe3821 100644 --- a/src/com/android/launcher2/CellLayoutChildren.java +++ b/src/com/android/launcher2/CellLayoutChildren.java @@ -16,12 +16,14 @@ package com.android.launcher2; +import java.util.ArrayList; + import android.app.WallpaperManager; import android.content.Context; import android.graphics.Rect; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.View.MeasureSpec; public class CellLayoutChildren extends ViewGroup { static final String TAG = "CellLayoutChildren"; @@ -32,12 +34,12 @@ public class CellLayoutChildren extends ViewGroup { private final WallpaperManager mWallpaperManager; - private int mCellWidth; - private int mCellHeight; - private int mLeftPadding; private int mTopPadding; + private int mCellWidth; + private int mCellHeight; + private int mWidthGap; private int mHeightGap; @@ -171,4 +173,102 @@ public class CellLayoutChildren extends ViewGroup { protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { super.setChildrenDrawnWithCacheEnabled(enabled); } -} \ No newline at end of file + + private final ArrayList mResizeFrames = new ArrayList(); + private AppWidgetResizeFrame mCurrentResizeFrame; + private int mXDown, mYDown; + private boolean mIsWidgetBeingResized; + + public void clearAllResizeFrames() { + for (AppWidgetResizeFrame frame: mResizeFrames) { + removeView(frame); + } + mResizeFrames.clear(); + } + + public boolean isWidgetBeingResized() { + return mIsWidgetBeingResized; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + Rect hitRect = new Rect(); + + int x = (int) ev.getX(); + int y = (int) ev.getY(); + + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + for (AppWidgetResizeFrame child: mResizeFrames) { + child.getHitRect(hitRect); + if (hitRect.contains(x, y)) { + if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { + mCurrentResizeFrame = child; + mIsWidgetBeingResized = true; + mXDown = x; + mYDown = y; + requestDisallowInterceptTouchEvent(true); + return true; + } + } + } + mCurrentResizeFrame = null; + } + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + boolean handled = false; + Rect hitRect = new Rect(); + int action = ev.getAction(); + + int x = (int) ev.getX(); + int y = (int) ev.getY(); + + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + for (AppWidgetResizeFrame child: mResizeFrames) { + child.getHitRect(hitRect); + if (hitRect.contains(x, y)) { + if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) { + mCurrentResizeFrame = child; + mIsWidgetBeingResized = true; + mXDown = x; + mYDown = y; + requestDisallowInterceptTouchEvent(true); + return true; + } + } + } + mCurrentResizeFrame = null; + } + + // TODO: Need to handle ACTION_POINTER_UP / multi-touch + if (mCurrentResizeFrame != null) { + switch (action) { + case MotionEvent.ACTION_MOVE: + mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: { + mCurrentResizeFrame.commitResizeForDelta(x - mXDown, y - mYDown); + mIsWidgetBeingResized = false; + handled = true; + } + } + } + return handled; + } + + public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget, CellLayout cellLayout) { + AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(), + itemInfo, widget, cellLayout); + + CellLayout.LayoutParams lp = new CellLayout.LayoutParams(-1, -1, -1, -1); + lp.isLockedToGrid = false; + + addView(resizeFrame, lp); + mResizeFrames.add(resizeFrame); + + resizeFrame.snapToWidget(false); + } +} diff --git a/src/com/android/launcher2/DragLayer.java b/src/com/android/launcher2/DragLayer.java index a9dd7e335b..1912f81e5d 100644 --- a/src/com/android/launcher2/DragLayer.java +++ b/src/com/android/launcher2/DragLayer.java @@ -16,6 +16,8 @@ package com.android.launcher2; +import com.android.launcher.R; + import android.content.Context; import android.graphics.Bitmap; import android.util.AttributeSet; @@ -56,6 +58,17 @@ public class DragLayer extends FrameLayout { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { + // Here we need to detect if any touch event has occured which doesn't result + // in resizing a widget. In this case, we dismiss any visible resize frames. + post(new Runnable() { + public void run() { + Workspace w = (Workspace) findViewById(R.id.workspace); + CellLayout currentPage = (CellLayout) w.getChildAt(w.getCurrentPage()); + if (!currentPage.getChildrenLayout().isWidgetBeingResized()) { + currentPage.getChildrenLayout().clearAllResizeFrames(); + } + } + }); return mDragController.onInterceptTouchEvent(ev); } diff --git a/src/com/android/launcher2/LauncherAppWidgetHostView.java b/src/com/android/launcher2/LauncherAppWidgetHostView.java index 85a80f9436..7c5de850dd 100644 --- a/src/com/android/launcher2/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher2/LauncherAppWidgetHostView.java @@ -72,7 +72,7 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView // Otherwise continue letting touch events fall through to children return false; } - + class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; @@ -122,4 +122,8 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView } super.onVisibilityChanged(changedView, visibility); } + + public int getResizableMode() { + return getAppWidgetInfo().resizableMode; + } } diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java index 23641743f7..12f57377e3 100644 --- a/src/com/android/launcher2/LauncherModel.java +++ b/src/com/android/launcher2/LauncherModel.java @@ -176,6 +176,33 @@ public class LauncherModel extends BroadcastReceiver { }); } + /** + * Resize an item in the DB to a new + */ + static void resizeItemInDatabase(Context context, ItemInfo item, int cellX, int cellY, + int spanX, int spanY) { + item.spanX = spanX; + item.spanY = spanY; + item.cellX = cellX; + item.cellY = cellY; + + final Uri uri = LauncherSettings.Favorites.getContentUri(item.id, false); + final ContentValues values = new ContentValues(); + final ContentResolver cr = context.getContentResolver(); + + values.put(LauncherSettings.Favorites.CONTAINER, item.container); + values.put(LauncherSettings.Favorites.SPANX, spanX); + values.put(LauncherSettings.Favorites.SPANY, spanY); + values.put(LauncherSettings.Favorites.CELLX, cellX); + values.put(LauncherSettings.Favorites.CELLY, cellY); + + sWorker.post(new Runnable() { + public void run() { + cr.update(uri, values, null, null); + } + }); + } + /** * Returns true if the shortcuts already exists in the database. * we identify a shortcut by its title and intent. diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java index e7735bec95..bd4b5020d4 100644 --- a/src/com/android/launcher2/Workspace.java +++ b/src/com/android/launcher2/Workspace.java @@ -2271,6 +2271,24 @@ public class Workspace extends SmoothPagedView cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen, mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY)); + if (cell instanceof LauncherAppWidgetHostView) { + final CellLayoutChildren children = dropTargetLayout.getChildrenLayout(); + 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.resizableMode != AppWidgetProviderInfo.RESIZE_NONE) { + post(new Runnable() { + public void run() { + children.addResizeFrame(info, hostView, + cellLayout); + } + }); + } + } + LauncherModel.moveItemInDatabase(mLauncher, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, screen, lp.cellX, lp.cellY); @@ -2309,7 +2327,7 @@ public class Workspace extends SmoothPagedView // would land in a cell occupied by a DragTarget (e.g. a Folder), // then drag events should be handled by that child. - ItemInfo item = (ItemInfo)dragInfo; + ItemInfo item = (ItemInfo) dragInfo; CellLayout currentLayout = getCurrentDropLayout(); int dragPointX, dragPointY;