Merge "Convert testing classest to Kotlin" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index a779641..0df6c36 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -311,13 +311,6 @@
}
flag {
- name: "enable_new_archiving_icon"
- namespace: "launcher"
- description: "Archived apps will use new icon in app title"
- bug: "350758155"
-}
-
-flag {
name: "enable_multi_instance_menu_taskbar"
namespace: "launcher"
description: "Menu in Taskbar with options to launch and manage multiple instances of the same app"
@@ -329,3 +322,13 @@
description: "Settings screen supports navigating to child preference if the key is not on the screen"
bug: "293390881"
}
+
+flag {
+ name: "use_new_icon_for_archived_apps"
+ namespace: "launcher"
+ description: "Archived apps will use new cloud icon in app title instead of overlay"
+ bug: "350758155"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index e4f7262..2cb37ff 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)
));
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 67cb7cb..9dee473 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -64,6 +64,10 @@
companion object {
private const val INDEX_LEFT = 0
private const val INDEX_RIGHT = 1
+
+ private fun Region.addBoundsToRegion(bounds: Rect?) {
+ bounds?.let { op(it, Region.Op.UNION) }
+ }
}
/** The bottom insets taskbar provides to the IME when IME is visible. */
@@ -128,32 +132,26 @@
}
}
+ val bubbleControllers = controllers.bubbleControllers.getOrNull()
val taskbarTouchableHeight = controllers.taskbarStashController.touchableHeight
val bubblesTouchableHeight =
- if (controllers.bubbleControllers.isPresent) {
- controllers.bubbleControllers.get().bubbleStashController.getTouchableHeight()
- } else {
- 0
- }
+ bubbleControllers?.bubbleStashController?.getTouchableHeight() ?: 0
+ // add bounds for task bar and bubble bar stash controllers
val touchableHeight = max(taskbarTouchableHeight, bubblesTouchableHeight)
-
defaultTouchableRegion.set(
0,
windowLayoutParams.height - touchableHeight,
context.deviceProfile.widthPx,
windowLayoutParams.height
)
- val isBubbleBarVisible =
- controllers.bubbleControllers.isPresent &&
- controllers.bubbleControllers.get().bubbleStashController.isBubbleBarVisible()
- // if there's an animating bubble add it to the touch region so that it's clickable
- val isAnimatingNewBubble =
- controllers.bubbleControllers.getOrNull()?.bubbleBarViewController?.isAnimatingNewBubble
- ?: false
- if (isBubbleBarVisible || isAnimatingNewBubble) {
- val iconBounds =
- controllers.bubbleControllers.get().bubbleBarViewController.bubbleBarBounds
- defaultTouchableRegion.op(iconBounds, Region.Op.UNION)
+ if (bubbleControllers != null) {
+ val bubbleBarViewController = bubbleControllers.bubbleBarViewController
+ val isBubbleBarVisible = bubbleControllers.bubbleStashController.isBubbleBarVisible()
+ val isAnimatingNewBubble = bubbleBarViewController.isAnimatingNewBubble
+ // if bubble bar is visible or animating new bubble, add bar bounds to the touch region
+ if (isBubbleBarVisible || isAnimatingNewBubble) {
+ defaultTouchableRegion.addBoundsToRegion(bubbleBarViewController.bubbleBarBounds)
+ }
}
// Pre-calculate insets for different providers across different rotations for this gravity
@@ -404,7 +402,7 @@
// Include the bounds of the bubble bar in the touchable region if they exist.
if (bubbleBarBounds != null) {
- region.op(bubbleBarBounds, Region.Op.UNION)
+ region.addBoundsToRegion(bubbleBarBounds)
}
insetsInfo.touchableRegion.set(region)
debugTouchableRegion.lastSetTouchableReason = "Transient Taskbar is in Overview"
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/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index da7a98f..32e2389 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -128,15 +128,11 @@
mController::finishAnimationToApp);
} else {
RemoteAnimationTarget[] nonAppTargets;
- if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
- nonAppTargets = mSystemUiProxy.onGoingToRecentsLegacy(appTargets);
- } else {
- final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
- final ArrayList<RemoteAnimationTarget> nonApps = new ArrayList<>();
- classifyTargets(appTargets, apps, nonApps);
- appTargets = apps.toArray(new RemoteAnimationTarget[apps.size()]);
- nonAppTargets = nonApps.toArray(new RemoteAnimationTarget[nonApps.size()]);
- }
+ final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
+ final ArrayList<RemoteAnimationTarget> nonApps = new ArrayList<>();
+ classifyTargets(appTargets, apps, nonApps);
+ appTargets = apps.toArray(new RemoteAnimationTarget[apps.size()]);
+ nonAppTargets = nonApps.toArray(new RemoteAnimationTarget[nonApps.size()]);
if (nonAppTargets == null) {
nonAppTargets = new RemoteAnimationTarget[0];
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 673de14..0c2f29b 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -1000,77 +1000,6 @@
}
}
- /**
- * Start multiple tasks in split-screen simultaneously.
- */
- public void startTasksWithLegacyTransition(int taskId1, Bundle options1, int taskId2,
- Bundle options2, @StagePosition int splitPosition,
- @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
- if (mSystemUiProxy != null) {
- try {
- mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
- splitPosition, snapPosition, adapter, instanceId);
- } catch (RemoteException e) {
- Log.w(TAG, splitFailureMessage(
- "startTasksWithLegacyTransition", "RemoteException"), e);
- }
- }
- }
-
- public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
- Bundle options1, int taskId, Bundle options2, @StagePosition int splitPosition,
- @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
- if (mSystemUiProxy != null) {
- try {
- mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
- options1, taskId, options2, splitPosition, snapPosition, adapter,
- instanceId);
- } catch (RemoteException e) {
- Log.w(TAG, splitFailureMessage(
- "startIntentAndTaskWithLegacyTransition", "RemoteException"), e);
- }
- }
- }
-
- public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, Bundle options1,
- int taskId, Bundle options2, @StagePosition int splitPosition,
- @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
- if (mSystemUiProxy != null) {
- try {
- mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
- taskId, options2, splitPosition, snapPosition, adapter, instanceId);
- } catch (RemoteException e) {
- Log.w(TAG, splitFailureMessage(
- "startShortcutAndTaskWithLegacyTransition", "RemoteException"), e);
- }
- }
- }
-
- /**
- * Starts a pair of intents or shortcuts in split-screen using legacy transition. Passing a
- * non-null shortcut info means to start the app as a shortcut.
- */
- public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
- @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @StagePosition int sidePosition,
- @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
- if (mSystemUiProxy != null) {
- try {
- mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, userId1,
- shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, options2,
- sidePosition, snapPosition, adapter, instanceId);
- } catch (RemoteException e) {
- Log.w(TAG, splitFailureMessage(
- "startIntentsWithLegacyTransition", "RemoteException"), e);
- }
- }
- }
-
public void startShortcut(String packageName, String shortcutId, int position,
Bundle options, UserHandle user, InstanceId instanceId) {
if (mSplitScreen != null) {
@@ -1105,36 +1034,6 @@
}
}
- /**
- * Call this when going to recents so that shell can set-up and provide appropriate leashes
- * for animation (eg. DividerBar).
- *
- * @return RemoteAnimationTargets of windows that need to animate but only exist in shell.
- */
- @Nullable
- public RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
- if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS && mSplitScreen != null) {
- try {
- return mSplitScreen.onGoingToRecentsLegacy(apps);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call onGoingToRecentsLegacy");
- }
- }
- return null;
- }
-
- @Nullable
- public RemoteAnimationTarget[] onStartingSplitLegacy(RemoteAnimationTarget[] apps) {
- if (mSplitScreen != null) {
- try {
- return mSplitScreen.onStartingSplitLegacy(apps);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call onStartingSplitLegacy");
- }
- }
- return null;
- }
-
//
// One handed
//
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 723aa03..f414399 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -264,12 +264,7 @@
}
}
- RemoteAnimationTarget[] nonAppTargets = ENABLE_SHELL_TRANSITIONS
- ? null : getSystemUiProxy().onStartingSplitLegacy(
- appearedTaskTargets);
- if (nonAppTargets == null) {
- nonAppTargets = new RemoteAnimationTarget[0];
- }
+ RemoteAnimationTarget[] nonAppTargets = new RemoteAnimationTarget[0];
if ((containerInterface.isInLiveTileMode()
|| mLastGestureState.getEndTarget() == RECENTS
|| isNonRecentsStartedTasksAppeared(appearedTaskTargets))
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index 54e34a0..b1f46a3 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -66,7 +66,7 @@
combine(
updatedThumbnails.map {
recentsTasksRepository.getThumbnailById(it.key).filter { thumbnailData ->
- thumbnailData == it.value
+ thumbnailData?.snapshotId == it.value.snapshotId
}
}
) {}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 3702f93..fa9a282 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1048,7 +1048,7 @@
@Nullable
public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
if (enableRefactorTaskThumbnail()) {
- // TODO(b/342560598): Listen in TaskRepository and reload
+ mHelper.onTaskThumbnailChanged(taskId, thumbnailData);
return null;
}
if (mHandleTaskStackChanges) {
@@ -1097,9 +1097,7 @@
/** Updates the thumbnail(s) of the relevant TaskView. */
public void updateThumbnail(Map<Integer, ThumbnailData> thumbnailData) {
- if (enableRefactorTaskThumbnail()) {
- mRecentsViewModel.addOrUpdateThumbnailOverride(thumbnailData);
- } else {
+ if (!enableRefactorTaskThumbnail()) {
for (Map.Entry<Integer, ThumbnailData> entry : thumbnailData.entrySet()) {
Integer id = entry.getKey();
ThumbnailData thumbnail = entry.getValue();
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewHelper.kt b/quickstep/src/com/android/quickstep/views/RecentsViewHelper.kt
index a63ccec..e8c9718 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewHelper.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewHelper.kt
@@ -60,9 +60,6 @@
// Update recentsViewModel and apply the thumbnailOverride ASAP, before waiting inside
// viewAttachedScope.
recentsViewModel.setRunningTaskShowScreenshot(true)
- if (updatedThumbnails != null) {
- recentsViewModel.addOrUpdateThumbnailOverride(updatedThumbnails)
- }
viewAttachedScope.launch {
recentsViewModel.waitForRunningTaskShowScreenshotToUpdate()
if (updatedThumbnails != null) {
@@ -71,4 +68,8 @@
ViewUtils.postFrameDrawn(taskView, onFinishRunnable)
}
}
+
+ fun onTaskThumbnailChanged(taskId: Int, thumbnailData: ThumbnailData) {
+ recentsViewModel.addOrUpdateThumbnailOverride(mapOf(taskId to thumbnailData))
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
index b3caf2d..dc16475 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
@@ -72,9 +72,8 @@
@Test
fun thumbnailOverrideWaitAndReset() = runTest {
- val thumbnailData1 = createThumbnailData()
- val thumbnailData2 = createThumbnailData()
- val thumbnailDataOverride = createThumbnailData()
+ val thumbnailData1 = createThumbnailData().apply { snapshotId = 1 }
+ val thumbnailData2 = createThumbnailData().apply { snapshotId = 2 }
tasksRepository.seedTasks(tasks)
tasksRepository.seedThumbnailData(mapOf(1 to thumbnailData1, 2 to thumbnailData2))
@@ -87,15 +86,16 @@
assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1)
assertThat(thumbnailDataFlow2.first()).isEqualTo(thumbnailData2)
- val thumbnailUpdate = mapOf(2 to thumbnailDataOverride)
systemUnderTest.setRunningTaskShowScreenshot(true)
- systemUnderTest.addOrUpdateThumbnailOverride(thumbnailUpdate)
+ val thumbnailOverride = mapOf(2 to createThumbnailData().apply { snapshotId = 3 })
+ systemUnderTest.addOrUpdateThumbnailOverride(thumbnailOverride)
systemUnderTest.waitForRunningTaskShowScreenshotToUpdate()
- systemUnderTest.waitForThumbnailsToUpdate(thumbnailUpdate)
+ val expectedUpdate = mapOf(2 to createThumbnailData().apply { snapshotId = 3 })
+ systemUnderTest.waitForThumbnailsToUpdate(expectedUpdate)
assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1)
- assertThat(thumbnailDataFlow2.first()).isEqualTo(thumbnailDataOverride)
+ assertThat(thumbnailDataFlow2.first()?.snapshotId).isEqualTo(3)
systemUnderTest.onReset()
diff --git a/res/color-night-v31/material_color_surface_container_lowest.xml b/res/color-night-v31/material_color_surface_container_lowest.xml
new file mode 100644
index 0000000..4396f6d
--- /dev/null
+++ b/res/color-night-v31/material_color_surface_container_lowest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_500" android:lStar="4" />
+</selector>
\ No newline at end of file
diff --git a/res/color-v31/material_color_surface_container_lowest.xml b/res/color-v31/material_color_surface_container_lowest.xml
new file mode 100644
index 0000000..f726aea
--- /dev/null
+++ b/res/color-v31/material_color_surface_container_lowest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_500" android:lStar="100" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable/cloud_download_semibold_24px.xml b/res/drawable/cloud_download_semibold_24px.xml
new file mode 100644
index 0000000..ef15f9f
--- /dev/null
+++ b/res/drawable/cloud_download_semibold_24px.xml
@@ -0,0 +1,11 @@
+<!-- From GM3 icon cloud_download:wght600_vd_theme_24 -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M260,819.22Q162.22,819.22 92.35,751.7Q22.48,684.17 22.48,586.63Q22.48,503.03 70.61,437.64Q118.74,372.26 198.13,354.13Q217.39,283.26 284.26,217.98Q351.13,152.7 428.7,152.7Q467.91,152.7 496.22,179.3Q524.52,205.91 524.52,244L524.52,484.3L584,425.7L646.22,487.91L480,654.13L313.78,487.91L376,425.7L435.48,484.3L435.48,252.48Q365.13,269.87 326.8,327.11Q288.48,384.35 288.48,446.78L261.7,446.78Q206.51,446.78 167.49,485.8Q128.48,524.81 128.48,580Q128.48,635.74 167,674.48Q205.51,713.22 260,713.22L740,713.22Q778.04,713.22 804.78,686.48Q831.52,659.74 831.52,620Q831.52,581.39 804.78,554.09Q778.04,526.78 738.3,526.78L675.48,526.78L675.48,441.7Q675.48,396.52 656.3,358.98Q637.13,321.44 604.52,292.44L604.52,171.74Q680.78,206.74 726.33,275.8Q771.87,344.87 776.39,428.13L776.39,428.13L776.39,428.13Q847.09,442.35 892.31,496.96Q937.52,551.57 937.52,623.39Q937.52,704.99 879.91,762.1Q822.3,819.22 740,819.22L260,819.22ZM480,431.83Q480,431.83 480,431.83Q480,431.83 480,431.83L480,431.83Q480,431.83 480,431.83Q480,431.83 480,431.83L480,431.83Q480,431.83 480,431.83Q480,431.83 480,431.83L480,431.83Q480,431.83 480,431.83Q480,431.83 480,431.83Q480,431.83 480,431.83Q480,431.83 480,431.83L480,431.83Q480,431.83 480,431.83Q480,431.83 480,431.83Q480,431.83 480,431.83Q480,431.83 480,431.83L480,431.83L480,431.83Q480,431.83 480,431.83Q480,431.83 480,431.83Z"/>
+</vector>
diff --git a/res/drawable/private_space_install_app_icon.xml b/res/drawable/private_space_install_app_icon.xml
index 12c4a82..cfec2b1 100644
--- a/res/drawable/private_space_install_app_icon.xml
+++ b/res/drawable/private_space_install_app_icon.xml
@@ -23,9 +23,9 @@
android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z" />
<path
android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z"
- android:fillColor="?attr/materialColorSurfaceContainerLowest" />
+ android:fillColor="@color/material_color_surface_container_lowest" />
<path
android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
- android:fillColor="?attr/materialColorOnSurface" />
+ android:fillColor="@color/material_color_on_surface" />
</group>
</vector>
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index 07c450e..0f630e5 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -57,4 +57,6 @@
@android:color/system_accent1_200</color>
<color name="work_fab_icon_color">
@android:color/system_accent1_900</color>
+
+ <color name="material_color_on_surface">@android:color/system_neutral1_100</color>
</resources>
\ No newline at end of file
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
new file mode 100644
index 0000000..887a2a5
--- /dev/null
+++ b/res/values-night/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<resources>
+ <color name="material_color_surface_container_lowest">#0D0E11</color>
+ <color name="material_color_on_surface">#E3E2E6</color>
+</resources>
\ No newline at end of file
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index 5c81d49..a5cdfc7 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -110,4 +110,6 @@
@android:color/system_accent1_900</color>
<color name="overview_foreground_scrim_color">@android:color/system_neutral1_1000</color>
+
+ <color name="material_color_on_surface">@android:color/system_neutral1_900</color>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index c82358b..3f8bede 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -142,6 +142,9 @@
<color name="widget_cell_title_color_dark">@color/system_on_surface_dark</color>
<color name="widget_cell_subtitle_color_dark">@color/system_on_surface_variant_dark</color>
+ <color name="material_color_surface_container_lowest">#FFFFFF</color>
+ <color name="material_color_on_surface">#1B1B1F</color>
+
<color name="system_primary_container_light">#D9E2FF</color>
<color name="system_on_primary_container_light">#001945</color>
<color name="system_primary_light">#475D92</color>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 0cb2137..2eb5034 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static android.graphics.fonts.FontStyle.FONT_WEIGHT_BOLD;
+import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
import static android.text.Layout.Alignment.ALIGN_NORMAL;
import static com.android.launcher3.Flags.enableCursorHoverStates;
@@ -118,6 +120,7 @@
private static final String EMPTY = "";
private static final StringMatcherUtility.StringMatcher MATCHER =
StringMatcherUtility.StringMatcher.getInstance();
+ private static final int BOLD_TEXT_ADJUSTMENT = FONT_WEIGHT_BOLD - FONT_WEIGHT_NORMAL;
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
@@ -501,10 +504,10 @@
mLastOriginalText = label;
mLastModifiedText = mLastOriginalText;
mBreakPointsIntArray = StringMatcherUtility.getListOfBreakpoints(label, MATCHER);
- if (Flags.enableNewArchivingIcon()
+ if (Flags.useNewIconForArchivedApps()
&& info instanceof ItemInfoWithIcon infoWithIcon
&& infoWithIcon.isInactiveArchive()) {
- setTextWithStartIcon(label, R.drawable.cloud_download_24px);
+ setTextWithArchivingIcon(label);
} else {
setText(label);
}
@@ -817,10 +820,10 @@
getLineSpacingExtra());
if (!TextUtils.equals(modifiedString, mLastModifiedText)) {
mLastModifiedText = modifiedString;
- if (Flags.enableNewArchivingIcon()
+ if (Flags.useNewIconForArchivedApps()
&& getTag() instanceof ItemInfoWithIcon infoWithIcon
&& infoWithIcon.isInactiveArchive()) {
- setTextWithStartIcon(modifiedString, R.drawable.cloud_download_24px);
+ setTextWithArchivingIcon(modifiedString);
} else {
setText(modifiedString);
}
@@ -845,12 +848,28 @@
}
/**
+ * Sets text with a start icon for App Archiving.
+ * Uses a bolded drawable if text is bolded.
+ * @param text
+ */
+ private void setTextWithArchivingIcon(CharSequence text) {
+ var drawableId = R.drawable.cloud_download_24px;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S
+ && getResources().getConfiguration().fontWeightAdjustment >= BOLD_TEXT_ADJUSTMENT) {
+ // If System bold text setting is on, then use a bolded icon
+ drawableId = R.drawable.cloud_download_semibold_24px;
+ }
+ setTextWithStartIcon(text, drawableId);
+ }
+
+ /**
* Uses a SpannableString to set text with a Drawable at the start of the TextView
* @param text text to use for TextView
- * @param drawableRes Drawable Resource to use for drawing image at start of text
+ * @param drawableId Drawable Resource to use for drawing image at start of text
*/
- private void setTextWithStartIcon(CharSequence text, @DrawableRes int drawableRes) {
- Drawable drawable = getContext().getDrawable(drawableRes);
+ @VisibleForTesting
+ public void setTextWithStartIcon(CharSequence text, @DrawableRes int drawableId) {
+ Drawable drawable = getContext().getDrawable(drawableId);
if (drawable == null) {
setText(text);
Log.w(TAG, "setTextWithStartIcon: start icon Drawable not found from resources"
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 08ccfb2..15641ab 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -115,7 +115,7 @@
if (BuildCompat.isAtLeastV() && Flags.enableSupportForArchiving()) {
ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
params.setEnableUnarchivalConfirmation(false);
- params.setEnableIconOverlay(!Flags.enableNewArchivingIcon());
+ params.setEnableIconOverlay(!Flags.useNewIconForArchivedApps());
launcherApps.setArchiveCompatibility(params);
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 9271596..a3cabc2 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -412,9 +412,8 @@
// Apply decorator to private apps.
if (hasPrivateApps) {
mAdapterItems.add(AdapterItem.asAppWithDecorationInfo(info,
- new SectionDecorationInfo(mActivityContext.getApplicationContext(),
- getRoundRegions(i, appList.size()),
- true /* decorateTogether */)));
+ new SectionDecorationInfo(mActivityContext,
+ getRoundRegions(i, appList.size()), true /* decorateTogether */)));
} else {
mAdapterItems.add(AdapterItem.asApp(info));
}
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/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()
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index b83349e..aa7f388 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -16,28 +16,41 @@
package com.android.launcher3.ui;
+import static android.graphics.fonts.FontStyle.FONT_WEIGHT_BOLD;
+import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
+import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;
+
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS;
import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT;
import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT_SMALL;
+import static com.android.launcher3.Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS;
import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
+import android.os.Build;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.text.SpannedString;
+import android.text.style.ImageSpan;
import android.view.ViewGroup;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import com.android.launcher3.BubbleTextView;
@@ -403,6 +416,62 @@
assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(false);
}
+ @EnableFlags(FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS)
+ @Test
+ public void applyIconAndLabel_setsImageSpan_whenInactiveArchivedApp() {
+ // Given
+ BubbleTextView spyTextView = spy(mBubbleTextView);
+ mGmailAppInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ BubbleTextView expectedTextView = new BubbleTextView(mContext);
+ mContext.getResources().getConfiguration().fontWeightAdjustment = 0;
+ int expectedDrawableId = mContext.getResources().getIdentifier(
+ "cloud_download_24px", /* name */
+ "drawable", /* defType */
+ mContext.getPackageName()
+ );
+ expectedTextView.setTextWithStartIcon(mGmailAppInfo.title, expectedDrawableId);
+ // When
+ spyTextView.applyIconAndLabel(mGmailAppInfo);
+ // Then
+ SpannedString expectedText = (SpannedString) expectedTextView.getText();
+ SpannedString actualText = (SpannedString) spyTextView.getText();
+ ImageSpan actualSpan = actualText.getSpans(
+ 0, /* queryStart */
+ 1, /* queryEnd */
+ ImageSpan.class
+ )[0];
+ ImageSpan expectedSpan = expectedText.getSpans(
+ 0, /* queryStart */
+ 1, /* queryEnd */
+ ImageSpan.class
+ )[0];
+ verify(spyTextView).setTextWithStartIcon(mGmailAppInfo.title, expectedDrawableId);
+ assertThat(actualText.toString()).isEqualTo(expectedText.toString());
+ assertThat(actualSpan.getDrawable().getBounds())
+ .isEqualTo(expectedSpan.getDrawable().getBounds());
+ assertThat(actualSpan.getVerticalAlignment()).isEqualTo(ALIGN_CENTER);
+ }
+
+ @EnableFlags(FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ @Test
+ public void applyIconAndLabel_setsBoldDrawable_whenBoldedTextForArchivedApp() {
+ // Given
+ int expectedDrawableId = mContext.getResources().getIdentifier(
+ "cloud_download_semibold_24px", /* name */
+ "drawable", /* defType */
+ mContext.getPackageName()
+ );
+ mContext.getResources().getConfiguration().fontWeightAdjustment =
+ FONT_WEIGHT_BOLD - FONT_WEIGHT_NORMAL;
+ BubbleTextView spyTextView = spy(mBubbleTextView);
+ mGmailAppInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+ // When
+ spyTextView.applyIconAndLabel(mGmailAppInfo);
+ // Then
+ verify(spyTextView).setTextWithStartIcon(mGmailAppInfo.title, expectedDrawableId);
+ }
+
@Test
public void applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_hasBadge() {
FlagOp op = FlagOp.NO_OP;