diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml index 1b5b0ee4db..14a916f68b 100644 --- a/quickstep/res/values/colors.xml +++ b/quickstep/res/values/colors.xml @@ -31,6 +31,7 @@ #99000000 #EBffffff #99000000 + #646464 #ffffff diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index dbf075cda3..b862d7c07d 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -353,6 +353,9 @@ 48dp 1dp 72dp + 4dp + 14dp + 2dp 12dp diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt index f665e214d7..06d25a21a7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt @@ -80,6 +80,13 @@ class DesktopTaskbarRunningAppsController( return newHotseatItemInfos.toTypedArray() } + override fun getRunningApps(): Set { + if (!isInDesktopMode) { + return emptySet() + } + return allRunningDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet() + } + @VisibleForTesting public override fun updateRunningApps(hotseatItems: SparseArray?) { if (!isInDesktopMode) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java index 6c84f806db..be87cfd8b7 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.HashMap; 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 @@ public class TaskbarModelCallbacks implements } hotseatItemInfos = mControllers.taskbarRecentAppsController .updateHotseatItemInfos(hotseatItemInfos); + Set 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 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 8445cff0ee..9b84f1bf94 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 androidx.annotation.CallSuper; 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 @@ public class TaskbarRecentAppsController { } /** 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 @@ public class TaskbarRecentAppsController { } /** Called to update the list of currently running apps, no-op except in desktop environment. */ - protected void updateRunningApps(SparseArray hotseatItems) { } + protected void updateRunningApps(SparseArray hotseatItems) { + } + + /** Returns the currently running apps, or an empty Set if outside of Desktop environment. */ + public Set getRunningApps() { + return emptySet(); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 5d0eac3c40..e0b446e5a9 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.util.MultiValueAlpha; import com.android.launcher3.views.IconButtonView; import java.io.PrintWriter; +import java.util.Set; import java.util.function.Predicate; /** @@ -507,6 +508,15 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar return mTaskbarView.getTaskbarDividerView(); } + /** Updates which icons are marked as running given the Set of currently running packages. */ + public void updateIconViewsRunningStates(Set 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 93eefe22d7..2cfcf380fd 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 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() { 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()) + } + + @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): List { return packageNames.map { createTestAppInfo(packageName = it) } } diff --git a/res/values/colors.xml b/res/values/colors.xml index a620eb0ff4..dfe40fc30f 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -90,6 +90,8 @@ #D3E3FD #0842A0 + #000000 + #00668B #B5CAD7 #4BB6E8 diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 31def0436b..1bf59e8477 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -394,6 +394,9 @@ 18dp 50dp 0dp + 0dp + 0dp + 0dp 0dp diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 83236d14ff..7f316e2cae 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -46,6 +46,7 @@ import android.text.TextUtils; 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 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, 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 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, 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 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, 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 class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, public void onDraw(Canvas canvas) { super.onDraw(canvas); drawDotIfNecessary(canvas); + drawRunningAppIndicatorIfNecessary(canvas); } /** @@ -640,6 +665,22 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } + /** 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 class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, 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 a309e6e95e..bc66a33b0c 100644 --- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java +++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java @@ -77,6 +77,7 @@ public class DoubleShadowBubbleTextView extends BubbleTextView { canvas.restore(); drawDotIfNecessary(canvas); + drawRunningAppIndicatorIfNecessary(canvas); } public static class ShadowInfo {