Merge "Add hover states to IconView and TaskThumbnailView in Overview" into main
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index e4f7262..9d394a8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -108,6 +108,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
+import com.android.launcher3.taskbar.bubbles.BubbleCreator;
import com.android.launcher3.taskbar.bubbles.BubbleDismissController;
import com.android.launcher3.taskbar.bubbles.BubbleDragController;
import com.android.launcher3.taskbar.bubbles.BubblePinController;
@@ -296,7 +297,8 @@
new BubbleBarPinController(this, mDragLayer,
() -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
new BubblePinController(this, mDragLayer,
- () -> DisplayController.INSTANCE.get(this).getInfo().currentSize)
+ () -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
+ new BubbleCreator(this)
));
}
@@ -1134,6 +1136,9 @@
* window.
*/
public void setTaskbarWindowFocusable(boolean focusable) {
+ if (isPhoneMode()) {
+ return;
+ }
if (focusable) {
mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
} else {
@@ -1146,7 +1151,7 @@
* Applies forcibly show flag to taskbar window iff transient taskbar is unstashed.
*/
public void applyForciblyShownFlagWhileTransientTaskbarUnstashed(boolean shouldForceShow) {
- if (!DisplayController.isTransientTaskbar(this)) {
+ if (!DisplayController.isTransientTaskbar(this) || isPhoneMode()) {
return;
}
if (shouldForceShow) {
@@ -1689,7 +1694,7 @@
* @param exclude {@code true} then the magnification region computation will omit the window.
*/
public void excludeFromMagnificationRegion(boolean exclude) {
- if (mIsExcludeFromMagnificationRegion == exclude) {
+ if (mIsExcludeFromMagnificationRegion == exclude || isPhoneMode()) {
return;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 20ab32e..e6b3acd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -470,7 +470,8 @@
// We're changing state to home, should close open popups e.g. Taskbar AllApps
handleOpenFloatingViews = true;
}
- if (mLauncherState == LauncherState.OVERVIEW) {
+ if (mLauncherState == LauncherState.OVERVIEW
+ && !mControllers.taskbarActivityContext.isPhoneMode()) {
// Calling to update the insets in TaskbarInsetController#updateInsetsTouchability
mControllers.taskbarActivityContext.notifyUpdateLayoutParams();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 58cd042..33d8a84 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -15,13 +15,8 @@
*/
package com.android.launcher3.taskbar.bubbles;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSONS_DATA;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
-import static com.android.launcher3.icons.FastBitmapDrawable.WHITE_SCRIM_ALPHA;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
@@ -34,35 +29,12 @@
import android.annotation.BinderThread;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.content.pm.ShortcutInfo;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Path;
import android.graphics.Point;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
import android.os.Bundle;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.PathParser;
-import android.view.LayoutInflater;
-import androidx.appcompat.content.res.AppCompatResources;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.launcher3.R;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.BubbleIconFactory;
-import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.quickstep.SystemUiProxy;
@@ -136,18 +108,16 @@
private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor(
new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND));
- private final LauncherApps mLauncherApps;
- private final BubbleIconFactory mIconFactory;
private final SystemUiProxy mSystemUiProxy;
private BubbleBarItem mSelectedBubble;
- private BubbleBarOverflow mOverflowBubble;
private ImeVisibilityChecker mImeVisibilityChecker;
private BubbleBarViewController mBubbleBarViewController;
private BubbleStashController mBubbleStashController;
private Optional<BubbleStashedHandleViewController> mBubbleStashedHandleViewController;
private BubblePinController mBubblePinController;
+ private BubbleCreator mBubbleCreator;
// Cache last sent top coordinate to avoid sending duplicate updates to shell
private int mLastSentBubbleBarTop;
@@ -168,6 +138,8 @@
List<RemovedBubble> removedBubbles;
List<String> bubbleKeysInOrder;
Point expandedViewDropTargetSize;
+ boolean showOverflow;
+ boolean showOverflowChanged;
// These need to be loaded in the background
BubbleBarBubble addedBubble;
@@ -186,6 +158,8 @@
removedBubbles = update.removedBubbles;
bubbleKeysInOrder = update.bubbleKeysInOrder;
expandedViewDropTargetSize = update.expandedViewDropTargetSize;
+ showOverflow = update.showOverflow;
+ showOverflowChanged = update.showOverflowChanged;
}
}
@@ -198,13 +172,6 @@
if (sBubbleBarEnabled) {
mSystemUiProxy.setBubblesListener(this);
}
- mLauncherApps = context.getSystemService(LauncherApps.class);
- mIconFactory = new BubbleIconFactory(context,
- context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size),
- context.getResources().getDimensionPixelSize(R.dimen.bubblebar_badge_size),
- context.getResources().getColor(R.color.important_conversation),
- context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width));
}
public void onDestroy() {
@@ -219,6 +186,7 @@
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
mBubblePinController = bubbleControllers.bubblePinController;
+ mBubbleCreator = bubbleControllers.bubbleCreator;
bubbleControllers.runAfterInit(() -> {
mBubbleBarViewController.setHiddenForBubbles(
@@ -233,27 +201,6 @@
}
/**
- * Creates and adds the overflow bubble to the bubble bar if it hasn't been created yet.
- *
- * <p>This should be called on the {@link #BUBBLE_STATE_EXECUTOR} executor to avoid inflating
- * the overflow multiple times.
- */
- private void createAndAddOverflowIfNeeded() {
- if (mOverflowBubble == null) {
- BubbleBarOverflow overflow = createOverflow(mContext);
- MAIN_EXECUTOR.execute(() -> {
- // we're on the main executor now, so check that the overflow hasn't been created
- // again to avoid races.
- if (mOverflowBubble == null) {
- mBubbleBarViewController.addBubble(
- overflow, /* isExpanding= */ false, /* suppressAnimation= */ true);
- mOverflowBubble = overflow;
- }
- });
- }
- }
-
- /**
* Updates the bubble bar, handle bar, and stash controllers based on sysui state flags.
*/
public void updateStateForSysuiFlags(@SystemUiStateFlags long flags) {
@@ -283,23 +230,25 @@
|| !update.currentBubbleList.isEmpty()) {
// We have bubbles to load
BUBBLE_STATE_EXECUTOR.execute(() -> {
- createAndAddOverflowIfNeeded();
if (update.addedBubble != null) {
- viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView,
+ viewUpdate.addedBubble = mBubbleCreator.populateBubble(mContext,
+ update.addedBubble,
+ mBarView,
null /* existingBubble */);
}
if (update.updatedBubble != null) {
BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey());
viewUpdate.updatedBubble =
- populateBubble(mContext, update.updatedBubble, mBarView,
+ mBubbleCreator.populateBubble(mContext, update.updatedBubble,
+ mBarView,
existingBubble);
}
if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
List<BubbleBarBubble> currentBubbles = new ArrayList<>();
for (int i = 0; i < update.currentBubbleList.size(); i++) {
- BubbleBarBubble b =
- populateBubble(mContext, update.currentBubbleList.get(i), mBarView,
- null /* existingBubble */);
+ BubbleBarBubble b = mBubbleCreator.populateBubble(mContext,
+ update.currentBubbleList.get(i), mBarView,
+ null /* existingBubble */);
currentBubbles.add(b);
}
viewUpdate.currentBubbles = currentBubbles;
@@ -326,7 +275,13 @@
BubbleBarBubble bubbleToSelect = null;
- if (update.addedBubble != null && update.removedBubbles.size() == 1) {
+ if (Flags.enableOptionalBubbleOverflow()
+ && update.showOverflowChanged && !update.showOverflow && update.addedBubble != null
+ && update.removedBubbles.isEmpty()) {
+ // A bubble was added from the overflow (& now it's empty / not showing)
+ mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
+ mBubbleBarViewController.removeOverflowAndAddBubble(update.addedBubble);
+ } else if (update.addedBubble != null && update.removedBubbles.size() == 1) {
// we're adding and removing a bubble at the same time. handle this as a single update.
RemovedBubble removedBubble = update.removedBubbles.get(0);
BubbleBarBubble bubbleToRemove = mBubbles.remove(removedBubble.getKey());
@@ -340,11 +295,17 @@
Log.w(TAG, "trying to remove bubble that doesn't exist: " + removedBubble.getKey());
}
} else {
+ boolean overflowNeedsToBeAdded = Flags.enableOptionalBubbleOverflow()
+ && update.showOverflowChanged && update.showOverflow;
if (!update.removedBubbles.isEmpty()) {
for (int i = 0; i < update.removedBubbles.size(); i++) {
RemovedBubble removedBubble = update.removedBubbles.get(i);
BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey());
- if (bubble != null) {
+ if (bubble != null && overflowNeedsToBeAdded) {
+ // First removal, show the overflow
+ overflowNeedsToBeAdded = false;
+ mBubbleBarViewController.addOverflowAndRemoveBubble(bubble);
+ } else if (bubble != null) {
mBubbleBarViewController.removeBubble(bubble);
} else {
Log.w(TAG, "trying to remove bubble that doesn't exist: "
@@ -357,6 +318,11 @@
mBubbleBarViewController.addBubble(update.addedBubble, isExpanding,
suppressAnimation);
}
+ if (Flags.enableOptionalBubbleOverflow()
+ && update.showOverflowChanged
+ && update.showOverflow != mBubbleBarViewController.isOverflowAdded()) {
+ mBubbleBarViewController.showOverflow(update.showOverflow);
+ }
}
// if a bubble was updated upstream, but removed before the update was received, add it back
@@ -388,6 +354,9 @@
}
}
}
+ if (Flags.enableOptionalBubbleOverflow() && update.initialState && update.showOverflow) {
+ mBubbleBarViewController.showOverflow(true);
+ }
// Adds and removals have happened, update visibility before any other visual changes.
mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
@@ -530,133 +499,6 @@
// Loading data for the bubbles
//
- @Nullable
- private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv,
- @Nullable BubbleBarBubble existingBubble) {
- String appName;
- Bitmap badgeBitmap;
- Bitmap bubbleBitmap;
- Path dotPath;
- int dotColor;
-
- boolean isImportantConvo = b.isImportantConversation();
-
- ShortcutRequest.QueryResult result = new ShortcutRequest(context,
- new UserHandle(b.getUserId()))
- .forPackage(b.getPackageName(), b.getShortcutId())
- .query(FLAG_MATCH_DYNAMIC
- | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
- | FLAG_MATCH_CACHED
- | FLAG_GET_PERSONS_DATA);
-
- ShortcutInfo shortcutInfo = result.size() > 0 ? result.get(0) : null;
- if (shortcutInfo == null) {
- Log.w(TAG, "No shortcutInfo found for bubble: " + b.getKey()
- + " with shortcutId: " + b.getShortcutId());
- }
-
- ApplicationInfo appInfo;
- try {
- appInfo = mLauncherApps.getApplicationInfo(
- b.getPackageName(),
- 0,
- new UserHandle(b.getUserId()));
- } catch (PackageManager.NameNotFoundException e) {
- // If we can't find package... don't think we should show the bubble.
- Log.w(TAG, "Unable to find packageName: " + b.getPackageName());
- return null;
- }
- if (appInfo == null) {
- Log.w(TAG, "Unable to find appInfo: " + b.getPackageName());
- return null;
- }
- PackageManager pm = context.getPackageManager();
- appName = String.valueOf(appInfo.loadLabel(pm));
- Drawable appIcon = appInfo.loadUnbadgedIcon(pm);
- Drawable badgedIcon = pm.getUserBadgedIcon(appIcon, new UserHandle(b.getUserId()));
-
- // Badged bubble image
- Drawable bubbleDrawable = mIconFactory.getBubbleDrawable(context, shortcutInfo,
- b.getIcon());
- if (bubbleDrawable == null) {
- // Default to app icon
- bubbleDrawable = appIcon;
- }
-
- BitmapInfo badgeBitmapInfo = mIconFactory.getBadgeBitmap(badgedIcon, isImportantConvo);
- badgeBitmap = badgeBitmapInfo.icon;
-
- float[] bubbleBitmapScale = new float[1];
- bubbleBitmap = mIconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
-
- // Dot color & placement
- Path iconPath = PathParser.createPathFromPathData(
- context.getResources().getString(
- com.android.internal.R.string.config_icon_mask));
- Matrix matrix = new Matrix();
- float scale = bubbleBitmapScale[0];
- float radius = BubbleView.DEFAULT_PATH_SIZE / 2f;
- matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
- radius /* pivot y */);
- iconPath.transform(matrix);
- dotPath = iconPath;
- dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
- Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
-
- if (existingBubble == null) {
- LayoutInflater inflater = LayoutInflater.from(context);
- BubbleView bubbleView = (BubbleView) inflater.inflate(
- R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
-
- BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
- badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
- bubbleView.setBubble(bubble);
- return bubble;
- } else {
- // If we already have a bubble (so it already has an inflated view), update it.
- existingBubble.setInfo(b);
- existingBubble.setBadge(badgeBitmap);
- existingBubble.setIcon(bubbleBitmap);
- existingBubble.setDotColor(dotColor);
- existingBubble.setDotPath(dotPath);
- existingBubble.setAppName(appName);
- return existingBubble;
- }
- }
-
- private BubbleBarOverflow createOverflow(Context context) {
- Bitmap bitmap = createOverflowBitmap(context);
- LayoutInflater inflater = LayoutInflater.from(context);
- BubbleView bubbleView = (BubbleView) inflater.inflate(
- R.layout.bubble_bar_overflow_button, mBarView, false /* attachToRoot */);
- BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView);
- bubbleView.setOverflow(overflow, bitmap);
- return overflow;
- }
-
- private Bitmap createOverflowBitmap(Context context) {
- Drawable iconDrawable = AppCompatResources.getDrawable(mContext,
- R.drawable.bubble_ic_overflow_button);
-
- final TypedArray ta = mContext.obtainStyledAttributes(
- new int[]{
- R.attr.materialColorOnPrimaryFixed,
- R.attr.materialColorPrimaryFixed
- });
- int overflowIconColor = ta.getColor(0, Color.WHITE);
- int overflowBackgroundColor = ta.getColor(1, Color.BLACK);
- ta.recycle();
-
- iconDrawable.setTint(overflowIconColor);
-
- int inset = context.getResources().getDimensionPixelSize(R.dimen.bubblebar_overflow_inset);
- Drawable foreground = new InsetDrawable(iconDrawable, inset);
- Drawable drawable = new AdaptiveIconDrawable(new ColorDrawable(overflowBackgroundColor),
- foreground);
-
- return mIconFactory.createBadgedIconBitmap(drawable).icon;
- }
-
private void onBubbleBarBoundsChanged() {
int newTop = mBarView.getRestingTopPositionOnScreen();
if (newTop != mLastSentBubbleBarTop) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 7d27a90..32ca9f2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -715,11 +715,13 @@
public void addBubble(BubbleView bubble) {
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
Gravity.LEFT);
+ final int index = bubble.isOverflow() ? getChildCount() : 0;
+
if (isExpanded()) {
// if we're expanded scale the new bubble in
bubble.setScaleX(0f);
bubble.setScaleY(0f);
- addView(bubble, 0, lp);
+ addView(bubble, index, lp);
bubble.showDotIfNeeded(/* animate= */ false);
mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing,
@@ -748,23 +750,33 @@
};
mBubbleAnimator.animateNewBubble(indexOfChild(mSelectedBubbleView), listener);
} else {
- addView(bubble, 0, lp);
+ addView(bubble, index, lp);
}
}
/** Add a new bubble and remove an old bubble from the bubble bar. */
- public void addBubbleAndRemoveBubble(View addedBubble, View removedBubble) {
+ public void addBubbleAndRemoveBubble(BubbleView addedBubble, BubbleView removedBubble) {
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
Gravity.LEFT);
+ boolean isOverflowSelected = mSelectedBubbleView.isOverflow();
+ boolean removingOverflow = removedBubble.isOverflow();
+ boolean addingOverflow = addedBubble.isOverflow();
+
if (!isExpanded()) {
removeView(removedBubble);
- addView(addedBubble, 0, lp);
+ int index = addingOverflow ? getChildCount() : 0;
+ addView(addedBubble, index, lp);
return;
}
+ int index = addingOverflow ? getChildCount() : 0;
addedBubble.setScaleX(0f);
addedBubble.setScaleY(0f);
- addView(addedBubble, 0, lp);
+ addView(addedBubble, index, lp);
+ if (isOverflowSelected && removingOverflow) {
+ // The added bubble will be selected
+ mSelectedBubbleView = addedBubble;
+ }
int indexOfSelectedBubble = indexOfChild(mSelectedBubbleView);
int indexOfBubbleToRemove = indexOfChild(removedBubble);
@@ -924,7 +936,7 @@
final float currentWidth = getWidth();
final float expandedWidth = expandedWidth();
final float collapsedWidth = collapsedWidth();
- int bubbleCount = getChildCount();
+ int childCount = getChildCount();
float viewBottom = mBubbleBarBounds.height() + (isExpanded() ? mPointerSize : 0);
float bubbleBarAnimatedTop = viewBottom - getBubbleBarHeight();
// When translating X & Y the scale is ignored, so need to deduct it from the translations
@@ -932,7 +944,7 @@
final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl());
// elevation state is opposite to widthState - when expanded all icons are flat
float elevationState = (1 - widthState);
- for (int i = 0; i < bubbleCount; i++) {
+ for (int i = 0; i < childCount; i++) {
BubbleView bv = (BubbleView) getChildAt(i);
if (bv == mDraggedBubbleView || bv == mDismissedByDragBubbleView) {
// Skip the dragged bubble. Its translation is managed by the drag controller.
@@ -951,9 +963,9 @@
bv.setTranslationY(ty);
// the position of the bubble when the bar is fully expanded
- final float expandedX = getExpandedBubbleTranslationX(i, bubbleCount, onLeft);
+ final float expandedX = getExpandedBubbleTranslationX(i, childCount, onLeft);
// the position of the bubble when the bar is fully collapsed
- final float collapsedX = getCollapsedBubbleTranslationX(i, bubbleCount, onLeft);
+ final float collapsedX = getCollapsedBubbleTranslationX(i, childCount, onLeft);
// slowly animate elevation while keeping correct Z ordering
float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i;
@@ -981,13 +993,10 @@
final float collapsedBarShift = onLeft ? 0 : currentWidth - collapsedWidth;
final float targetX = collapsedX + collapsedBarShift;
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
- // If we're fully collapsed, hide all bubbles except for the first 2. If there are
- // only 2 bubbles, hide the second bubble as well because it's the overflow.
+ // If we're fully collapsed, hide all bubbles except for the first 2, excluding
+ // the overflow.
if (widthState == 0) {
- if (i > MAX_VISIBLE_BUBBLES_COLLAPSED - 1) {
- bv.setAlpha(0);
- } else if (i == MAX_VISIBLE_BUBBLES_COLLAPSED - 1
- && bubbleCount == MAX_VISIBLE_BUBBLES_COLLAPSED) {
+ if (bv.isOverflow() || i > MAX_VISIBLE_BUBBLES_COLLAPSED - 1) {
bv.setAlpha(0);
} else {
bv.setAlpha(1);
@@ -1043,22 +1052,26 @@
return translationX - getScaleIconShift();
}
- private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount, boolean onLeft) {
- if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
+ private float getCollapsedBubbleTranslationX(int bubbleIndex, int childCount, boolean onLeft) {
+ if (bubbleIndex < 0 || bubbleIndex >= childCount) {
return 0;
}
float translationX;
if (onLeft) {
- // Shift the first bubble only if there are more bubbles in addition to overflow
- translationX = mBubbleBarPadding + (
- bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
- ? mIconOverlapAmount : 0);
+ // Shift the first bubble only if there are more bubbles
+ if (bubbleIndex == 0 && getBubbleChildCount() >= MAX_VISIBLE_BUBBLES_COLLAPSED) {
+ translationX = mIconOverlapAmount;
+ } else {
+ translationX = 0f;
+ }
} else {
- translationX = mBubbleBarPadding + (
- bubbleIndex == 0 || bubbleCount <= MAX_VISIBLE_BUBBLES_COLLAPSED
- ? 0 : mIconOverlapAmount);
+ if (bubbleIndex == 1 && getBubbleChildCount() >= MAX_VISIBLE_BUBBLES_COLLAPSED) {
+ translationX = mIconOverlapAmount;
+ } else {
+ translationX = 0f;
+ }
}
- return translationX - getScaleIconShift();
+ return mBubbleBarPadding + translationX - getScaleIconShift();
}
/**
@@ -1256,15 +1269,20 @@
}
private float collapsedWidth() {
- final int childCount = getChildCount();
+ final int bubbleChildCount = getBubbleChildCount();
final float horizontalPadding = 2 * mBubbleBarPadding;
- // If there are more than 2 bubbles, the first 2 should be visible when collapsed.
- // Otherwise just the first bubble should be visible because we don't show the overflow.
- return childCount > MAX_VISIBLE_BUBBLES_COLLAPSED
+ // If there are more than 2 bubbles, the first 2 should be visible when collapsed,
+ // excluding the overflow.
+ return bubbleChildCount >= MAX_VISIBLE_BUBBLES_COLLAPSED
? getScaledIconSize() + mIconOverlapAmount + horizontalPadding
: getScaledIconSize() + horizontalPadding;
}
+ /** Returns the child count excluding the overflow if it's present. */
+ private int getBubbleChildCount() {
+ return hasOverflow() ? getChildCount() - 1 : getChildCount();
+ }
+
private float getBubbleBarExpandedHeight() {
return getBubbleBarCollapsedHeight() + mPointerSize;
}
@@ -1303,8 +1321,8 @@
return mIsAnimatingNewBubble;
}
- private boolean hasOverview() {
- // Overview is always the last bubble
+ private boolean hasOverflow() {
+ // Overflow is always the last bubble
View lastChild = getChildAt(getChildCount() - 1);
if (lastChild instanceof BubbleView bubbleView) {
return bubbleView.getBubble() instanceof BubbleBarOverflow;
@@ -1336,7 +1354,7 @@
CharSequence contentDesc = firstChild != null ? firstChild.getContentDescription() : "";
// Don't count overflow if it exists
- int bubbleCount = getChildCount() - (hasOverview() ? 1 : 0);
+ int bubbleCount = getChildCount() - (hasOverflow() ? 1 : 0);
if (bubbleCount > 1) {
contentDesc = getResources().getString(R.string.bubble_bar_description_multiple_bubbles,
contentDesc, bubbleCount - 1);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 916b7b1..2cdc0ce 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -75,6 +75,7 @@
private View.OnClickListener mBubbleClickListener;
private View.OnClickListener mBubbleBarClickListener;
private BubbleView.Controller mBubbleViewController;
+ private BubbleBarOverflow mOverflowBubble;
// These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing
private final MultiValueAlpha mBubbleBarAlpha;
@@ -91,6 +92,8 @@
private boolean mHiddenForNoBubbles = true;
private boolean mShouldShowEducation;
+ public boolean mOverflowAdded;
+
private BubbleBarViewAnimator mBubbleBarViewAnimator;
private final TimeSource mTimeSource = System::currentTimeMillis;
@@ -121,6 +124,7 @@
mBubbleClickListener = v -> onBubbleClicked((BubbleView) v);
mBubbleBarClickListener = v -> expandBubbleBar();
mBubbleDragController.setupBubbleBarView(mBarView);
+ mOverflowBubble = bubbleControllers.bubbleCreator.createOverflow(mBarView);
mBarView.setOnClickListener(mBubbleBarClickListener);
mBarView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
@@ -491,6 +495,46 @@
}
}
+ /** Whether the overflow view is added to the bubble bar. */
+ public boolean isOverflowAdded() {
+ return mOverflowAdded;
+ }
+
+ /** Shows or hides the overflow view. */
+ public void showOverflow(boolean showOverflow) {
+ if (mOverflowAdded == showOverflow) return;
+ mOverflowAdded = showOverflow;
+ if (mOverflowAdded) {
+ mBarView.addBubble(mOverflowBubble.getView());
+ mOverflowBubble.getView().setOnClickListener(mBubbleClickListener);
+ mOverflowBubble.getView().setController(mBubbleViewController);
+ } else {
+ mBarView.removeBubble(mOverflowBubble.getView());
+ mOverflowBubble.getView().setOnClickListener(null);
+ mOverflowBubble.getView().setController(null);
+ }
+ }
+
+ /** Adds the overflow view to the bubble bar while animating a view away. */
+ public void addOverflowAndRemoveBubble(BubbleBarBubble removedBubble) {
+ if (mOverflowAdded) return;
+ mOverflowAdded = true;
+ mBarView.addBubbleAndRemoveBubble(mOverflowBubble.getView(), removedBubble.getView());
+ mOverflowBubble.getView().setOnClickListener(mBubbleClickListener);
+ mOverflowBubble.getView().setController(mBubbleViewController);
+ removedBubble.getView().setController(null);
+ }
+
+ /** Removes the overflow view to the bubble bar while animating a view in. */
+ public void removeOverflowAndAddBubble(BubbleBarBubble addedBubble) {
+ if (!mOverflowAdded) return;
+ mOverflowAdded = false;
+ mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), mOverflowBubble.getView());
+ addedBubble.getView().setOnClickListener(mBubbleClickListener);
+ addedBubble.getView().setController(mBubbleViewController);
+ mOverflowBubble.getView().setController(null);
+ }
+
/**
* Adds the provided bubble to the bubble bar.
*/
@@ -501,10 +545,6 @@
mBubbleDragController.setupBubbleView(b.getView());
b.getView().setController(mBubbleViewController);
- if (b instanceof BubbleBarOverflow) {
- return;
- }
-
if (suppressAnimation || !(b instanceof BubbleBarBubble bubble)) {
// the bubble bar and handle are initialized as part of the first bubble animation.
// if the animation is suppressed, immediately stash or show the bubble bar to
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index a5243fa..8478dc2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -33,6 +33,7 @@
public final BubbleDismissController bubbleDismissController;
public final BubbleBarPinController bubbleBarPinController;
public final BubblePinController bubblePinController;
+ public final BubbleCreator bubbleCreator;
private final RunnableList mPostInitRunnables = new RunnableList();
@@ -49,7 +50,8 @@
BubbleDragController bubbleDragController,
BubbleDismissController bubbleDismissController,
BubbleBarPinController bubbleBarPinController,
- BubblePinController bubblePinController) {
+ BubblePinController bubblePinController,
+ BubbleCreator bubbleCreator) {
this.bubbleBarController = bubbleBarController;
this.bubbleBarViewController = bubbleBarViewController;
this.bubbleStashController = bubbleStashController;
@@ -58,6 +60,7 @@
this.bubbleDismissController = bubbleDismissController;
this.bubbleBarPinController = bubbleBarPinController;
this.bubblePinController = bubblePinController;
+ this.bubbleCreator = bubbleCreator;
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java
new file mode 100644
index 0000000..8e9a2f6
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles;
+
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSONS_DATA;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
+
+import static com.android.launcher3.icons.FastBitmapDrawable.WHITE_SCRIM_ALPHA;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.PathParser;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.appcompat.content.res.AppCompatResources;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.wm.shell.common.bubbles.BubbleInfo;
+
+/**
+ * Loads the necessary info to populate / present a bubble (name, icon, shortcut).
+ */
+public class BubbleCreator {
+
+ private static final String TAG = BubbleCreator.class.getSimpleName();
+
+ private final Context mContext;
+ private final LauncherApps mLauncherApps;
+ private final BubbleIconFactory mIconFactory;
+
+ public BubbleCreator(Context context) {
+ mContext = context;
+ mLauncherApps = mContext.getSystemService(LauncherApps.class);
+ mIconFactory = new BubbleIconFactory(context,
+ context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size),
+ context.getResources().getDimensionPixelSize(R.dimen.bubblebar_badge_size),
+ context.getResources().getColor(R.color.important_conversation),
+ context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
+ }
+
+ /**
+ * Creates a BubbleBarBubble object, including the view if needed, and populates it with
+ * the info needed for presentation.
+ *
+ * @param context the context to use for inflation.
+ * @param info the info to use to populate the bubble.
+ * @param barView the parent view for the bubble (bubble is not added to the view).
+ * @param existingBubble if a bubble exists already, this object gets updated with the new
+ * info & returned (& any existing views are reused instead of inflating
+ * new ones.
+ */
+ @Nullable
+ public BubbleBarBubble populateBubble(Context context, BubbleInfo info, ViewGroup barView,
+ @Nullable BubbleBarBubble existingBubble) {
+ String appName;
+ Bitmap badgeBitmap;
+ Bitmap bubbleBitmap;
+ Path dotPath;
+ int dotColor;
+
+ boolean isImportantConvo = info.isImportantConversation();
+
+ ShortcutRequest.QueryResult result = new ShortcutRequest(context,
+ new UserHandle(info.getUserId()))
+ .forPackage(info.getPackageName(), info.getShortcutId())
+ .query(FLAG_MATCH_DYNAMIC
+ | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
+ | FLAG_MATCH_CACHED
+ | FLAG_GET_PERSONS_DATA);
+
+ ShortcutInfo shortcutInfo = result.size() > 0 ? result.get(0) : null;
+ if (shortcutInfo == null) {
+ Log.w(TAG, "No shortcutInfo found for bubble: " + info.getKey()
+ + " with shortcutId: " + info.getShortcutId());
+ }
+
+ ApplicationInfo appInfo;
+ try {
+ appInfo = mLauncherApps.getApplicationInfo(
+ info.getPackageName(),
+ 0,
+ new UserHandle(info.getUserId()));
+ } catch (PackageManager.NameNotFoundException e) {
+ // If we can't find package... don't think we should show the bubble.
+ Log.w(TAG, "Unable to find packageName: " + info.getPackageName());
+ return null;
+ }
+ if (appInfo == null) {
+ Log.w(TAG, "Unable to find appInfo: " + info.getPackageName());
+ return null;
+ }
+ PackageManager pm = context.getPackageManager();
+ appName = String.valueOf(appInfo.loadLabel(pm));
+ Drawable appIcon = appInfo.loadUnbadgedIcon(pm);
+ Drawable badgedIcon = pm.getUserBadgedIcon(appIcon, new UserHandle(info.getUserId()));
+
+ // Badged bubble image
+ Drawable bubbleDrawable = mIconFactory.getBubbleDrawable(context, shortcutInfo,
+ info.getIcon());
+ if (bubbleDrawable == null) {
+ // Default to app icon
+ bubbleDrawable = appIcon;
+ }
+
+ BitmapInfo badgeBitmapInfo = mIconFactory.getBadgeBitmap(badgedIcon, isImportantConvo);
+ badgeBitmap = badgeBitmapInfo.icon;
+
+ float[] bubbleBitmapScale = new float[1];
+ bubbleBitmap = mIconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
+
+ // Dot color & placement
+ Path iconPath = PathParser.createPathFromPathData(
+ context.getResources().getString(
+ com.android.internal.R.string.config_icon_mask));
+ Matrix matrix = new Matrix();
+ float scale = bubbleBitmapScale[0];
+ float radius = BubbleView.DEFAULT_PATH_SIZE / 2f;
+ matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+ radius /* pivot y */);
+ iconPath.transform(matrix);
+ dotPath = iconPath;
+ dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
+ Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
+
+ if (existingBubble == null) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ BubbleView bubbleView = (BubbleView) inflater.inflate(
+ R.layout.bubblebar_item_view, barView, false /* attachToRoot */);
+
+ BubbleBarBubble bubble = new BubbleBarBubble(info, bubbleView,
+ badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
+ bubbleView.setBubble(bubble);
+ return bubble;
+ } else {
+ // If we already have a bubble (so it already has an inflated view), update it.
+ existingBubble.setInfo(info);
+ existingBubble.setBadge(badgeBitmap);
+ existingBubble.setIcon(bubbleBitmap);
+ existingBubble.setDotColor(dotColor);
+ existingBubble.setDotPath(dotPath);
+ existingBubble.setAppName(appName);
+ return existingBubble;
+ }
+ }
+
+ /**
+ * Creates the overflow view shown in the bubble bar.
+ *
+ * @param barView the parent view for the bubble (bubble is not added to the view).
+ */
+ public BubbleBarOverflow createOverflow(ViewGroup barView) {
+ Bitmap bitmap = createOverflowBitmap();
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ BubbleView bubbleView = (BubbleView) inflater.inflate(
+ R.layout.bubble_bar_overflow_button, barView, false /* attachToRoot */);
+ BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView);
+ bubbleView.setOverflow(overflow, bitmap);
+ return overflow;
+ }
+
+ private Bitmap createOverflowBitmap() {
+ Drawable iconDrawable = AppCompatResources.getDrawable(mContext,
+ R.drawable.bubble_ic_overflow_button);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[]{
+ R.attr.materialColorOnPrimaryFixed,
+ R.attr.materialColorPrimaryFixed
+ });
+ int overflowIconColor = ta.getColor(0, Color.WHITE);
+ int overflowBackgroundColor = ta.getColor(1, Color.BLACK);
+ ta.recycle();
+
+ iconDrawable.setTint(overflowIconColor);
+
+ int inset = mContext.getResources().getDimensionPixelSize(R.dimen.bubblebar_overflow_inset);
+ Drawable foreground = new InsetDrawable(iconDrawable, inset);
+ Drawable drawable = new AdaptiveIconDrawable(new ColorDrawable(overflowBackgroundColor),
+ foreground);
+
+ return mIconFactory.createBadgedIconBitmap(drawable).icon;
+ }
+
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 09da3e0..f0f2872 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -74,6 +74,7 @@
private boolean mOnLeft = false;
private BubbleBarItem mBubble;
+ private boolean mIsOverflow;
private Bitmap mIcon;
@@ -271,12 +272,18 @@
*/
public void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
mBubble = overflow;
+ mIsOverflow = true;
mIcon = bitmap;
updateBubbleIcon();
mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
setContentDescription(getResources().getString(R.string.bubble_bar_overflow_description));
}
+ /** Whether this view represents the overflow button. */
+ public boolean isOverflow() {
+ return mIsOverflow;
+ }
+
/** Returns the bubble being rendered in this view. */
@Nullable
public BubbleBarItem getBubble() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3c9bd0f..4dc04e7 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -989,7 +989,6 @@
dp = dp.copy(mContext);
}
dp.updateInsets(targets.homeContentInsets);
- dp.updateIsSeascape(mContext);
initTransitionEndpoints(dp);
orientationState.setMultiWindowMode(dp.isMultiWindowMode);
}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index f89888a..54653fa 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -782,7 +782,6 @@
(RelativeLayout.LayoutParams) mFakeHotseatView.getLayoutParams();
if (!mTutorialFragment.isLargeScreen()) {
DeviceProfile dp = mTutorialFragment.getDeviceProfile();
- dp.updateIsSeascape(mContext);
hotseatLayoutParams.addRule(dp.isLandscape
? (dp.isSeascape()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
index e583f63..0a3351d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar
+import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.Utilities
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
@@ -35,12 +36,14 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(LauncherMultivalentJUnit::class)
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+@Ignore
class TaskbarEduTooltipControllerTest {
private val context =
@@ -77,6 +80,7 @@
@Before
fun setUp() {
+ Log.e("Taskbar", "TaskbarEduTooltipControllerTest test started")
Utilities.disableRunningInTestHarnessForTests()
}
@@ -85,6 +89,7 @@
if (wasInTestHarness) {
Utilities.enableRunningInTestHarnessForTests()
}
+ Log.e("Taskbar", "TaskbarEduTooltipControllerTest test completed")
}
@Test
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 8585b66..177b28c 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -140,15 +140,11 @@
}
protected void onDeviceProfileInitiated() {
- if (mDeviceProfile.isVerticalBarLayout()) {
- mDeviceProfile.updateIsSeascape(this);
- }
}
@Override
public void onDisplayInfoChanged(Context context, Info info, int flags) {
if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) {
- mDeviceProfile.updateIsSeascape(this);
reapplyUi();
}
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 1a5f4b9..5cbf6fb 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -65,7 +65,6 @@
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType;
import com.android.launcher3.responsive.ResponsiveSpecsProvider;
import com.android.launcher3.util.CellContentDimensions;
-import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.IconSizeSteps;
import com.android.launcher3.util.ResourceHelper;
@@ -298,9 +297,6 @@
// the widgetView, such that the actual view size is same as the widget size.
public final Rect widgetPadding = new Rect();
- // When true, nav bar is on the left side of the screen.
- private boolean mIsSeascape;
-
// Notification dots
public final DotRenderer mDotRendererWorkSpace;
public final DotRenderer mDotRendererAllApps;
@@ -2007,25 +2003,8 @@
return isLandscape && transposeLayoutWithOrientation;
}
- /**
- * Updates orientation information and returns true if it has changed from the previous value.
- */
- public boolean updateIsSeascape(Context context) {
- if (isVerticalBarLayout()) {
- boolean isSeascape = DisplayController.INSTANCE.get(context)
- .getInfo().rotation == Surface.ROTATION_270;
- if (mIsSeascape != isSeascape) {
- mIsSeascape = isSeascape;
- // Hotseat changing sides requires updating workspace left/right paddings
- updateWorkspacePadding();
- return true;
- }
- }
- return false;
- }
-
public boolean isSeascape() {
- return isVerticalBarLayout() && mIsSeascape;
+ return rotationHint == Surface.ROTATION_270 && isVerticalBarLayout();
}
public boolean shouldFadeAdjacentWorkspaceScreens() {
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index 0a7beab..78ce3a2 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -62,6 +62,10 @@
fun preInflateAllAppsViewHolders(context: T) {
val appsView = context.appsView ?: return
val activeRv: RecyclerView = appsView.activeRecyclerView ?: return
+ val preInflateCount = getPreinflateCount(context)
+ if (preInflateCount <= 0) {
+ return
+ }
if (activeRv.layoutManager == null) {
if (BuildConfig.IS_STUDIO_BUILD) {
@@ -99,7 +103,12 @@
override fun getLayoutManager(): RecyclerView.LayoutManager? = null
}
- preInflateAllAppsViewHolders(adapter, BaseAllAppsAdapter.VIEW_TYPE_ICON, activeRv) {
+ preInflateAllAppsViewHolders(
+ adapter,
+ BaseAllAppsAdapter.VIEW_TYPE_ICON,
+ activeRv,
+ preInflateCount
+ ) {
getPreinflateCount(context)
}
}
@@ -109,10 +118,10 @@
adapter: RecyclerView.Adapter<*>,
viewType: Int,
parent: ViewGroup,
+ preInflationCount: Int,
preInflationCountProvider: () -> Int
) {
- val preinflationCount = preInflationCountProvider.invoke()
- if (preinflationCount <= 0) {
+ if (preInflationCount <= 0) {
return
}
mCancellableTask?.cancel()
@@ -121,7 +130,7 @@
CancellableTask(
{
val list: ArrayList<ViewHolder> = ArrayList()
- for (i in 0 until preinflationCount) {
+ for (i in 0 until preInflationCount) {
if (task?.canceled == true) {
break
}
@@ -132,8 +141,8 @@
MAIN_EXECUTOR,
{ viewHolders ->
// Run preInflationCountProvider again as the needed VH might have changed
- val newPreinflationCount = preInflationCountProvider.invoke()
- for (i in 0 until minOf(viewHolders.size, newPreinflationCount)) {
+ val newPreInflationCount = preInflationCountProvider.invoke()
+ for (i in 0 until minOf(viewHolders.size, newPreInflationCount)) {
putRecycledView(viewHolders[i])
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
index c32461e..a3c7f4f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
@@ -104,7 +104,7 @@
val cl = cellLayoutBuilder.createCellLayout(board.width, board.height, false)
// The views have to be sorted or the result can vary
board.icons
- .map(IconPoint::getCoord)
+ .map(IconPoint::coord)
.sortedWith(
Comparator.comparing { p: Any -> (p as Point).x }
.thenComparing { p: Any -> (p as Point).y }
@@ -120,9 +120,7 @@
)
}
board.widgets
- .sortedWith(
- Comparator.comparing(WidgetRect::getCellX).thenComparing(WidgetRect::getCellY)
- )
+ .sortedWith(Comparator.comparing(WidgetRect::cellX).thenComparing(WidgetRect::cellY))
.forEach { widget ->
addViewInCellLayout(
cl,
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/BoardClasses.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/BoardClasses.kt
new file mode 100644
index 0000000..3cbfc5a
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/BoardClasses.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.board
+
+import android.graphics.Point
+import android.graphics.Rect
+
+/** Represents a widget in a CellLayoutBoard */
+data class WidgetRect(
+ val type: Char,
+ val bounds: Rect,
+) {
+ val spanX: Int = bounds.right - bounds.left + 1
+ val spanY: Int = bounds.top - bounds.bottom + 1
+ val cellY: Int = bounds.bottom
+ val cellX: Int = bounds.left
+
+ fun shouldIgnore() = type == CellType.IGNORE
+
+ fun contains(x: Int, y: Int) = bounds.contains(x, y)
+}
+
+/**
+ * [A-Z]: Represents a folder and number of icons in the folder is represented by the order of
+ * letter in the alphabet, A=2, B=3, C=4 ... etc.
+ */
+data class FolderPoint(val coord: Point, val type: Char) {
+ val numberIconsInside: Int = type.code - 'A'.code + 2
+}
+
+/** Represents an icon in a CellLayoutBoard */
+data class IconPoint(val coord: Point, val type: Char = CellType.ICON)
+
+object CellType {
+ // The cells marked by this will be filled by 1x1 widgets and will be ignored when
+ // validating
+ const val IGNORE = 'x'
+
+ // The cells marked by this will be filled by app icons
+ const val ICON = 'i'
+
+ // The cells marked by FOLDER will be filled by folders with 27 app icons inside
+ const val FOLDER = 'Z'
+
+ // Empty space
+ const val EMPTY = '-'
+
+ // Widget that will be saved as "main widget" for easier retrieval
+ const val MAIN_WIDGET = 'm' // Everything else will be consider a widget
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
index e5ad888..04bfee9 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
@@ -88,7 +88,7 @@
public WidgetRect getWidgetOfType(char type) {
return mWidgetsRects.stream()
- .filter(widgetRect -> widgetRect.mType == type).findFirst().orElse(null);
+ .filter(widgetRect -> widgetRect.getType() == type).findFirst().orElse(null);
}
public WidgetRect getWidgetAt(int x, int y) {
@@ -117,8 +117,8 @@
}
private void removeWidgetFromBoard(WidgetRect widget) {
- for (int xi = widget.mBounds.left; xi <= widget.mBounds.right; xi++) {
- for (int yi = widget.mBounds.bottom; yi <= widget.mBounds.top; yi++) {
+ for (int xi = widget.getBounds().left; xi <= widget.getBounds().right; xi++) {
+ for (int yi = widget.getBounds().bottom; yi <= widget.getBounds().top; yi++) {
mWidget[xi][yi] = '-';
}
}
@@ -127,7 +127,7 @@
private void removeOverlappingItems(Rect rect) {
// Remove overlapping widgets and remove them from the board
mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
- if (rect.intersect(widget.mBounds)) {
+ if (rect.intersect(widget.getBounds())) {
removeWidgetFromBoard(widget);
return false;
}
@@ -135,8 +135,8 @@
}).collect(Collectors.toList());
// Remove overlapping icons and remove them from the board
mIconPoints = mIconPoints.stream().filter(iconPoint -> {
- int x = iconPoint.coord.x;
- int y = iconPoint.coord.y;
+ int x = iconPoint.getCoord().x;
+ int y = iconPoint.getCoord().y;
if (rect.contains(x, y)) {
mWidget[x][y] = '-';
return false;
@@ -146,8 +146,8 @@
// Remove overlapping folders and remove them from the board
mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
- int x = folderPoint.coord.x;
- int y = folderPoint.coord.y;
+ int x = folderPoint.getCoord().x;
+ int y = folderPoint.getCoord().y;
if (rect.contains(x, y)) {
mWidget[x][y] = '-';
return false;
@@ -159,7 +159,7 @@
private void removeOverlappingItems(Point p) {
// Remove overlapping widgets and remove them from the board
mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
- if (IdenticalBoardComparator.Companion.touchesPoint(widget.mBounds, p)) {
+ if (IdenticalBoardComparator.Companion.touchesPoint(widget.getBounds(), p)) {
removeWidgetFromBoard(widget);
return false;
}
@@ -167,8 +167,8 @@
}).collect(Collectors.toList());
// Remove overlapping icons and remove them from the board
mIconPoints = mIconPoints.stream().filter(iconPoint -> {
- int x = iconPoint.coord.x;
- int y = iconPoint.coord.y;
+ int x = iconPoint.getCoord().x;
+ int y = iconPoint.getCoord().y;
if (p.x == x && p.y == y) {
mWidget[x][y] = '-';
return false;
@@ -178,8 +178,8 @@
// Remove overlapping folders and remove them from the board
mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
- int x = folderPoint.coord.x;
- int y = folderPoint.coord.y;
+ int x = folderPoint.getCoord().x;
+ int y = folderPoint.getCoord().y;
if (p.x == x && p.y == y) {
mWidget[x][y] = '-';
return false;
@@ -226,7 +226,7 @@
public void removeItem(char type) {
mWidgetsRects.stream()
- .filter(widgetRect -> widgetRect.mType == type)
+ .filter(widgetRect -> widgetRect.getType() == type)
.forEach(widgetRect -> removeOverlappingItems(
new Point(widgetRect.getCellX(), widgetRect.getCellY())));
}
@@ -365,10 +365,10 @@
board.mWidth = lines[0].length();
board.mWidgetsRects = getRects(board.mWidget);
board.mWidgetsRects.forEach(widgetRect -> {
- if (widgetRect.mType == CellType.MAIN_WIDGET) {
+ if (widgetRect.getType() == CellType.MAIN_WIDGET) {
board.mMain = widgetRect;
}
- board.mWidgetsMap.put(widgetRect.mType, widgetRect);
+ board.mWidgetsMap.put(widgetRect.getType(), widgetRect);
});
board.mIconPoints = getIconPoints(board.mWidget);
board.mFolderPoints = getFolderPoints(board.mWidget);
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java
deleted file mode 100644
index 49c146b..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellType.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.celllayout.board;
-
-public class CellType {
- // The cells marked by this will be filled by 1x1 widgets and will be ignored when
- // validating
- public static final char IGNORE = 'x';
- // The cells marked by this will be filled by app icons
- public static final char ICON = 'i';
- // The cells marked by FOLDER will be filled by folders with 27 app icons inside
- public static final char FOLDER = 'Z';
- // Empty space
- public static final char EMPTY = '-';
- // Widget that will be saved as "main widget" for easier retrieval
- public static final char MAIN_WIDGET = 'm';
- // Everything else will be consider a widget
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java
deleted file mode 100644
index 39ba434..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/FolderPoint.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.celllayout.board;
-
-import android.graphics.Point;
-
-public class FolderPoint {
- public Point coord;
- public char mType;
-
- public FolderPoint(Point coord, char type) {
- this.coord = coord;
- mType = type;
- }
-
- /**
- * [A-Z]: Represents a folder and number of icons in the folder is represented by
- * the order of letter in the alphabet, A=2, B=3, C=4 ... etc.
- */
- public int getNumberIconsInside() {
- return (mType - 'A') + 2;
- }
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java
deleted file mode 100644
index d3d2970..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IconPoint.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.celllayout.board;
-
-import android.graphics.Point;
-
-public class IconPoint {
- public Point coord;
- public char mType;
-
- public IconPoint(Point coord, char type) {
- this.coord = coord;
- mType = type;
- }
-
- public char getType() {
- return mType;
- }
-
- public void setType(char type) {
- mType = type;
- }
-
- public Point getCoord() {
- return coord;
- }
-
- public void setCoord(Point coord) {
- this.coord = coord;
- }
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt
index a4a420c..aacd940 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/IdenticalBoardComparator.kt
@@ -26,11 +26,11 @@
/** Converts a list of WidgetRect into a map of the count of different widget.bounds */
private fun widgetsToBoundsMap(widgets: List<WidgetRect>) =
- widgets.groupingBy { it.mBounds }.eachCount()
+ widgets.groupingBy { it.bounds }.eachCount()
/** Converts a list of IconPoint into a map of the count of different icon.coord */
private fun iconsToPosCountMap(widgets: List<IconPoint>) =
- widgets.groupingBy { it.getCoord() }.eachCount()
+ widgets.groupingBy { it.coord }.eachCount()
override fun compare(
cellLayoutBoard: CellLayoutBoard,
@@ -47,7 +47,7 @@
widgetsToBoundsMap(
otherCellLayoutBoard.widgets
.filter { !it.shouldIgnore() }
- .filter { !overlapsWithIgnored(ignoredRectangles, it.mBounds) }
+ .filter { !overlapsWithIgnored(ignoredRectangles, it.bounds) }
)
if (widgetsMap != otherWidgetMap) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java
deleted file mode 100644
index 8a427dd..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.celllayout.board;
-
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.launcher3.ui.TestViewHelpers.findWidgetProvider;
-import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Process;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.celllayout.FavoriteItemsTransaction;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-
-import java.util.function.Supplier;
-import java.util.stream.IntStream;
-
-public class TestWorkspaceBuilder {
-
- private static final String TAG = "CellLayoutBoardBuilder";
- private static final String TEST_ACTIVITY_PACKAGE_PREFIX = "com.android.launcher3.tests.";
- private ComponentName mAppComponentName = new ComponentName(
- "com.google.android.calculator", "com.android.calculator2.Calculator");
- private UserHandle mMyUser;
-
- private Context mContext;
-
- public TestWorkspaceBuilder(Context context) {
- mMyUser = Process.myUserHandle();
- mContext = context;
- }
-
- /**
- * Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases.
- */
- private FavoriteItemsTransaction fillWithWidgets(WidgetRect widgetRect,
- FavoriteItemsTransaction transaction, int screenId) {
- int initX = widgetRect.getCellX();
- int initY = widgetRect.getCellY();
- for (int x = initX; x < initX + widgetRect.getSpanX(); x++) {
- for (int y = initY; y < initY + widgetRect.getSpanY(); y++) {
- try {
- // this widgets are filling, we don't care if we can't place them
- transaction.addItem(createWidgetInCell(
- new WidgetRect(CellType.IGNORE,
- new Rect(x, y, x, y)), screenId));
- } catch (Exception e) {
- Log.d(TAG, "Unable to place filling widget at " + x + "," + y);
- }
- }
- }
- return transaction;
- }
-
- private AppInfo getApp() {
- return new AppInfo(mAppComponentName, "test icon", mMyUser,
- AppInfo.makeLaunchIntent(mAppComponentName));
- }
-
- /**
- * Helper to set the app to use for the test workspace,
- * using activity-alias from AndroidManifest-common.
- * @param testAppName the android:name field of the test app activity-alias to use
- */
- public void setTestAppActivityAlias(String testAppName) {
- this.mAppComponentName = new ComponentName(
- getInstrumentation().getContext().getPackageName(),
- TEST_ACTIVITY_PACKAGE_PREFIX + testAppName
- );
- }
-
- private void addCorrespondingWidgetRect(WidgetRect widgetRect,
- FavoriteItemsTransaction transaction, int screenId) {
- if (widgetRect.mType == 'x') {
- fillWithWidgets(widgetRect, transaction, screenId);
- } else {
- transaction.addItem(createWidgetInCell(widgetRect, screenId));
- }
- }
-
- /**
- * Builds the given board into the transaction
- */
- public FavoriteItemsTransaction buildFromBoard(CellLayoutBoard board,
- FavoriteItemsTransaction transaction, final int screenId) {
- board.getWidgets().forEach(
- (widgetRect) -> addCorrespondingWidgetRect(widgetRect, transaction, screenId));
- board.getIcons().forEach((iconPoint) ->
- transaction.addItem(() -> createIconInCell(iconPoint, screenId))
- );
- board.getFolders().forEach((folderPoint) ->
- transaction.addItem(() -> createFolderInCell(folderPoint, screenId))
- );
- return transaction;
- }
-
- /**
- * Fills the hotseat row with apps instead of suggestions, for this to work the workspace should
- * be clean otherwise this doesn't overrides the existing icons.
- */
- public FavoriteItemsTransaction fillHotseatIcons(FavoriteItemsTransaction transaction) {
- IntStream.range(0, InvariantDeviceProfile.INSTANCE.get(mContext).numDatabaseHotseatIcons)
- .forEach(i -> transaction.addItem(() -> getHotseatValues(i)));
- return transaction;
- }
-
- private Supplier<ItemInfo> createWidgetInCell(
- WidgetRect widgetRect, int screenId) {
- // Create the widget lazily since the appWidgetId can get lost during setup
- return () -> {
- LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
- LauncherAppWidgetInfo item = createWidgetInfo(info, getApplicationContext(), true);
- item.cellX = widgetRect.getCellX();
- item.cellY = widgetRect.getCellY();
- item.spanX = widgetRect.getSpanX();
- item.spanY = widgetRect.getSpanY();
- item.screenId = screenId;
- return item;
- };
- }
-
- public FolderInfo createFolderInCell(FolderPoint folderPoint, int screenId) {
- FolderInfo folderInfo = new FolderInfo();
- folderInfo.screenId = screenId;
- folderInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
- folderInfo.cellX = folderPoint.coord.x;
- folderInfo.cellY = folderPoint.coord.y;
- folderInfo.minSpanY = folderInfo.minSpanX = folderInfo.spanX = folderInfo.spanY = 1;
- folderInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, null);
-
- for (int i = 0; i < folderPoint.getNumberIconsInside(); i++) {
- folderInfo.add(getDefaultWorkspaceItem(screenId), false);
- }
-
- return folderInfo;
- }
-
- private WorkspaceItemInfo getDefaultWorkspaceItem(int screenId) {
- WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
- item.screenId = screenId;
- item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
- item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
- return item;
- }
-
- private ItemInfo createIconInCell(IconPoint iconPoint, int screenId) {
- WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
- item.screenId = screenId;
- item.cellX = iconPoint.getCoord().x;
- item.cellY = iconPoint.getCoord().y;
- item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
- item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
- return item;
- }
-
- private ItemInfo getHotseatValues(int x) {
- WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
- item.cellX = x;
- item.cellY = 0;
- item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
- item.rank = x;
- item.screenId = x;
- item.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
- return item;
- }
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
new file mode 100644
index 0000000..8952b85
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout.board
+
+import android.content.ComponentName
+import android.content.Context
+import android.graphics.Rect
+import android.os.Process
+import android.os.UserHandle
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.celllayout.FavoriteItemsTransaction
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.ui.TestViewHelpers
+import com.android.launcher3.util.WidgetUtils
+import java.util.function.Supplier
+
+class TestWorkspaceBuilder(private val mContext: Context) {
+
+ private var appComponentName =
+ ComponentName("com.google.android.calculator", "com.android.calculator2.Calculator")
+ private val myUser: UserHandle = Process.myUserHandle()
+
+ /** Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases. */
+ private fun fillWithWidgets(
+ widgetRect: WidgetRect,
+ transaction: FavoriteItemsTransaction,
+ screenId: Int
+ ): FavoriteItemsTransaction {
+ val initX = widgetRect.cellX
+ val initY = widgetRect.cellY
+ for (x in initX until initX + widgetRect.spanX) {
+ for (y in initY until initY + widgetRect.spanY) {
+ try {
+ // this widgets are filling, we don't care if we can't place them
+ transaction.addItem(
+ createWidgetInCell(WidgetRect(CellType.IGNORE, Rect(x, y, x, y)), screenId)
+ )
+ } catch (e: Exception) {
+ Log.d(TAG, "Unable to place filling widget at $x,$y")
+ }
+ }
+ }
+ return transaction
+ }
+
+ private fun app() =
+ AppInfo(appComponentName, "test icon", myUser, AppInfo.makeLaunchIntent(appComponentName))
+
+ /**
+ * Helper to set the app to use for the test workspace, using activity-alias from
+ * AndroidManifest-common.
+ *
+ * @param testAppName the android:name field of the test app activity-alias to use
+ */
+ fun setTestAppActivityAlias(testAppName: String) {
+ appComponentName =
+ ComponentName(
+ InstrumentationRegistry.getInstrumentation().context.packageName,
+ TEST_ACTIVITY_PACKAGE_PREFIX + testAppName
+ )
+ }
+
+ private fun addCorrespondingWidgetRect(
+ widgetRect: WidgetRect,
+ transaction: FavoriteItemsTransaction,
+ screenId: Int
+ ) {
+ if (widgetRect.type == 'x') {
+ fillWithWidgets(widgetRect, transaction, screenId)
+ } else {
+ transaction.addItem(createWidgetInCell(widgetRect, screenId))
+ }
+ }
+
+ /** Builds the given board into the transaction */
+ fun buildFromBoard(
+ board: CellLayoutBoard,
+ transaction: FavoriteItemsTransaction,
+ screenId: Int
+ ): FavoriteItemsTransaction {
+ board.widgets.forEach { addCorrespondingWidgetRect(it, transaction, screenId) }
+ board.icons.forEach { transaction.addItem { createIconInCell(it, screenId) } }
+ board.folders.forEach { transaction.addItem { createFolderInCell(it, screenId) } }
+ return transaction
+ }
+
+ /**
+ * Fills the hotseat row with apps instead of suggestions, for this to work the workspace should
+ * be clean otherwise this doesn't overrides the existing icons.
+ */
+ fun fillHotseatIcons(transaction: FavoriteItemsTransaction): FavoriteItemsTransaction {
+ for (i in 0..<InvariantDeviceProfile.INSTANCE[mContext].numDatabaseHotseatIcons) {
+ transaction.addItem { getHotseatValues(i) }
+ }
+ return transaction
+ }
+
+ private fun createWidgetInCell(widgetRect: WidgetRect, paramScreenId: Int): Supplier<ItemInfo> {
+ // Create the widget lazily since the appWidgetId can get lost during setup
+ return Supplier<ItemInfo> {
+ WidgetUtils.createWidgetInfo(
+ TestViewHelpers.findWidgetProvider(false),
+ ApplicationProvider.getApplicationContext(),
+ true
+ )
+ .apply {
+ cellX = widgetRect.cellX
+ cellY = widgetRect.cellY
+ spanX = widgetRect.spanX
+ spanY = widgetRect.spanY
+ screenId = paramScreenId
+ }
+ }
+ }
+
+ fun createFolderInCell(folderPoint: FolderPoint, paramScreenId: Int): FolderInfo =
+ FolderInfo().apply {
+ screenId = paramScreenId
+ container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+ cellX = folderPoint.coord.x
+ cellY = folderPoint.coord.y
+ spanY = 1
+ spanX = 1
+ minSpanX = 1
+ minSpanY = 1
+ setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, null)
+ for (i in 0 until folderPoint.numberIconsInside) {
+ add(getDefaultWorkspaceItem(paramScreenId), false)
+ }
+ }
+
+ private fun getDefaultWorkspaceItem(paramScreenId: Int): WorkspaceItemInfo =
+ WorkspaceItemInfo(app()).apply {
+ screenId = paramScreenId
+ spanY = 1
+ spanX = 1
+ minSpanX = 1
+ minSpanY = 1
+ container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+ }
+
+ private fun createIconInCell(iconPoint: IconPoint, paramScreenId: Int) =
+ WorkspaceItemInfo(app()).apply {
+ screenId = paramScreenId
+ cellX = iconPoint.coord.x
+ cellY = iconPoint.coord.y
+ spanY = 1
+ spanX = 1
+ minSpanX = 1
+ minSpanY = 1
+ container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+ }
+
+ private fun getHotseatValues(x: Int) =
+ WorkspaceItemInfo(app()).apply {
+ cellX = x
+ cellY = 0
+ spanY = 1
+ spanX = 1
+ minSpanX = 1
+ minSpanY = 1
+ rank = x
+ screenId = x
+ container = LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ }
+
+ companion object {
+ private const val TAG = "CellLayoutBoardBuilder"
+ private const val TEST_ACTIVITY_PACKAGE_PREFIX = "com.android.launcher3.tests."
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java
deleted file mode 100644
index c90ce85..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/WidgetRect.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.celllayout.board;
-
-import android.graphics.Rect;
-
-public class WidgetRect {
- public char mType;
- public Rect mBounds;
-
- public WidgetRect(char type, Rect bounds) {
- this.mType = type;
- this.mBounds = bounds;
- }
-
- public int getSpanX() {
- return mBounds.right - mBounds.left + 1;
- }
-
- public int getSpanY() {
- return mBounds.top - mBounds.bottom + 1;
- }
-
- public int getCellX() {
- return mBounds.left;
- }
-
- public int getCellY() {
- return mBounds.bottom;
- }
-
- boolean shouldIgnore() {
- return this.mType == CellType.IGNORE;
- }
-
- boolean contains(int x, int y) {
- return mBounds.contains(x, y);
- }
-
- @Override
- public String toString() {
- return "WidgetRect type = " + mType + " x = " + getCellX() + " | y " + getCellY()
- + " xs = " + getSpanX() + " ys = " + getSpanY();
- }
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt b/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt
index 8204313..3e6aae2 100644
--- a/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt
@@ -65,7 +65,7 @@
@Test
fun preinflate_success() {
- underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
+ underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 }
awaitTasksCompleted()
assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(10)
@@ -73,7 +73,7 @@
@Test
fun preinflate_not_triggered() {
- underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 0 }
+ underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 0) { 0 }
awaitTasksCompleted()
assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
@@ -81,7 +81,7 @@
@Test
fun preinflate_cancel_before_runOnMainThread() {
- underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
+ underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 }
assertThat(underTest.mCancellableTask!!.canceled).isFalse()
underTest.clear()
@@ -94,7 +94,7 @@
@Test
fun preinflate_cancel_after_run() {
- underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
+ underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 }
assertThat(underTest.mCancellableTask!!.canceled).isFalse()
awaitTasksCompleted()