outlines,
- DropTarget.DragObject dragObject) {
+ private void removePredictedApps(DropTarget.DragObject dragObject) {
if (mIconRemoveAnimators != null) {
mIconRemoveAnimators.end();
}
mIconRemoveAnimators = new AnimatorSet();
removeOutlineDrawings();
- for (PredictedAppIcon icon : getPredictedIcons()) {
+ for (PredictedIconOnPage entry : getPredictedIcons()) {
+ PredictedAppIcon icon = entry.icon;
if (!icon.isEnabled()) {
continue;
}
@@ -345,8 +382,10 @@ public class HotseatPredictionController implements DragController.DragListener,
continue;
}
int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
- outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
- mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
+ mOutlineDrawings.add(new OutlineOnPage(entry.page,
+ new PredictedAppIcon.PredictedIconOutlineDrawing(
+ mHotseat.getCellXFromOrder(rank),
+ mHotseat.getCellYFromOrder(rank), icon)));
icon.setEnabled(false);
ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
animator.addListener(new AnimationSuccessListener() {
@@ -369,19 +408,28 @@ public class HotseatPredictionController implements DragController.DragListener,
*/
private void removeIconWithoutNotify(PredictedAppIcon icon) {
mPauseFlags |= FLAG_REMOVING_PREDICTED_ICON;
- mHotseat.removeView(icon);
+ ViewGroup parent = (ViewGroup) icon.getParent();
+ if (parent != null) {
+ // Parent is ShortcutAndWidgetContainer; remove via CellLayout.
+ ViewGroup cellLayout = (ViewGroup) parent.getParent();
+ if (cellLayout instanceof CellLayout) {
+ cellLayout.removeView(icon);
+ } else {
+ parent.removeView(icon);
+ }
+ }
mPauseFlags &= ~FLAG_REMOVING_PREDICTED_ICON;
}
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
- removePredictedApps(mOutlineDrawings, dragObject);
+ removePredictedApps(dragObject);
if (mOutlineDrawings.isEmpty()) return;
- for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
- mHotseat.addDelegatedCellDrawing(outlineDrawing);
+ for (OutlineOnPage outline : mOutlineDrawings) {
+ outline.page.addDelegatedCellDrawing(outline.drawing);
+ outline.page.invalidate();
}
mPauseFlags |= FLAG_DRAG_IN_PROGRESS;
- mHotseat.invalidate();
}
@Override
@@ -419,6 +467,7 @@ public class HotseatPredictionController implements DragController.DragListener,
@Override
public void onDeviceProfileChanged(DeviceProfile profile) {
this.mHotSeatItemsCount = profile.numShownHotseatIcons * profile.numHotseatRows;
+ attachPageListeners();
}
@Override
@@ -447,8 +496,8 @@ public class HotseatPredictionController implements DragController.DragListener,
}
int cardinality = 0;
- for (PredictedAppIcon icon : getPredictedIcons()) {
- ItemInfo info = (ItemInfo) icon.getTag();
+ for (PredictedIconOnPage entry : getPredictedIcons()) {
+ ItemInfo info = (ItemInfo) entry.icon.getTag();
cardinality |= 1 << info.screenId;
}
@@ -482,7 +531,11 @@ public class HotseatPredictionController implements DragController.DragListener,
* Called when user completes adding item requiring a config activity to the hotseat
*/
public void onDeferredDrop(int cellX, int cellY) {
- View child = mHotseat.getChildAt(cellX, cellY);
+ CellLayout page = mHotseat.getCurrentPageLayout();
+ if (page == null) {
+ return;
+ }
+ View child = page.getChildAt(cellX, cellY);
if (child instanceof PredictedAppIcon) {
removeIconWithoutNotify((PredictedAppIcon) child);
}
@@ -534,4 +587,24 @@ public class HotseatPredictionController implements DragController.DragListener,
writer.println(prefix + "\t\t" + info);
}
}
+
+ private static final class PredictedIconOnPage {
+ final CellLayout page;
+ final PredictedAppIcon icon;
+
+ PredictedIconOnPage(CellLayout page, PredictedAppIcon icon) {
+ this.page = page;
+ this.icon = icon;
+ }
+ }
+
+ private static final class OutlineOnPage {
+ final CellLayout page;
+ final PredictedAppIcon.PredictedIconOutlineDrawing drawing;
+
+ OutlineOnPage(CellLayout page, PredictedAppIcon.PredictedIconOutlineDrawing drawing) {
+ this.page = page;
+ this.drawing = drawing;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt b/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt
index 2f90ee760e..7e79806c05 100644
--- a/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt
+++ b/quickstep/src/com/android/quickstep/util/unfold/UnfoldAnimationBuilder.kt
@@ -160,7 +160,11 @@ object UnfoldAnimationBuilder {
LINEAR
)
}
- registerViews(hotseat)
+ setClipChildren(hotseat, false, restoreList)
+ setClipToPadding(hotseat, false, restoreList)
+ for (page in hotseat.pageLayouts) {
+ registerViews(page)
+ }
}
anim.addEndListener { restoreList.forEach { it.action(it.target, it.value) } }
}
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
index d14dcd54af..461b4a910b 100644
--- a/res/layout/hotseat.xml
+++ b/res/layout/hotseat.xml
@@ -12,10 +12,11 @@
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.
+
+ Modifications copyright 2026 Lawnchair
-->
\ No newline at end of file
+ android:clipChildren="false" />
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 1b4c319353..eef2c43797 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -191,6 +191,8 @@ public class CellLayout extends ViewGroup {
public static final int FOLDER = 2;
@ContainerType private final int mContainerType;
+ /** Page index within a multi-page hotseat; unused for non-hotseat layouts. */
+ private int mHotseatPageIndex = 0;
public static final float DEFAULT_SCALE = 1f;
@@ -336,6 +338,20 @@ public class CellLayout extends ViewGroup {
mCellLayoutContainer = cellLayoutContainer;
}
+ /** Sets which dock page this layout represents when used as a hotseat page. */
+ public void setHotseatPageIndex(int pageIndex) {
+ mHotseatPageIndex = pageIndex;
+ }
+
+ /** Returns the dock page index for multi-page hotseat layouts. */
+ public int getHotseatPageIndex() {
+ return mHotseatPageIndex;
+ }
+
+ public boolean isHotseat() {
+ return mContainerType == HOTSEAT;
+ }
+
/**
* Sets or clears a delegate used for accessible drag and drop
*/
@@ -1473,7 +1489,7 @@ public class CellLayout extends ViewGroup {
int container = Favorites.CONTAINER_DESKTOP;
if (mContainerType == HOTSEAT) {
- screenId = -1;
+ screenId = mHotseatPageIndex;
container = Favorites.CONTAINER_HOTSEAT;
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 8459796da3..87ae24be8a 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -221,6 +221,8 @@ public class DeviceProfile {
private final HotseatProfile hotseatProfile;
public int numShownHotseatIcons;
public int numHotseatRows;
+ /** Number of horizontally scrollable dock pages (1 = classic single dock). */
+ public int numHotseatPages;
public int hotseatCellHeightPx;
private int mHotseatColumnSpan;
private int mHotseatWidthPx; // not used in vertical bar layout
@@ -331,6 +333,7 @@ public class DeviceProfile {
hotseatQsbWidth = 0;
hotseatBorderSpace = 0;
numHotseatRows = 1;
+ numHotseatPages = 1;
mBubbleBarSpaceThresholdPx = 0;
numShownAllAppsColumns = 0;
mViewScaleProvider = null;
@@ -517,6 +520,7 @@ public class DeviceProfile {
numShownHotseatIcons = displayOptionSpec.numShownHotseatIcons;
numHotseatRows = Math.max(1, Math.min(2, PreferenceManager.getInstance(context).getHotseatRows().get()));
+ numHotseatPages = Math.max(1, Math.min(5, PreferenceManager.getInstance(context).getDockPages().get()));
mHotseatColumnSpan = inv.numColumns;
numShownAllAppsColumns = displayOptionSpec.numAllAppsColumns;
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 5c490ff3e3..a1e45f9866 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -27,11 +27,8 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.InsetDrawable;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -45,35 +42,34 @@ import android.widget.FrameLayout;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
-import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider;
+import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
-import com.android.launcher3.taskbar.BlurredBitmapDrawable;
-
+import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.util.HorizontalInsettableView;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.views.ActivityContext;
-import android.graphics.drawable.Drawable;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import com.hoko.blur.HokoBlur;
-import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
import app.lawnchair.hotseat.DisabledHotseat;
import app.lawnchair.hotseat.HotseatMode;
+import app.lawnchair.hotseat.HotseatPagedView;
import app.lawnchair.hotseat.LawnchairHotseat;
import app.lawnchair.preferences.PreferenceManager;
+import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
import app.lawnchair.preferences2.PreferenceManager2;
import app.lawnchair.theme.drawable.DrawableTokens;
/**
- * View class that represents the bottom row of the home screen.
+ * View class that represents the bottom dock of the home screen.
+ * Hosts a {@link app.lawnchair.hotseat.HotseatPagedView} of icon grids plus an optional QSB.
*/
-public class Hotseat extends CellLayout implements Insettable {
+public class Hotseat extends FrameLayout implements Insettable {
public static final int ALPHA_CHANNEL_TASKBAR_ALIGNMENT = 0;
public static final int ALPHA_CHANNEL_PREVIEW_RENDERER = 1;
@@ -98,11 +94,13 @@ public class Hotseat extends CellLayout implements Insettable {
// Ratio of empty space, qsb should take up to appear visually centered.
public static final float QSB_CENTER_FACTOR = .325f;
private static final int BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS = 250;
+ private static final int DOCK_PAGE_INDICATOR_HEIGHT_DP = 8;
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mHasVerticalHotseat;
private Workspace> mWorkspace;
private boolean mSendTouchToWorkspace;
+ private boolean mSendTouchToPager;
private final MultiValueAlpha mIconsAlphaChannels;
private final MultiValueAlpha mQsbAlphaChannels;
@@ -111,6 +109,12 @@ public class Hotseat extends CellLayout implements Insettable {
private final MultiPropertyFactory mIconsTranslationXFactory;
private final View mQsb;
+ private final FrameLayout mIconsContainer;
+ private final HotseatPagedView mPagedView;
+ private final PageIndicatorDots mPageIndicator;
+ private final int mPageIndicatorHeight;
+
+ private final ActivityContext mActivity;
PreferenceManager2 preferenceManager2;
PreferenceManager preferenceManager;
@@ -125,6 +129,7 @@ public class Hotseat extends CellLayout implements Insettable {
public Hotseat(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ mActivity = ActivityContext.lookupContext(context);
preferenceManager2 = PreferenceManager2.getInstance(context);
preferenceManager = PreferenceManager.getInstance(context);
@@ -143,6 +148,29 @@ public class Hotseat extends CellLayout implements Insettable {
}
int layoutId = hotseatMode.getLayoutResourceId();
+ mIconsContainer = new FrameLayout(context);
+ mIconsContainer.setLayoutParams(new LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ mIconsContainer.setClipChildren(false);
+ mIconsContainer.setClipToPadding(false);
+ addView(mIconsContainer);
+
+ mPagedView = new HotseatPagedView(context);
+ mPagedView.setLayoutParams(new LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ mIconsContainer.addView(mPagedView);
+
+ mPageIndicatorHeight = Math.round(
+ DOCK_PAGE_INDICATOR_HEIGHT_DP * getResources().getDisplayMetrics().density);
+ mPageIndicator = new PageIndicatorDots(context);
+ LayoutParams indicatorLp = new LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, mPageIndicatorHeight);
+ indicatorLp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ mPageIndicator.setLayoutParams(indicatorLp);
+ mPageIndicator.setVisibility(GONE);
+ mIconsContainer.addView(mPageIndicator);
+ mPagedView.setPageIndicator(mPageIndicator);
+
if (Flags.enableQsbOnHotseat()) {
mQsb = LayoutInflater.from(context).inflate(R.layout.qsb_container_hotseat, this,
false);
@@ -152,19 +180,24 @@ public class Hotseat extends CellLayout implements Insettable {
}
addView(mQsb);
- mIconsAlphaChannels = new MultiValueAlpha(getShortcutsAndWidgets(),
- ALPHA_CHANNEL_CHANNELS_COUNT);
+
+ // Create an initial page so alpha/translation channels have a target during construction.
+ mPagedView.resetPages(false, null, mActivity.getDeviceProfile());
+
+ mIconsAlphaChannels = new MultiValueAlpha(mIconsContainer, ALPHA_CHANNEL_CHANNELS_COUNT);
mIconsAlphaChannels.setUpdateVisibility(true);
if (mQsb instanceof Reorderable qsbReorderable) {
mQsbTranslationX = qsbReorderable.getTranslateDelegate()
.getTranslationX(MultiTranslateDelegate.INDEX_NAV_BAR_ANIM);
}
- mIconsTranslationXFactory = new MultiPropertyFactory<>(getShortcutsAndWidgets(),
+ mIconsTranslationXFactory = new MultiPropertyFactory<>(mIconsContainer,
VIEW_TRANSLATE_X, ICONS_TRANSLATION_X_CHANNELS_COUNT, Float::sum);
mQsbAlphaChannels = new MultiValueAlpha(mQsb, ALPHA_CHANNEL_CHANNELS_COUNT);
mQsbAlphaChannels.setUpdateVisibility(true);
setUpBackground();
+ setClipChildren(false);
+ setClipToPadding(false);
}
private void setUpBackground() {
@@ -196,6 +229,40 @@ public class Hotseat extends CellLayout implements Insettable {
return mQsbTranslationX;
}
+ public HotseatPagedView getPagedView() {
+ return mPagedView;
+ }
+
+ /** Returns the CellLayout for the given dock page. */
+ @Nullable
+ public CellLayout getPageAt(int page) {
+ return mPagedView.getPageAt(page);
+ }
+
+ /** Returns the currently visible dock page layout. */
+ @Nullable
+ public CellLayout getCurrentPageLayout() {
+ return mPagedView.getCurrentCellLayout();
+ }
+
+ /** Returns all dock page layouts. */
+ public CellLayout[] getPageLayouts() {
+ int count = mPagedView.getPageCount();
+ CellLayout[] pages = new CellLayout[count];
+ for (int i = 0; i < count; i++) {
+ pages[i] = mPagedView.getPageAt(i);
+ }
+ return pages;
+ }
+
+ /** Whether {@code layout} is one of this hotseat's page CellLayouts. */
+ public boolean isHotseatPage(View layout) {
+ if (!(layout instanceof CellLayout)) {
+ return false;
+ }
+ return layout.getParent() == mPagedView;
+ }
+
/**
* Returns orientation specific cell X given invariant order in the hotseat
*/
@@ -205,7 +272,8 @@ public class Hotseat extends CellLayout implements Insettable {
}
DeviceProfile dp = mActivity.getDeviceProfile();
int numColumns = dp.numShownHotseatIcons;
- return rank % numColumns;
+ int localRank = getLocalRank(rank, dp);
+ return localRank % numColumns;
}
/**
@@ -213,11 +281,29 @@ public class Hotseat extends CellLayout implements Insettable {
*/
public int getCellYFromOrder(int rank) {
if (mHasVerticalHotseat) {
- return getCountY() - (rank + 1);
+ CellLayout page = getCurrentPageLayout();
+ int countY = page != null ? page.getCountY() : mActivity.getDeviceProfile().numShownHotseatIcons;
+ return countY - (rank + 1);
}
DeviceProfile dp = mActivity.getDeviceProfile();
int numColumns = dp.numShownHotseatIcons;
- return rank / numColumns;
+ int localRank = getLocalRank(rank, dp);
+ return localRank / numColumns;
+ }
+
+ /** Returns the dock page index for a global hotseat rank. */
+ public int getPageFromOrder(int rank) {
+ if (mHasVerticalHotseat) {
+ return 0;
+ }
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ int slotsPerPage = Math.max(1, dp.numShownHotseatIcons * dp.numHotseatRows);
+ return rank / slotsPerPage;
+ }
+
+ private static int getLocalRank(int rank, DeviceProfile dp) {
+ int slotsPerPage = Math.max(1, dp.numShownHotseatIcons * dp.numHotseatRows);
+ return rank % slotsPerPage;
}
boolean isHasVerticalHotseat() {
@@ -228,45 +314,34 @@ public class Hotseat extends CellLayout implements Insettable {
ActivityContext activityContext = ActivityContext.lookupContext(getContext());
boolean bubbleBarEnabled = activityContext.isBubbleBarEnabled();
boolean hasBubbles = activityContext.hasBubbles();
- removeAllViewsInLayout();
mHasVerticalHotseat = hasVerticalHotseat;
DeviceProfile dp = mActivity.getDeviceProfile();
+ mPagedView.resetPages(hasVerticalHotseat, mWorkspace, dp);
+
if (bubbleBarEnabled) {
- if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) {
- getShortcutsAndWidgets().setTranslationProvider(
- cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
- if (mQsb instanceof HorizontalInsettableView) {
- HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb;
+ for (CellLayout page : getPageLayouts()) {
+ if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) {
+ page.getShortcutsAndWidgets().setTranslationProvider(
+ cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
+ } else {
+ page.getShortcutsAndWidgets().setTranslationProvider(null);
+ }
+ }
+ if (mQsb instanceof HorizontalInsettableView) {
+ HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb;
+ if (dp.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles)) {
final float insetFraction = (float) dp.iconSizePx / dp.hotseatQsbWidth;
- // post this to the looper so that QSB has a chance to redraw itself, e.g.
- // after device rotation
mQsb.post(() -> insettableQsb.setHorizontalInsets(insetFraction));
- }
- } else {
- getShortcutsAndWidgets().setTranslationProvider(null);
- if (mQsb instanceof HorizontalInsettableView) {
- ((HorizontalInsettableView) mQsb).setHorizontalInsets(0);
+ } else {
+ insettableQsb.setHorizontalInsets(0);
}
}
- }
-
- resetCellSize(dp);
- if (hasVerticalHotseat) {
- setGridSize(1, dp.numShownHotseatIcons);
- } else {
- setGridSize(dp.numShownHotseatIcons, dp.numHotseatRows);
}
}
/**
* Adjust the hotseat icons for the bubble bar.
- *
- * When the bubble bar becomes visible, if needed, this method animates the hotseat icons
- * to reduce the spacing between them and make room for the bubble bar. The QSB width is
- * animated as well to align with the hotseat icons.
- *
- *
When the bubble bar goes away, any adjustments that were previously made are reversed.
*/
public void adjustForBubbleBar(boolean isBubbleBarVisible) {
DeviceProfile dp = mActivity.getDeviceProfile();
@@ -274,30 +349,30 @@ public class Hotseat extends CellLayout implements Insettable {
&& dp.shouldAdjustHotseatOrQsbForBubbleBar(getContext());
boolean shouldAdjustHotseat = shouldAdjust
&& dp.shouldAlignBubbleBarWithHotseat();
- ShortcutAndWidgetContainer icons = getShortcutsAndWidgets();
- // update the translation provider for future layout passes of hotseat icons.
- if (shouldAdjustHotseat) {
- icons.setTranslationProvider(
- cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
- } else {
- icons.setTranslationProvider(null);
- }
AnimatorSet animatorSet = new AnimatorSet();
- for (int i = 0; i < icons.getChildCount(); i++) {
- View child = icons.getChildAt(i);
- if (child.getLayoutParams() instanceof CellLayoutLayoutParams lp) {
- float tx = shouldAdjustHotseat
- ? dp.getHotseatAdjustedTranslation(getContext(), lp.getCellX()) : 0;
- if (child instanceof Reorderable) {
- MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
- animatorSet.play(
- mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
- } else {
- animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
+ for (CellLayout page : getPageLayouts()) {
+ ShortcutAndWidgetContainer icons = page.getShortcutsAndWidgets();
+ if (shouldAdjustHotseat) {
+ icons.setTranslationProvider(
+ cellX -> dp.getHotseatAdjustedTranslation(getContext(), cellX));
+ } else {
+ icons.setTranslationProvider(null);
+ }
+ for (int i = 0; i < icons.getChildCount(); i++) {
+ View child = icons.getChildAt(i);
+ if (child.getLayoutParams() instanceof CellLayoutLayoutParams lp) {
+ float tx = shouldAdjustHotseat
+ ? dp.getHotseatAdjustedTranslation(getContext(), lp.getCellX()) : 0;
+ if (child instanceof Reorderable) {
+ MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+ animatorSet.play(
+ mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
+ } else {
+ animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
+ }
}
}
}
- //TODO(b/381109832) refactor & simplify adjustment logic
boolean shouldAdjustQsb =
shouldAdjustHotseat || (shouldAdjust && dp.shouldAlignBubbleBarWithQSB());
if (mQsb instanceof HorizontalInsettableView horizontalInsettableQsb) {
@@ -315,13 +390,6 @@ public class Hotseat extends CellLayout implements Insettable {
animatorSet.setDuration(BUBBLE_BAR_ADJUSTMENT_ANIMATION_DURATION_MS).start();
}
- @Override
- protected int getTranslationXForCell(int cellX, int cellY) {
- TranslationProvider translationProvider = getShortcutsAndWidgets().getTranslationProvider();
- if (translationProvider == null) return 0;
- return (int) translationProvider.getTranslationX(cellX);
- }
-
@Override
public void setInsets(Rect insets) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
@@ -329,6 +397,7 @@ public class Hotseat extends CellLayout implements Insettable {
if (grid.isVerticalBarLayout()) {
mQsb.setVisibility(View.GONE);
+ mPageIndicator.setVisibility(GONE);
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
if (grid.isSeascape()) {
lp.gravity = Gravity.LEFT;
@@ -352,16 +421,48 @@ public class Hotseat extends CellLayout implements Insettable {
public void setWorkspace(Workspace> w) {
mWorkspace = w;
- setCellLayoutContainer(w);
+ for (CellLayout page : getPageLayouts()) {
+ page.setCellLayoutContainer(w);
+ }
+ }
+
+ private boolean isTouchOnQsb(MotionEvent ev) {
+ if (mQsb == null || mQsb.getVisibility() != VISIBLE) {
+ return false;
+ }
+ float x = ev.getX();
+ float y = ev.getY();
+ return x >= mQsb.getLeft() && x < mQsb.getRight()
+ && y >= mQsb.getTop() && y < mQsb.getBottom();
+ }
+
+ private MotionEvent obtainPagerEvent(MotionEvent ev) {
+ MotionEvent pagerEv = MotionEvent.obtain(ev);
+ pagerEv.offsetLocation(-mIconsContainer.getLeft() - mPagedView.getLeft(),
+ -mIconsContainer.getTop() - mPagedView.getTop());
+ return pagerEv;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
- // We allow horizontal workspace scrolling from within the Hotseat. We do this by delegating
- // touch intercept the Workspace, and if it intercepts, delegating touch to the Workspace
- // for the remainder of the this input stream.
int yThreshold = getMeasuredHeight() - getPaddingBottom();
- if (mWorkspace != null && ev.getY() <= yThreshold) {
+ if (ev.getY() > yThreshold || isTouchOnQsb(ev)) {
+ return false;
+ }
+
+ // Multi-page dock: own the icon-band stream and dispatch it to the pager so empty
+ // pages can still scroll (same pattern as single-page workspace forwarding).
+ if (mPagedView.isPagingEnabled()) {
+ final int action = ev.getAction() & MotionEvent.ACTION_MASK;
+ if (action == MotionEvent.ACTION_DOWN) {
+ mSendTouchToPager = true;
+ mSendTouchToWorkspace = false;
+ }
+ return mSendTouchToPager;
+ }
+
+ // Single-page dock: forward horizontal swipes to workspace.
+ if (mWorkspace != null) {
mSendTouchToWorkspace = mWorkspace.onInterceptTouchEvent(ev);
return mSendTouchToWorkspace;
}
@@ -370,7 +471,16 @@ public class Hotseat extends CellLayout implements Insettable {
@Override
public boolean onTouchEvent(MotionEvent event) {
- // See comment in #onInterceptTouchEvent
+ if (mSendTouchToPager) {
+ MotionEvent pagerEv = obtainPagerEvent(event);
+ boolean handled = mPagedView.dispatchTouchEvent(pagerEv);
+ pagerEv.recycle();
+ final int action = event.getAction() & MotionEvent.ACTION_MASK;
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mSendTouchToPager = false;
+ }
+ return handled;
+ }
if (mSendTouchToWorkspace) {
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
@@ -380,45 +490,93 @@ public class Hotseat extends CellLayout implements Insettable {
}
return mWorkspace.onTouchEvent(event);
}
- // Always let touch follow through to Workspace.
return false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(widthSize, heightSize);
+
+ int paddingLeft = getPaddingLeft();
+ int paddingRight = getPaddingRight();
+ int paddingTop = getPaddingTop();
+ int paddingBottom = getPaddingBottom();
DeviceProfile dp = mActivity.getDeviceProfile();
+ boolean showIndicator = mPagedView.isPagingEnabled() && !dp.isVerticalBarLayout();
+ int indicatorSpace = showIndicator ? mPageIndicatorHeight : 0;
+
+ int pagerWidth = widthSize - paddingLeft - paddingRight;
+ int pagerHeight = heightSize - paddingTop - paddingBottom;
+ mIconsContainer.measure(
+ makeMeasureSpec(Math.max(0, pagerWidth), MeasureSpec.EXACTLY),
+ makeMeasureSpec(Math.max(0, pagerHeight), MeasureSpec.EXACTLY));
+
+ int pageHeight = Math.max(0, pagerHeight - indicatorSpace);
+ mPagedView.measure(
+ makeMeasureSpec(Math.max(0, pagerWidth), MeasureSpec.EXACTLY),
+ makeMeasureSpec(pageHeight, MeasureSpec.EXACTLY));
+
+ if (showIndicator) {
+ mPageIndicator.measure(
+ makeMeasureSpec(Math.max(0, pagerWidth), MeasureSpec.EXACTLY),
+ makeMeasureSpec(mPageIndicatorHeight, MeasureSpec.EXACTLY));
+ }
- // LC: Fix weird sizing with hotseatQsbWidth being 0 on phone
int width;
if (dp.isQsbInline) {
width = dp.hotseatQsbWidth;
} else {
- width = getShortcutsAndWidgets().getMeasuredWidth();
+ width = mPagedView.getMeasuredWidth();
}
-
- mQsb.measure(makeMeasureSpec(width, MeasureSpec.EXACTLY),
+
+ mQsb.measure(makeMeasureSpec(Math.max(0, width), MeasureSpec.EXACTLY),
makeMeasureSpec(dp.getHotseatProfile().getQsbHeight(), MeasureSpec.EXACTLY));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
+ int width = r - l;
+ int height = b - t;
+ int paddingLeft = getPaddingLeft();
+ int paddingRight = getPaddingRight();
+ int paddingTop = getPaddingTop();
+ int paddingBottom = getPaddingBottom();
+
+ DeviceProfile dp = mActivity.getDeviceProfile();
+ mIconsContainer.layout(paddingLeft, paddingTop, width - paddingRight,
+ height - paddingBottom);
+
+ int containerWidth = mIconsContainer.getWidth();
+ int containerHeight = mIconsContainer.getHeight();
+ boolean showIndicator = mPagedView.isPagingEnabled() && !dp.isVerticalBarLayout();
+ int indicatorSpace = showIndicator ? mPageIndicatorHeight : 0;
+
+ mPagedView.layout(0, 0, containerWidth, Math.max(0, containerHeight - indicatorSpace));
+
+ if (showIndicator) {
+ int indicatorTop = containerHeight - mPageIndicatorHeight;
+ mPageIndicator.layout(0, indicatorTop, containerWidth,
+ indicatorTop + mPageIndicatorHeight);
+ mPageIndicator.setVisibility(VISIBLE);
+ } else {
+ mPageIndicator.setVisibility(GONE);
+ }
int qsbMeasuredWidth = mQsb.getMeasuredWidth();
int left;
- DeviceProfile dp = mActivity.getDeviceProfile();
if (dp.isQsbInline) {
int qsbSpace = dp.hotseatBorderSpace;
left = Utilities.isRtl(getResources()) ? r - getPaddingRight() + qsbSpace
: l + getPaddingLeft() - qsbMeasuredWidth - qsbSpace;
} else {
- left = (r - l - qsbMeasuredWidth) / 2;
+ left = (width - qsbMeasuredWidth) / 2;
}
int right = left + qsbMeasuredWidth;
- int bottom = b - t - dp.getQsbOffsetY();
+ int bottom = height - dp.getQsbOffsetY();
int top = bottom - dp.getHotseatProfile().getQsbHeight();
mQsb.layout(left, top, right, bottom);
}
@@ -454,9 +612,25 @@ public class Hotseat extends CellLayout implements Insettable {
return mQsb;
}
+ /** Delegates to the current page's shortcuts container. */
+ @Nullable
+ public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
+ CellLayout page = getCurrentPageLayout();
+ return page != null ? page.getShortcutsAndWidgets() : null;
+ }
+
+ /** Delegates accessibility drag helper to the current page. */
+ @Nullable
+ public DragAndDropAccessibilityDelegate getDragAndDropAccessibilityDelegate() {
+ CellLayout page = getCurrentPageLayout();
+ return page != null ? page.getDragAndDropAccessibilityDelegate() : null;
+ }
+
/** Dumps the Hotseat internal state */
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "Hotseat:");
+ writer.println(prefix + "\tpages: " + mPagedView.getPageCount()
+ + " pagingEnabled=" + mPagedView.isPagingEnabled());
mIconsAlphaChannels.dump(
prefix + "\t",
writer,
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 02a6572575..e7340488eb 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -748,7 +748,8 @@ public class Launcher extends StatefulActivity
mCellPosMapper = new TwoPanelCellPosMapper(mDeviceProfile.inv.numColumns);
} else {
mCellPosMapper = new CellPosMapper(mDeviceProfile.isVerticalBarLayout(),
- mDeviceProfile.numShownHotseatIcons);
+ mDeviceProfile.numShownHotseatIcons, mDeviceProfile.numHotseatRows,
+ mDeviceProfile.numHotseatPages);
}
mModelWriter = mModel.getWriter(true, mCellPosMapper, this);
updateFixedLandscape();
@@ -2151,7 +2152,7 @@ public class Launcher extends StatefulActivity
boolean isHotseatLayout(View layout) {
// TODO: Remove this method
- return mHotseat != null && (layout == mHotseat);
+ return mHotseat != null && (layout == mHotseat || mHotseat.isHotseatPage(layout));
}
@Override
@@ -2371,7 +2372,13 @@ public class Launcher extends StatefulActivity
}
List containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
- containers.add(mWorkspace.getHotseat());
+ Hotseat hotseat = mWorkspace.getHotseat();
+ if (hotseat != null) {
+ CellLayout currentPage = hotseat.getCurrentPageLayout();
+ if (currentPage != null) {
+ containers.add(currentPage);
+ }
+ }
mWorkspace.forEachVisiblePage(page -> containers.add((CellLayout) page));
CellLayout[] containerArray = containers.toArray(new CellLayout[0]);
LauncherBindableItemsContainer visibleContainer =
@@ -2501,11 +2508,18 @@ public class Launcher extends StatefulActivity
writer.println(prefix + " Hotseat");
mHotseat.dump(prefix, writer);
- ViewGroup layout = mHotseat.getShortcutsAndWidgets();
- for (int j = 0; j < layout.getChildCount(); j++) {
- Object tag = layout.getChildAt(j).getTag();
- if (tag != null) {
- writer.println(prefix + " " + tag);
+ CellLayout[] hotseatPages = mHotseat.getPageLayouts();
+ for (int p = 0; p < hotseatPages.length; p++) {
+ ViewGroup layout = hotseatPages[p].getShortcutsAndWidgets();
+ if (layout == null) {
+ continue;
+ }
+ writer.println(prefix + " Dock page " + p);
+ for (int j = 0; j < layout.getChildCount(); j++) {
+ Object tag = layout.getChildAt(j).getTag();
+ if (tag != null) {
+ writer.println(prefix + " " + tag);
+ }
}
}
}
@@ -2928,8 +2942,15 @@ public class Launcher extends StatefulActivity
* @param screenId must be presenterPos and not modelPos.
*/
public CellLayout getCellLayout(int container, int screenId) {
- return (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
- ? mHotseat : mWorkspace.getScreenWithId(screenId);
+ if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ || container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ if (mHotseat == null) {
+ return null;
+ }
+ CellLayout page = mHotseat.getPageAt(screenId);
+ return page != null ? page : mHotseat.getCurrentPageLayout();
+ }
+ return mWorkspace.getScreenWithId(screenId);
}
@Override
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index b587d4d57e..6620f052eb 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -145,6 +145,7 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import app.lawnchair.hotseat.HotseatPagedView;
import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
import static app.lawnchair.util.LawnchairUtilsKt.toBitmap;
import app.lawnchair.LawnchairApp;
@@ -765,7 +766,8 @@ public class Workspace extends PagedView
int dragSourceChildCount = mDragSourceInternal.getChildCount();
// If the icon was dragged from Hotseat, there is no page pair
- if (isTwoPanelEnabled() && !(mDragSourceInternal.getParent() instanceof Hotseat)) {
+ if (isTwoPanelEnabled() && !mLauncher.isHotseatLayout(
+ (View) mDragSourceInternal.getParent())) {
int pagePairScreenId = getScreenPair(getCellPosMapper().mapModelToPresenter(
dragObject.dragInfo).screenId);
CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId);
@@ -1017,6 +1019,10 @@ public class Workspace extends PagedView
if (index != -1) {
return mWorkspaceScreens.keyAt(index);
}
+ Hotseat hotseat = mLauncher.getHotseat();
+ if (hotseat != null && hotseat.isHotseatPage(layout)) {
+ return layout.getHotseatPageIndex();
+ }
return -1;
}
@@ -1859,7 +1865,12 @@ public class Workspace extends PagedView
protected void enableAccessibleDrag(boolean enable,
@Nullable DragObject dragObject) {
super.enableAccessibleDrag(enable, dragObject);
- setEnableForLayout(mLauncher.getHotseat(), enable);
+ Hotseat hotseat = mLauncher.getHotseat();
+ if (hotseat != null) {
+ for (CellLayout page : hotseat.getPageLayouts()) {
+ setEnableForLayout(page, enable);
+ }
+ }
if (enable && dragObject != null
&& dragObject.dragInfo instanceof LauncherAppWidgetInfo) {
mLauncher.getHotseat().setImportantForAccessibility(
@@ -2787,7 +2798,7 @@ public class Workspace extends PagedView
private boolean setDropLayoutForDragObject(DragObject d, float centerX, float centerY) {
CellLayout layout = null;
if (shouldUseHotseatAsDropLayout(d)) {
- layout = mLauncher.getHotseat();
+ layout = resolveHotseatDropLayout(d);
} else if (!isDragObjectOverSmartSpace(d)) {
// If the object is over qsb/smartspace, we don't want to highlight anything.
@@ -2813,14 +2824,35 @@ public class Workspace extends PagedView
return false;
}
+ @Nullable
+ private CellLayout resolveHotseatDropLayout(DragObject dragObject) {
+ Hotseat hotseat = mLauncher.getHotseat();
+ if (hotseat == null) {
+ return null;
+ }
+ // Prefer the visible dock page so drops land where the user is looking.
+ CellLayout current = hotseat.getCurrentPageLayout();
+ HotseatPagedView pager = hotseat.getPagedView();
+ if (pager != null && pager.isPageInTransition()) {
+ float[] xy = new float[] { dragObject.x, dragObject.y };
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
+ mLauncher.getDragLayer().mapCoordInSelfToDescendant(pager, xy);
+ CellLayout underFinger = pager.findPageAtLocalX(xy[0]);
+ if (underFinger != null) {
+ return underFinger;
+ }
+ }
+ return current;
+ }
+
private boolean shouldUseHotseatAsDropLayout(DragObject dragObject) {
if (mLauncher.getHotseat() == null
|| mLauncher.getHotseat().getShortcutsAndWidgets() == null
|| isDragWidget(dragObject)) {
return false;
}
- View hotseatShortcuts = mLauncher.getHotseat().getShortcutsAndWidgets();
- getViewBoundsRelativeToWorkspace(hotseatShortcuts, mTempRect);
+ View hotseatIcons = mLauncher.getHotseat().getPagedView();
+ getViewBoundsRelativeToWorkspace(hotseatIcons, mTempRect);
return mTempRect.contains(dragObject.x, dragObject.y);
}
@@ -3710,16 +3742,14 @@ public class Workspace extends PagedView
*/
private CellLayout[] getWorkspaceAndHotseatCellLayouts() {
int screenCount = getChildCount();
- final CellLayout[] layouts;
- if (mLauncher.getHotseat() != null) {
- layouts = new CellLayout[screenCount + 1];
- layouts[screenCount] = mLauncher.getHotseat();
- } else {
- layouts = new CellLayout[screenCount];
- }
+ CellLayout[] hotseatPages = mLauncher.getHotseat() != null
+ ? mLauncher.getHotseat().getPageLayouts()
+ : new CellLayout[0];
+ final CellLayout[] layouts = new CellLayout[screenCount + hotseatPages.length];
for (int screen = 0; screen < screenCount; screen++) {
layouts[screen] = (CellLayout) getChildAt(screen);
}
+ System.arraycopy(hotseatPages, 0, layouts, screenCount, hotseatPages.length);
return layouts;
}
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index 226aeceddf..7a40eee50f 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -54,16 +54,18 @@ public interface WorkspaceLayoutManager {
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(info);
int x = presenterPos.cellX;
int y = presenterPos.cellY;
+ int screenId = presenterPos.screenId;
if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|| info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
- int screenId = presenterPos.screenId;
- x = getHotseat().getCellXFromOrder(screenId);
- y = getHotseat().getCellYFromOrder(screenId);
- // TODO(b/335141365): Remove this log after the bug is fixed.
+ // Model screenId is a flat rank; presenter screenId is the dock page.
+ int rank = info.screenId;
+ x = getHotseat().getCellXFromOrder(rank);
+ y = getHotseat().getCellYFromOrder(rank);
+ screenId = getHotseat().getPageFromOrder(rank);
Log.d(TAG, "addInScreenFromBind: hotseat inflation with x = " + x
- + " and y = " + y);
+ + " and y = " + y + " page = " + screenId);
}
- addInScreen(child, info.container, presenterPos.screenId, x, y, info.spanX, info.spanY);
+ addInScreen(child, info.container, screenId, x, y, info.spanX, info.spanY);
}
/**
@@ -103,10 +105,18 @@ public interface WorkspaceLayoutManager {
throw new RuntimeException("Screen id should not be extra empty screen: " + screenId);
}
- final CellLayout layout;
+ CellLayout layout;
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|| container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
- layout = getHotseat();
+ Hotseat hotseat = getHotseat();
+ layout = hotseat.getPageAt(screenId);
+ if (layout == null) {
+ layout = hotseat.getCurrentPageLayout();
+ }
+ if (layout == null) {
+ Log.e(TAG, "Skipping child, hotseat page " + screenId + " not found");
+ return;
+ }
// Hide folder title in the hotseat
if (child instanceof FolderIcon) {
diff --git a/src/com/android/launcher3/celllayout/CellPosMapper.java b/src/com/android/launcher3/celllayout/CellPosMapper.java
index 2d9ac4da04..9441454d1d 100644
--- a/src/com/android/launcher3/celllayout/CellPosMapper.java
+++ b/src/com/android/launcher3/celllayout/CellPosMapper.java
@@ -12,12 +12,15 @@
* 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.
+ *
+ * Modifications copyright 2026 Lawnchair
*/
package com.android.launcher3.celllayout;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.data.ItemInfo;
import java.util.Objects;
@@ -27,19 +30,42 @@ import java.util.Objects;
*/
public class CellPosMapper {
- public static final CellPosMapper DEFAULT = new CellPosMapper(false, -1);
+ public static final CellPosMapper DEFAULT = new CellPosMapper(false, -1, 1, 1);
private final boolean mHasVerticalHotseat;
private final int mNumOfHotseat;
+ private final int mNumHotseatRows;
public CellPosMapper(boolean hasVerticalHotseat, int numOfHotseat) {
+ this(hasVerticalHotseat, numOfHotseat, 1, 1);
+ }
+
+ public CellPosMapper(boolean hasVerticalHotseat, int numOfHotseat, int numHotseatRows,
+ int numHotseatPages) {
mHasVerticalHotseat = hasVerticalHotseat;
mNumOfHotseat = numOfHotseat;
+ mNumHotseatRows = Math.max(1, numHotseatRows);
+ }
+
+ private static boolean isHotseatContainer(int container) {
+ return container == CONTAINER_HOTSEAT || container == CONTAINER_HOTSEAT_PREDICTION;
}
/**
- * Maps the position in model to the position in view
+ * Maps the position in model to the position in view.
+ * Hotseat model screenId is a flat rank; presenter screenId is the dock page index.
*/
public CellPos mapModelToPresenter(ItemInfo info) {
+ if (isHotseatContainer(info.container) && mNumOfHotseat > 0) {
+ if (mHasVerticalHotseat) {
+ return new CellPos(0, mNumOfHotseat - info.screenId - 1, 0);
+ }
+ int slotsPerPage = Math.max(1, mNumOfHotseat * mNumHotseatRows);
+ int page = info.screenId / slotsPerPage;
+ int local = info.screenId % slotsPerPage;
+ int cellX = local % mNumOfHotseat;
+ int cellY = local / mNumOfHotseat;
+ return new CellPos(cellX, cellY, page);
+ }
return new CellPos(info.cellX, info.cellY, info.screenId);
}
@@ -48,12 +74,17 @@ public class CellPosMapper {
*/
public CellPos mapPresenterToModel(int presenterX, int presenterY, int presenterScreen,
int container) {
- if (container == Favorites.CONTAINER_HOTSEAT) {
- if (mHasVerticalHotseat) {
+ if (isHotseatContainer(container)) {
+ if (mHasVerticalHotseat && mNumOfHotseat > 0) {
presenterScreen = mNumOfHotseat - presenterY - 1;
- } else {
- // For multi-row hotseat, screenId (rank) = row * numColumns + column
- presenterScreen = presenterY * mNumOfHotseat + presenterX;
+ } else if (!mHasVerticalHotseat && mNumOfHotseat > 0) {
+ // presenterScreen is the dock page index (0-based)
+ int page = Math.max(0, presenterScreen);
+ int slotsPerPage = Math.max(1, mNumOfHotseat * mNumHotseatRows);
+ int localRank = presenterY * mNumOfHotseat + presenterX;
+ presenterScreen = page * slotsPerPage + localRank;
+ } else if (!mHasVerticalHotseat) {
+ presenterScreen = presenterX;
}
}
return new CellPos(presenterX, presenterY, presenterScreen);
@@ -67,7 +98,7 @@ public class CellPosMapper {
private final int mColumnCount;
public TwoPanelCellPosMapper(int columnCount) {
- super(false, -1);
+ super(false, -1, 1, 1);
mColumnCount = columnCount;
}
diff --git a/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java b/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java
index 408a5a04a7..5233dd3df1 100644
--- a/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java
+++ b/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java
@@ -183,10 +183,16 @@ public class KeyboardDragAndDropView extends AbstractFloatingView
mDelegates.add(((CellLayout) pv.getChildAt(i)).getDragAndDropAccessibilityDelegate());
}
if (openFolder == null) {
- mDelegates.add(pv.getNextPage() + 1,
- mLauncher.getHotseat().getDragAndDropAccessibilityDelegate());
+ DragAndDropAccessibilityDelegate hotseatDelegate =
+ mLauncher.getHotseat().getDragAndDropAccessibilityDelegate();
+ if (hotseatDelegate != null) {
+ mDelegates.add(pv.getNextPage() + 1, hotseatDelegate);
+ }
}
mDelegates.forEach(delegate -> {
+ if (delegate == null) {
+ return;
+ }
mIntList.clear();
delegate.getVisibleVirtualViews(mIntList);
mIntList.forEach(id -> mNodes.add(new VirtualNodeInfo(delegate, id)));
diff --git a/src/com/android/launcher3/preview/LauncherPreviewRenderer.java b/src/com/android/launcher3/preview/LauncherPreviewRenderer.java
index 2839147e89..dc6d7f8f7d 100644
--- a/src/com/android/launcher3/preview/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/preview/LauncherPreviewRenderer.java
@@ -81,6 +81,7 @@ import com.android.launcher3.widget.LauncherWidgetHolder;
import app.lawnchair.preferences2.PreferenceCacheExtensionsKt;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -307,7 +308,10 @@ public class LauncherPreviewRenderer extends BaseContext
private List getAllLayouts() {
List screens = new ArrayList<>(mWorkspaceScreens.values());
- screens.add(getHotseat());
+ Hotseat hotseat = getHotseat();
+ if (hotseat != null) {
+ Collections.addAll(screens, hotseat.getPageLayouts());
+ }
return screens;
}
@@ -379,7 +383,8 @@ public class LauncherPreviewRenderer extends BaseContext
int cellX = mHotseat.getCellXFromOrder(rank);
int cellY = mHotseat.getCellYFromOrder(rank);
- if (mHotseat.isOccupied(cellX, cellY)) continue;
+ CellLayout page = mHotseat.getPageAt(mHotseat.getPageFromOrder(rank));
+ if (page == null || page.isOccupied(cellX, cellY)) continue;
WorkspaceItemInfo itemInfo =
new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(predictionIndex));
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index f6590464bd..494923c68b 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -297,8 +297,12 @@ public class TestInformationHandler implements ResourceBasedOverride {
int cellIndex = extra.getInt(TestProtocol.TEST_INFO_PARAM_INDEX);
return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
final Hotseat hotseat = launcher.getHotseat();
+ final CellLayout page = hotseat.getCurrentPageLayout();
+ if (page == null) {
+ return new Point(0, 0);
+ }
final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
- hotseat, cellIndex, /* cellY= */ 0,
+ page, cellIndex, /* cellY= */ 0,
/* spanX= */ 1, /* spanY= */ 1);
// TODO(b/234322284): return the real center point.
return new Point(cellRect.left + (cellRect.right - cellRect.left) / 3,
@@ -428,6 +432,9 @@ public class TestInformationHandler implements ResourceBasedOverride {
ShortcutAndWidgetContainer hotseatIconsContainer =
l.getHotseat().getShortcutsAndWidgets();
ArrayList hotseatIconNames = new ArrayList<>();
+ if (hotseatIconsContainer == null) {
+ return hotseatIconNames;
+ }
for (int i = 0; i < hotseatIconsContainer.getChildCount(); i++) {
// Use unchecked cast to catch changes in hotseat layout
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 0c744962f2..26cd7829ef 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -554,7 +554,8 @@ public interface ActivityContext extends SavedStateRegistryOwner {
default CellPosMapper getCellPosMapper() {
DeviceProfile dp = getDeviceProfile();
- return new CellPosMapper(dp.isVerticalBarLayout(), dp.numShownHotseatIcons);
+ return new CellPosMapper(dp.isVerticalBarLayout(), dp.numShownHotseatIcons,
+ dp.numHotseatRows, dp.numHotseatPages);
}
/** Returns a writer for updating model properties */