diff --git a/lawnchair/res/values/config.xml b/lawnchair/res/values/config.xml index a5ab4bc714..aa855ce821 100644 --- a/lawnchair/res/values/config.xml +++ b/lawnchair/res/values/config.xml @@ -114,8 +114,8 @@ false true false - false - false + true + true true false false diff --git a/lawnchair/src/app/lawnchair/widget/WidgetStackContentView.kt b/lawnchair/src/app/lawnchair/widget/WidgetStackContentView.kt index e9f6309023..9c2e48e218 100644 --- a/lawnchair/src/app/lawnchair/widget/WidgetStackContentView.kt +++ b/lawnchair/src/app/lawnchair/widget/WidgetStackContentView.kt @@ -27,11 +27,14 @@ import android.widget.FrameLayout import androidx.viewpager.widget.PagerAdapter import androidx.viewpager.widget.ViewPager import app.lawnchair.smartspace.PageIndicator +import com.android.launcher3.DeviceProfile import com.android.launcher3.Launcher import com.android.launcher3.R import com.android.launcher3.model.data.LauncherAppWidgetInfo +import com.android.launcher3.util.MultiTranslateDelegate import com.android.launcher3.widget.LauncherAppWidgetHostView import com.android.launcher3.widget.LauncherWidgetHolder +import com.android.launcher3.widget.NavigableAppWidgetHostView import com.android.launcher3.widget.PendingAppWidgetHostView import com.android.launcher3.widget.WidgetInflater import com.android.launcher3.widget.WidgetManagerHelper @@ -392,6 +395,12 @@ class WidgetStackContentView @JvmOverloads constructor( } else { stopAutoRotate() } + + // Apply scaling to all widgets after stack info is updated + // This ensures widgets scale correctly when stack is resized + handler.post { + applyWidgetScaling() + } } /** @@ -573,6 +582,13 @@ class WidgetStackContentView @JvmOverloads constructor( pendingView.isClickable = false pendingView.isFocusable = false + // Apply scaling after view is created + if (pendingView is NavigableAppWidgetHostView) { + pendingView.post { + applyScalingToWidget(pendingView) + } + } + // Update position after successful restore stackInfo?.let { info -> val needsPositionUpdate = widgetInfo.screenId != info.screenId || @@ -646,6 +662,14 @@ class WidgetStackContentView @JvmOverloads constructor( ) hostView?.isClickable = false hostView?.isFocusable = false + + // Apply scaling after view is created + if (hostView is NavigableAppWidgetHostView) { + hostView.post { + applyScalingToWidget(hostView) + } + } + return hostView } @@ -721,6 +745,15 @@ class WidgetStackContentView @JvmOverloads constructor( ) pendingView.isClickable = false pendingView.isFocusable = false + + // Apply scaling after view is created + if (pendingView is NavigableAppWidgetHostView) { + // Post to apply scaling after layout + pendingView.post { + applyScalingToWidget(pendingView) + } + } + return pendingView as? LauncherAppWidgetHostView } @@ -773,6 +806,14 @@ class WidgetStackContentView @JvmOverloads constructor( hostView?.isClickable = false hostView?.isFocusable = false + // Apply scaling after view is created + if (hostView is NavigableAppWidgetHostView) { + // Post to apply scaling after layout + hostView.post { + applyScalingToWidget(hostView) + } + } + return hostView } @@ -836,6 +877,57 @@ class WidgetStackContentView @JvmOverloads constructor( } } + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + // Apply scaling to all widget views after layout + applyWidgetScaling() + } + + /** + * Applies scaling to all widget views to fit within the stack bounds. + * Similar to ShortcutAndWidgetContainer.layoutChild() logic. + */ + private fun applyWidgetScaling() { + synchronized(widgetViews) { + widgetViews.forEach { widgetView -> + if (widgetView is NavigableAppWidgetHostView) { + applyScalingToWidget(widgetView) + } + } + } + } + + /** + * Applies scaling to a single widget view to fit within the stack bounds. + * Similar to ShortcutAndWidgetContainer.layoutChild() logic. + */ + private fun applyScalingToWidget(widgetView: NavigableAppWidgetHostView) { + val launcherInstance = launcher ?: return + val profile = launcherInstance.deviceProfile + val widgetInfo = widgetView.tag as? com.android.launcher3.model.data.ItemInfo + if (widgetInfo != null) { + // Get the app widget scale from device profile + val appWidgetScale = profile.getAppWidgetScale(widgetInfo) + val scaleX = appWidgetScale.x + val scaleY = appWidgetScale.y + + // Apply scale to fit (use minimum to maintain aspect ratio) + widgetView.setScaleToFit(Math.min(scaleX, scaleY)) + + // Apply translation for centering (similar to ShortcutAndWidgetContainer) + // Use measured dimensions if layout dimensions aren't available yet + val width = if (widgetView.width > 0) widgetView.width else widgetView.measuredWidth + val height = if (widgetView.height > 0) widgetView.height else widgetView.measuredHeight + if (width > 0 && height > 0) { + widgetView.getTranslateDelegate().setTranslation( + MultiTranslateDelegate.INDEX_WIDGET_CENTERING, + -(width - (width * scaleX)) / 2.0f, + -(height - (height * scaleY)) / 2.0f, + ) + } + } + } + /** * Refreshes widgets that were pending but are now ready * This ensures widgets transition from "Loading..." to actual content @@ -918,6 +1010,14 @@ class WidgetStackContentView @JvmOverloads constructor( } else { widgetViews.add(view) } + + // Apply scaling to newly loaded widget + if (view is NavigableAppWidgetHostView) { + view.post { + applyScalingToWidget(view) + } + } + needsRefresh = true android.util.Log.d("WidgetStackContentView", "Loaded missing widget $widgetId at position $positionInStack for stack ${currentStackInfo.stackId}") } else { @@ -1007,6 +1107,13 @@ class WidgetStackContentView @JvmOverloads constructor( realView?.isFocusable = false if (realView != null) { + // Apply scaling after view is created + if (realView is NavigableAppWidgetHostView) { + realView.post { + applyScalingToWidget(realView) + } + } + // Replace pending view with real widget view synchronized(widgetViews) { val actualIndex = widgetViews.indexOfFirst { it.appWidgetId == widgetInfo.appWidgetId } @@ -1081,6 +1188,13 @@ class WidgetStackContentView @JvmOverloads constructor( realView?.isFocusable = false if (realView != null) { + // Apply scaling after view is created + if (realView is NavigableAppWidgetHostView) { + realView.post { + applyScalingToWidget(realView) + } + } + synchronized(widgetViews) { val actualIndex = widgetViews.indexOfFirst { it.appWidgetId == widgetInfo.appWidgetId } if (actualIndex != -1 && actualIndex < widgetViews.size) { @@ -1368,6 +1482,11 @@ class WidgetStackContentView @JvmOverloads constructor( // Widgets must be attached to receive live updates from AppWidgetManager container.addView(view) + // Apply scaling to widget after it's added to container + if (view is NavigableAppWidgetHostView) { + applyScalingToWidget(view) + } + // Ensure widget is properly set up for updates if (view is LauncherAppWidgetHostView) { val widgetId = view.appWidgetId @@ -1377,6 +1496,11 @@ class WidgetStackContentView @JvmOverloads constructor( // Widget is now attached and will receive updates automatically android.util.Log.d("WidgetStackContentView", "Widget $widgetId attached at position $position - will receive live updates") + // Apply scaling again after layout (in case dimensions changed) + if (view is NavigableAppWidgetHostView) { + applyScalingToWidget(view) + } + // Request an immediate update to ensure widget is fresh try { val appWidgetManager = AppWidgetManager.getInstance(context) diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index be7756234d..ff6334e856 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -43,6 +43,7 @@ import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.util.PendingRequestArgs; import com.android.launcher3.views.ArrowTipView; import com.android.launcher3.widget.LauncherAppWidgetHostView; @@ -57,6 +58,8 @@ import java.util.List; import app.lawnchair.preferences2.PreferenceManager2; import app.lawnchair.theme.color.tokens.ColorTokens; import app.lawnchair.theme.drawable.DrawableTokens; +import app.lawnchair.widget.WidgetStackInfo; +import app.lawnchair.widget.WidgetStackView; public class AppWidgetResizeFrame extends AbstractFloatingView implements View.OnKeyListener { private static final int SNAP_DURATION_MS = 150; @@ -85,6 +88,7 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O private final List mSystemGestureExclusionRects = new ArrayList<>(HANDLE_COUNT); private LauncherAppWidgetHostView mWidgetView; + private WidgetStackView mWidgetStackView; private CellLayout mCellLayout; private DragLayer mDragLayer; private ImageButton mReconfigureButton; @@ -250,6 +254,32 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O frame.post(() -> frame.snapToWidget(false)); } + public static void showForWidgetStack(WidgetStackView widgetStack, CellLayout cellLayout) { + PreferenceManager2 pref2 = PreferenceManager2.getInstance(widgetStack.getContext()); + boolean force = PreferenceExtensionsKt.firstBlocking(pref2.getForceWidgetResize()); + boolean unlimited = PreferenceExtensionsKt.firstBlocking(pref2.getWidgetUnlimitedSize()); + + // If widget stack is not added to view hierarchy, we cannot show resize frame at + // correct location + if (widgetStack.getParent() == null) { + return; + } + Launcher launcher = Launcher.getLauncher(cellLayout.getContext()); + AbstractFloatingView.closeAllOpenViews(launcher); + + DragLayer dl = launcher.getDragLayer(); + AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater() + .inflate(R.layout.app_widget_resize_frame, dl, false); + ImageView imageView = frame.findViewById(R.id.widget_resize_frame); + imageView.setImageDrawable(DrawableTokens.WidgetResizeFrame.resolve(launcher)); + frame.setupForWidgetStack(widgetStack, cellLayout, dl, force, unlimited); + ((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true; + + dl.addView(frame); + frame.mIsOpen = true; + frame.post(() -> frame.snapToWidget(false)); + } + private void setCornerRadiusFromWidget() { if (mWidgetView != null && mWidgetView.hasEnforcedCornerRadius()) { float enforcedCornerRadius = mWidgetView.getEnforcedCornerRadius(); @@ -375,6 +405,82 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O mWidgetView.addOnLayoutChangeListener(mWidgetViewLayoutListener); } + private void setupForWidgetStack(WidgetStackView widgetStackView, CellLayout cellLayout, + DragLayer dragLayer, boolean force, boolean unlimited) { + mCellLayout = cellLayout; + mWidgetStackView = widgetStackView; + mWidgetView = null; // Clear widget view when using stack + mDragLayer = dragLayer; + InvariantDeviceProfile idp = LauncherAppState.getIDP(cellLayout.getContext()); + + app.lawnchair.widget.WidgetStackInfo stackInfo = widgetStackView.getStackInfo(); + if (stackInfo == null) { + return; + } + + // For widget stacks, we allow resizing in both directions by default + // Use the minimum and maximum spans from the device profile + int resizeMode = AppWidgetProviderInfo.RESIZE_BOTH; + if (unlimited) { + mMinHSpan = 1; + mMinVSpan = 1; + mMaxHSpan = idp.numColumns; + mMaxVSpan = idp.numRows; + } else { + // Use current stack size as base, allow resizing from 1x1 to full screen + mMinHSpan = 1; + mMinVSpan = 1; + mMaxHSpan = idp.numColumns; + mMaxVSpan = idp.numRows; + } + + // Show all resize handles for widget stacks + mVerticalResizeActive = mMinVSpan < idp.numRows && mMaxVSpan > 1 && mMinVSpan < mMaxVSpan; + if (!mVerticalResizeActive) { + mDragHandles[INDEX_TOP].setVisibility(GONE); + mDragHandles[INDEX_BOTTOM].setVisibility(GONE); + } + mHorizontalResizeActive = mMinHSpan < idp.numColumns && mMaxHSpan > 1 && mMinHSpan < mMaxHSpan; + if (!mHorizontalResizeActive) { + mDragHandles[INDEX_LEFT].setVisibility(GONE); + mDragHandles[INDEX_RIGHT].setVisibility(GONE); + } + + // Hide reconfigure button for widget stacks (not applicable) + mReconfigureButton = (ImageButton) findViewById(R.id.widget_reconfigure_button); + mReconfigureButton.setVisibility(GONE); + + CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mWidgetStackView.getLayoutParams(); + ItemInfo widgetInfo = (ItemInfo) mWidgetStackView.getTag(); + if (widgetInfo != null) { + CellPos presenterPos = mLauncher.getCellPosMapper().mapModelToPresenter(widgetInfo); + lp.setCellX(presenterPos.cellX); + lp.setTmpCellX(presenterPos.cellX); + lp.setCellY(presenterPos.cellY); + lp.setTmpCellY(presenterPos.cellY); + lp.cellHSpan = widgetInfo.spanX; + lp.cellVSpan = widgetInfo.spanY; + lp.isLockedToGrid = true; + } + + // When we create the resize frame, we first mark all cells as unoccupied. The + // appropriate + // cells (same if not resized, or different) will be marked as occupied when the + // resize + // frame is dismissed. + mCellLayout.markCellsAsUnoccupiedForView(mWidgetStackView); + + if (widgetInfo != null) { + mLauncher.getStatsLogManager() + .logger() + .withInstanceId(logInstanceId) + .withItemInfo(widgetInfo) + .log(LAUNCHER_WIDGET_RESIZE_STARTED); + } + + setOnKeyListener(this); + } + public boolean beginResizeIfPointInRegion(int x, int y) { mLeftBorderActive = x < mTouchTargetWidth; mRightBorderActive = x > getWidth() - mTouchTargetWidth; @@ -491,7 +597,12 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O * Based on the current deltas, we determine if and how to resize the widget. */ private void resizeWidgetIfNeeded(boolean onDismiss) { - ViewGroup.LayoutParams wlp = mWidgetView.getLayoutParams(); + View targetView = mWidgetView != null ? mWidgetView : (View) mWidgetStackView; + if (targetView == null) { + return; + } + + ViewGroup.LayoutParams wlp = targetView.getLayoutParams(); if (!(wlp instanceof CellLayoutLayoutParams)) { return; } @@ -550,30 +661,101 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O mLastDirectionVector[1] = mDirectionVector[1]; } - // We don't want to evaluate resize if a widget was pending config activity and - // was already - // occupying a space on the screen. This otherwise will cause reorder algorithm - // evaluate a - // different location for the widget and cause a jump. - if (!(mWidgetView instanceof PendingAppWidgetHostView) && mCellLayout.createAreaForResize( - cellX, cellY, spanX, spanY, mWidgetView, mDirectionVector, onDismiss)) { - if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY)) { - mStateAnnouncer.announce( - mLauncher.getString(R.string.widget_resized, spanX, spanY)); + // Handle widget stack resize + if (mWidgetStackView != null) { + if (mCellLayout.createAreaForResize( + cellX, cellY, spanX, spanY, mWidgetStackView, mDirectionVector, onDismiss)) { + if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY)) { + mStateAnnouncer.announce( + mLauncher.getString(R.string.widget_resized, spanX, spanY)); + } + + lp.setTmpCellX(cellX); + lp.setTmpCellY(cellY); + lp.cellHSpan = spanX; + lp.cellVSpan = spanY; + mRunningVInc += vSpanDelta; + mRunningHInc += hSpanDelta; + + // Update stack info with new size + WidgetStackInfo stackInfo = mWidgetStackView.getStackInfo(); + if (stackInfo != null) { + ItemInfo itemInfo = (ItemInfo) mWidgetStackView.getTag(); + if (itemInfo != null) { + int container = itemInfo.container; + int screenId = mLauncher.getCellPosMapper().mapModelToPresenter(itemInfo).screenId; + + WidgetStackInfo updatedStackInfo = stackInfo.copy( + stackInfo.getStackId(), + stackInfo.getWidgetIds(), + stackInfo.getCurrentIndex(), + stackInfo.getAutoRotate(), + container, + screenId, + cellX, + cellY, + spanX, + spanY + ); + + // Update all widgets in the stack to the new size + final com.android.launcher3.model.BgDataModel bgDataModel = mLauncher.getModel().getBgDataModel(); + synchronized (bgDataModel) { + for (Integer widgetIdObj : stackInfo.getWidgetIds()) { + int widgetId = widgetIdObj; + for (ItemInfo item : bgDataModel.itemsIdMap) { + if (item instanceof LauncherAppWidgetInfo) { + LauncherAppWidgetInfo wInfo = (LauncherAppWidgetInfo) item; + if (wInfo.appWidgetId == widgetId) { + mLauncher.getModelWriter().modifyItemInDatabase( + wInfo, container, screenId, + cellX, cellY, spanX, spanY + ); + break; + } + } + } + } + } + + // Save updated stack info to database + mLauncher.getModelWriter().saveWidgetStack(updatedStackInfo); + + // Update the view with new stack info + mWidgetStackView.setStackInfo(updatedStackInfo); + + // Update item info + itemInfo.spanX = spanX; + itemInfo.spanY = spanY; + } + } } + } else if (mWidgetView != null) { + // We don't want to evaluate resize if a widget was pending config activity and + // was already + // occupying a space on the screen. This otherwise will cause reorder algorithm + // evaluate a + // different location for the widget and cause a jump. + if (!(mWidgetView instanceof PendingAppWidgetHostView) && mCellLayout.createAreaForResize( + cellX, cellY, spanX, spanY, mWidgetView, mDirectionVector, onDismiss)) { + if (mStateAnnouncer != null && (lp.cellHSpan != spanX || lp.cellVSpan != spanY)) { + mStateAnnouncer.announce( + mLauncher.getString(R.string.widget_resized, spanX, spanY)); + } - lp.setTmpCellX(cellX); - lp.setTmpCellY(cellY); - lp.cellHSpan = spanX; - lp.cellVSpan = spanY; - mRunningVInc += vSpanDelta; - mRunningHInc += hSpanDelta; + lp.setTmpCellX(cellX); + lp.setTmpCellY(cellY); + lp.cellHSpan = spanX; + lp.cellVSpan = spanY; + mRunningVInc += vSpanDelta; + mRunningHInc += hSpanDelta; - if (!onDismiss) { - WidgetSizes.updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY); + if (!onDismiss) { + WidgetSizes.updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY); + } } } - mWidgetView.requestLayout(); + targetView.requestLayout(); } @Override @@ -583,11 +765,17 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O // We are done with resizing the widget. Save the widget size & position to // LauncherModel resizeWidgetIfNeeded(true); - mLauncher.getStatsLogManager() - .logger() - .withInstanceId(logInstanceId) - .withItemInfo((ItemInfo) mWidgetView.getTag()) - .log(LAUNCHER_WIDGET_RESIZE_COMPLETED); + View targetView = mWidgetView != null ? mWidgetView : (View) mWidgetStackView; + if (targetView != null) { + ItemInfo itemInfo = (ItemInfo) targetView.getTag(); + if (itemInfo != null) { + mLauncher.getStatsLogManager() + .logger() + .withInstanceId(logInstanceId) + .withItemInfo(itemInfo) + .log(LAUNCHER_WIDGET_RESIZE_COMPLETED); + } + } } private void onTouchUp() { @@ -609,11 +797,17 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O * relative to the {@link DragLayer}. */ private void getSnappedRectRelativeToDragLayer(@NonNull Rect out) { - float scale = mWidgetView.getScaleToFit(); - if (FeatureFlags.ENABLE_WIDGET_TRANSITION_FOR_RESIZING.get()) { + View targetView = mWidgetView != null ? mWidgetView : (View) mWidgetStackView; + if (targetView == null) { + out.setEmpty(); + return; + } + + float scale = mWidgetView != null ? mWidgetView.getScaleToFit() : 1.0f; + if (FeatureFlags.ENABLE_WIDGET_TRANSITION_FOR_RESIZING.get() && mWidgetView != null) { getViewRectRelativeToDragLayer(out); } else { - mDragLayer.getViewRectRelativeToSelf(mWidgetView, out); + mDragLayer.getViewRectRelativeToSelf(targetView, out); } int width = 2 * mBackgroundPadding + Math.round(scale * out.width()); @@ -658,8 +852,12 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O // The widget is guaranteed to be attached to the cell layout at this point, // thus setting // the transition here + View targetView = mWidgetView != null ? mWidgetView : (View) mWidgetStackView; + if (targetView == null) { + return; + } if (FeatureFlags.ENABLE_WIDGET_TRANSITION_FOR_RESIZING.get() - && mWidgetView.getLayoutTransition() == null) { + && mWidgetView != null && mWidgetView.getLayoutTransition() == null) { final LayoutTransition transition = new LayoutTransition(); transition.setDuration(RESIZE_TRANSITION_DURATION_MS); transition.enableTransitionType(LayoutTransition.CHANGING); @@ -813,12 +1011,14 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O @Override protected void handleClose(boolean animate) { - if (FeatureFlags.ENABLE_WIDGET_TRANSITION_FOR_RESIZING.get()) { - mWidgetView.clearCellChildViewPreLayoutListener(); - mWidgetView.setLayoutTransition(null); + if (mWidgetView != null) { + if (FeatureFlags.ENABLE_WIDGET_TRANSITION_FOR_RESIZING.get()) { + mWidgetView.clearCellChildViewPreLayoutListener(); + mWidgetView.setLayoutTransition(null); + } + mWidgetView.removeOnLayoutChangeListener(mWidgetViewLayoutListener); } mDragLayer.removeView(this); - mWidgetView.removeOnLayoutChangeListener(mWidgetViewLayoutListener); } private void updateInvalidResizeEffect(CellLayout cellLayout, CellLayout pairedCellLayout, diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index ae72bcda76..e28c4412fa 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -56,7 +56,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; -import android.os.Looper; import android.os.Message; import android.os.Parcelable; import android.util.AttributeSet; @@ -2816,12 +2815,12 @@ public class Workspace extends PagedView lp.isLockedToGrid = true; if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT && - cell instanceof LauncherAppWidgetHostView) { + (cell instanceof LauncherAppWidgetHostView || cell instanceof WidgetStackView)) { // We post this call so that the widget has a chance to be placed // in its final location onCompleteRunnable = getWidgetResizeFrameRunnable(options, - (LauncherAppWidgetHostView) cell, dropTargetLayout, forceWidgetResize); + cell, dropTargetLayout, forceWidgetResize); } // If this is a widget stack, update the stack info position @@ -2906,7 +2905,7 @@ public class Workspace extends PagedView if (pageIsVisible) { onCompleteRunnable = getWidgetResizeFrameRunnable(options, - (LauncherAppWidgetHostView) cell, cellLayout, forceWidgetResize); + cell, cellLayout, forceWidgetResize); } } } @@ -2971,15 +2970,28 @@ public class Workspace extends PagedView @Nullable private Runnable getWidgetResizeFrameRunnable(DragOptions options, - LauncherAppWidgetHostView hostView, CellLayout cellLayout, boolean force) { - AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo(); - boolean shouldResize = (pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) || force; - if (pInfo != null && shouldResize && !options.isAccessibleDrag) { - return () -> { - if (!isPageInTransition()) { - AppWidgetResizeFrame.showForWidget(hostView, cellLayout); - } - }; + View widgetView, CellLayout cellLayout, boolean force) { + if (widgetView instanceof LauncherAppWidgetHostView) { + LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) widgetView; + AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo(); + boolean shouldResize = (pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) || force; + if (pInfo != null && shouldResize && !options.isAccessibleDrag) { + return () -> { + if (!isPageInTransition()) { + AppWidgetResizeFrame.showForWidget(hostView, cellLayout); + } + }; + } + } else if (widgetView instanceof WidgetStackView) { + WidgetStackView stackView = (WidgetStackView) widgetView; + // Widget stacks are always resizable + if (!options.isAccessibleDrag) { + return () -> { + if (!isPageInTransition()) { + AppWidgetResizeFrame.showForWidgetStack(stackView, cellLayout); + } + }; + } } return null; }