Merge "[Desktop Mode] Show indicators under Taskbar app icons for running apps" into main
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 1b5b0ee..14a916f 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -31,6 +31,7 @@
<color name="taskbar_nav_icon_dark_color_on_home">#99000000</color>
<color name="taskbar_stashed_handle_light_color">#EBffffff</color>
<color name="taskbar_stashed_handle_dark_color">#99000000</color>
+ <color name="taskbar_running_app_indicator_color">#646464</color>
<!-- Floating rotation button -->
<color name="floating_rotation_button_light_color">#ffffff</color>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index dbf075c..b862d7c 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -353,6 +353,9 @@
<dimen name="taskbar_back_button_suw_start_margin">48dp</dimen>
<dimen name="taskbar_back_button_suw_bottom_margin">1dp</dimen>
<dimen name="taskbar_back_button_suw_height">72dp</dimen>
+ <dimen name="taskbar_running_app_indicator_height">4dp</dimen>
+ <dimen name="taskbar_running_app_indicator_width">14dp</dimen>
+ <dimen name="taskbar_running_app_indicator_top_margin">2dp</dimen>
<!-- Transient taskbar -->
<dimen name="transient_taskbar_padding">12dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt
index f665e21..06d25a2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt
@@ -80,6 +80,13 @@
return newHotseatItemInfos.toTypedArray()
}
+ override fun getRunningApps(): Set<String> {
+ if (!isInDesktopMode) {
+ return emptySet()
+ }
+ return allRunningDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet()
+ }
+
@VisibleForTesting
public override fun updateRunningApps(hotseatItems: SparseArray<ItemInfo>?) {
if (!isInDesktopMode) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 6c84f80..be87cfd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -47,6 +47,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -233,15 +234,23 @@
}
hotseatItemInfos = mControllers.taskbarRecentAppsController
.updateHotseatItemInfos(hotseatItemInfos);
+ Set<String> runningPackages = mControllers.taskbarRecentAppsController.getRunningApps();
if (mDeferUpdatesForSUW) {
ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
- mDeferredUpdates = () -> mContainer.updateHotseatItems(finalHotseatItemInfos);
+ mDeferredUpdates = () ->
+ commitHotseatItemUpdates(finalHotseatItemInfos, runningPackages);
} else {
- mContainer.updateHotseatItems(hotseatItemInfos);
+ commitHotseatItemUpdates(hotseatItemInfos, runningPackages);
}
}
+ private void commitHotseatItemUpdates(
+ ItemInfo[] hotseatItemInfos, Set<String> runningPackages) {
+ mContainer.updateHotseatItems(hotseatItemInfos);
+ mControllers.taskbarViewController.updateIconViewsRunningStates(runningPackages);
+ }
+
/**
* This is used to defer UI updates after SUW builds the unstash animation.
* @param defer if true, defers updates to the UI
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
index 8445cff..9b84f1b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar;
+import static java.util.Collections.emptySet;
+
import android.util.SparseArray;
import androidx.annotation.CallSuper;
@@ -22,6 +24,8 @@
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
+import java.util.Set;
+
/**
* Base class for providing recent apps functionality
*/
@@ -43,7 +47,8 @@
}
/** Stores the current {@link AppInfo} instances, no-op except in desktop environment. */
- protected void setApps(AppInfo[] apps) { }
+ protected void setApps(AppInfo[] apps) {
+ }
/**
* Indicates whether recent apps functionality is enabled, should return false except in
@@ -59,5 +64,11 @@
}
/** Called to update the list of currently running apps, no-op except in desktop environment. */
- protected void updateRunningApps(SparseArray<ItemInfo> hotseatItems) { }
+ protected void updateRunningApps(SparseArray<ItemInfo> hotseatItems) {
+ }
+
+ /** Returns the currently running apps, or an empty Set if outside of Desktop environment. */
+ public Set<String> getRunningApps() {
+ return emptySet();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 5d0eac3..e0b446e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -71,6 +71,7 @@
import com.android.launcher3.views.IconButtonView;
import java.io.PrintWriter;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -507,6 +508,15 @@
return mTaskbarView.getTaskbarDividerView();
}
+ /** Updates which icons are marked as running given the Set of currently running packages. */
+ public void updateIconViewsRunningStates(Set<String> runningPackages) {
+ for (View iconView : getIconViews()) {
+ if (iconView instanceof BubbleTextView btv) {
+ btv.updateRunningState(runningPackages.contains(btv.getTargetPackageName()));
+ }
+ }
+ }
+
/**
* Defers any updates to the UI for the setup wizard animation.
*/
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt
index 93eefe2..2cfcf38 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt
@@ -154,6 +154,29 @@
assertThat(newHotseatItems?.map { it.targetPackage }).isEqualTo(expectedPackages)
}
+ @Test
+ fun getRunningApps_notInDesktopMode_returnsEmptySet() {
+ setInDesktopMode(false)
+ val runningTasks =
+ createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
+ whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
+ taskbarRunningAppsController.updateRunningApps(createSparseArray(emptyList()))
+
+ assertThat(taskbarRunningAppsController.runningApps).isEqualTo(emptySet<String>())
+ }
+
+ @Test
+ fun getRunningApps_inDesktopMode_returnsRunningApps() {
+ setInDesktopMode(true)
+ val runningTasks =
+ createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
+ whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
+ taskbarRunningAppsController.updateRunningApps(createSparseArray(emptyList()))
+
+ assertThat(taskbarRunningAppsController.runningApps)
+ .isEqualTo(setOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
+ }
+
private fun createHotseatItemsFromPackageNames(packageNames: List<String>): List<ItemInfo> {
return packageNames.map { createTestAppInfo(packageName = it) }
}
diff --git a/res/values/colors.xml b/res/values/colors.xml
index a620eb0..dfe40fc 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -90,6 +90,8 @@
<color name="drop_target_hover_button_color_light">#D3E3FD</color>
<color name="drop_target_hover_button_color_dark">#0842A0</color>
+ <color name="taskbar_running_app_indicator_color">#000000</color>
+
<color name="preload_icon_accent_color_light">#00668B</color>
<color name="preload_icon_background_color_light">#B5CAD7</color>
<color name="preload_icon_accent_color_dark">#4BB6E8</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 31def04..1bf59e8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -394,6 +394,9 @@
<dimen name="min_hotseat_icon_space">18dp</dimen>
<dimen name="max_hotseat_icon_space">50dp</dimen>
<dimen name="min_hotseat_qsb_width">0dp</dimen>
+ <dimen name="taskbar_running_app_indicator_height">0dp</dimen>
+ <dimen name="taskbar_running_app_indicator_width">0dp</dimen>
+ <dimen name="taskbar_running_app_indicator_top_margin">0dp</dimen>
<!-- Transient taskbar (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="transient_taskbar_padding">0dp</dimen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a7284e5..2a8298f 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -46,6 +46,7 @@
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.Property;
+import android.util.Size;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -182,6 +183,13 @@
private Animator mDotScaleAnim;
private boolean mForceHideDot;
+ // These fields, related to showing running apps, are only used for Taskbar.
+ private final Size mRunningAppIndicatorSize;
+ private final int mRunningAppIndicatorTopMargin;
+ private final Paint mRunningAppIndicatorPaint;
+ private final Rect mRunningAppIconBounds = new Rect();
+ private boolean mIsRunning;
+
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mStayPressed;
@ViewDebug.ExportedProperty(category = "launcher")
@@ -248,6 +256,16 @@
defaultIconSize);
a.recycle();
+ mRunningAppIndicatorSize = new Size(
+ getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width),
+ getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_height));
+ mRunningAppIndicatorTopMargin =
+ getResources().getDimensionPixelSize(
+ R.dimen.taskbar_running_app_indicator_top_margin);
+ mRunningAppIndicatorPaint = new Paint();
+ mRunningAppIndicatorPaint.setColor(getResources().getColor(
+ R.color.taskbar_running_app_indicator_color, context.getTheme()));
+
mLongPressHelper = new CheckLongPressHelper(this);
mDotParams = new DotRenderer.DrawParams();
@@ -394,6 +412,12 @@
setDownloadStateContentDescription(info, info.getProgressLevel());
}
+ /** Updates whether the app this view represents is currently running. */
+ @UiThread
+ public void updateRunningState(boolean isRunning) {
+ mIsRunning = isRunning;
+ }
+
protected void setItemInfo(ItemInfoWithIcon itemInfo) {
setTag(itemInfo);
}
@@ -620,6 +644,7 @@
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawDotIfNecessary(canvas);
+ drawRunningAppIndicatorIfNecessary(canvas);
}
/**
@@ -640,6 +665,22 @@
}
}
+ /** Draws a line under the app icon if this is representing a running app in Desktop Mode. */
+ protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) {
+ if (!mIsRunning || mDisplay != DISPLAY_TASKBAR) {
+ return;
+ }
+ getIconBounds(mRunningAppIconBounds);
+ // TODO(b/333872717): update color, shape, and size of indicator
+ int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin;
+ canvas.drawRect(
+ mRunningAppIconBounds.centerX() - mRunningAppIndicatorSize.getWidth() / 2,
+ indicatorTop,
+ mRunningAppIconBounds.centerX() + mRunningAppIndicatorSize.getWidth() / 2,
+ indicatorTop + mRunningAppIndicatorSize.getHeight(),
+ mRunningAppIndicatorPaint);
+ }
+
@Override
public void setForceHideDot(boolean forceHideDot) {
if (mForceHideDot == forceHideDot) {
@@ -1230,4 +1271,13 @@
public boolean canShowLongPressPopup() {
return getTag() instanceof ItemInfo && ShortcutUtil.supportsShortcuts((ItemInfo) getTag());
}
+
+ /** Returns the package name of the app this icon represents. */
+ public String getTargetPackageName() {
+ Object tag = getTag();
+ if (tag instanceof ItemInfo itemInfo) {
+ return itemInfo.getTargetPackage();
+ }
+ return null;
+ }
}
diff --git a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
index a309e6e..bc66a33 100644
--- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
+++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
@@ -77,6 +77,7 @@
canvas.restore();
drawDotIfNecessary(canvas);
+ drawRunningAppIndicatorIfNecessary(canvas);
}
public static class ShadowInfo {