Merge "(1/2) Fix NPE for title description broken by ag/28991306. It can be nullable initially before its set." into main
diff --git a/Android.bp b/Android.bp
index def024e..b205d0c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,7 +27,7 @@
"android.os.flags-aconfig-java",
"android.appwidget.flags-aconfig-java",
"com.android.window.flags.window-aconfig-java",
- ]
+ ],
}
// Common source files used to build launcher (java and kotlin)
@@ -153,7 +153,7 @@
soong_config_variables: {
release_enable_compose_in_launcher: {
srcs: [
- ":launcher-compose-enabled-src"
+ ":launcher-compose-enabled-src",
],
// Compose dependencies
@@ -166,7 +166,7 @@
// in compose/launcher3/facade/disabled/.
conditions_default: {
srcs: [
- ":launcher-compose-disabled-src"
+ ":launcher-compose-disabled-src",
],
static_libs: [],
},
@@ -179,7 +179,7 @@
soong_config_variables: {
release_enable_compose_in_launcher: {
srcs: [
- ":launcher-quickstep-compose-enabled-src"
+ ":launcher-quickstep-compose-enabled-src",
],
// Compose dependencies
@@ -192,7 +192,7 @@
// in compose/quickstep/facade/disabled/.
conditions_default: {
srcs: [
- ":launcher-quickstep-compose-disabled-src"
+ ":launcher-quickstep-compose-disabled-src",
],
static_libs: [],
},
@@ -322,6 +322,8 @@
"kotlinx_coroutines",
"com_android_launcher3_flags_lib",
"com_android_wm_shell_flags_lib",
+ "dagger2",
+ "jsr330",
],
manifest: "AndroidManifest-common.xml",
@@ -357,6 +359,7 @@
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
target_sdk_version: "current",
+ plugins: ["dagger2-compiler"],
privileged: true,
system_ext_specific: true,
@@ -392,6 +395,7 @@
"lottie",
"SystemUISharedLib",
"SettingsLibSettingsTheme",
+ "dagger2",
],
manifest: "quickstep/AndroidManifest.xml",
min_sdk_version: "current",
@@ -421,7 +425,10 @@
"QuickstepResLib",
"androidx.room_room-runtime",
],
- plugins: ["androidx.room_room-compiler-plugin"],
+ plugins: [
+ "androidx.room_room-compiler-plugin",
+ "dagger2-compiler",
+ ],
manifest: "quickstep/AndroidManifest.xml",
additional_manifests: [
"go/AndroidManifest.xml",
@@ -437,7 +444,7 @@
name: "Launcher3QuickStepLib",
defaults: [
"launcher_compose_defaults",
- "quickstep_compose_defaults"
+ "quickstep_compose_defaults",
],
srcs: [
":launcher-src",
@@ -458,6 +465,7 @@
],
manifest: "quickstep/AndroidManifest.xml",
platform_apis: true,
+ plugins: ["dagger2-compiler"],
min_sdk_version: "current",
// TODO(b/319712088): re-enable use_resource_processor
use_resource_processor: false,
@@ -500,7 +508,6 @@
}
-
// Build rule for Launcher3 Go app with quickstep for Android Go devices.
// Note that the following two rules are exactly same, and should
// eventually be merged into a single target
@@ -540,6 +547,7 @@
include_filter: ["com.android.launcher3.*"],
},
}
+
android_app {
name: "Launcher3QuickStepGo",
static_libs: ["Launcher3GoLib"],
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index dbcf49c..40c3797 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -316,6 +316,7 @@
description: "Menu in Taskbar with options to launch and manage multiple instances of the same app"
bug: "355237285"
}
+
flag {
name: "navigate_to_child_preference"
namespace: "launcher"
@@ -349,3 +350,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "ignore_three_finger_trackpad_for_nav_handle_long_press"
+ namespace: "launcher"
+ description: "Ignore three finger trackpad event for nav handle long press"
+ bug: "342143522"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/dagger/LauncherAppComponent.java b/quickstep/src/com/android/launcher3/dagger/LauncherAppComponent.java
new file mode 100644
index 0000000..dab2582
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/dagger/LauncherAppComponent.java
@@ -0,0 +1,34 @@
+/*
+ * 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.dagger;
+
+import dagger.Component;
+
+import javax.inject.Singleton;
+
+/**
+ * Root component for Dagger injection for Launcher Quickstep.
+ */
+@Singleton
+@Component
+public interface LauncherAppComponent extends LauncherBaseAppComponent {
+ /** Builder for quickstep LauncherAppComponent. */
+ @Component.Builder
+ interface Builder extends LauncherBaseAppComponent.Builder {
+ LauncherAppComponent build();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index 644705b..e31b1d4 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -36,10 +36,10 @@
import com.android.quickstep.GestureState;
import com.android.quickstep.SystemUiProxy;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import java.util.HashSet;
import java.util.Set;
-import java.util.concurrent.Executor;
/**
* Controls the visibility of the workspace and the resumed / paused state when desktop mode
@@ -51,6 +51,7 @@
private static final boolean DEBUG = false;
private final Launcher mLauncher;
private final Set<DesktopVisibilityListener> mDesktopVisibilityListeners = new HashSet<>();
+ private final Set<TaskbarDesktopModeListener> mTaskbarDesktopModeListeners = new HashSet<>();
private int mVisibleDesktopTasksCount;
private boolean mInOverviewState;
@@ -110,6 +111,16 @@
mDesktopVisibilityListeners.remove(listener);
}
+ /** Registers a listener for Taskbar changes in Desktop Mode. */
+ public void registerTaskbarDesktopModeListener(TaskbarDesktopModeListener listener) {
+ mTaskbarDesktopModeListeners.add(listener);
+ }
+
+ /** Removes a previously registered listener for Taskbar changes in Desktop Mode. */
+ public void unregisterTaskbarDesktopModeListener(TaskbarDesktopModeListener listener) {
+ mTaskbarDesktopModeListeners.remove(listener);
+ }
+
/**
* Sets the number of desktop windows that are visible and updates launcher visibility based on
* it.
@@ -201,6 +212,16 @@
DisplayController.handleInfoChangeForDesktopMode(mLauncher);
}
+ private void notifyTaskbarDesktopModeListeners(boolean doesAnyTaskRequireTaskbarRounding) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyTaskbarDesktopModeListeners: doesAnyTaskRequireTaskbarRounding="
+ + doesAnyTaskRequireTaskbarRounding);
+ }
+ for (TaskbarDesktopModeListener listener : mTaskbarDesktopModeListeners) {
+ listener.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding);
+ }
+ }
+
/**
* TODO: b/333533253 - Remove after flag rollout
*/
@@ -377,7 +398,29 @@
@Override
public void onStashedChanged(int displayId, boolean stashed) {
- Log.w(TAG, "IDesktopTaskListener: onStashedChanged is deprecated");
+ Log.w(TAG, "DesktopTaskListenerImpl: onStashedChanged is deprecated");
}
+
+ @Override
+ public void onTaskbarCornerRoundingUpdate(boolean doesAnyTaskRequireTaskbarRounding) {
+ MAIN_EXECUTOR.execute(() -> {
+ if (mController != null && DesktopModeStatus.useRoundedCorners()) {
+ Log.d(TAG, "DesktopTaskListenerImpl: doesAnyTaskRequireTaskbarRounding= "
+ + doesAnyTaskRequireTaskbarRounding);
+ mController.notifyTaskbarDesktopModeListeners(
+ doesAnyTaskRequireTaskbarRounding);
+ }
+ });
+ }
+ }
+
+ /** A listener for Taskbar in Desktop Mode. */
+ public interface TaskbarDesktopModeListener {
+ /**
+ * Callback for when task is resized in desktop mode.
+ *
+ * @param doesAnyTaskRequireTaskbarRounding whether task requires taskbar corner roundness.
+ */
+ void onTaskbarCornerRoundingUpdate(boolean doesAnyTaskRequireTaskbarRounding);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index f61840a..a979d58 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -63,7 +63,6 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
-import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.graphics.drawable.RotateDrawable;
@@ -74,8 +73,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
-import android.view.View.OnClickListener;
-import android.view.View.OnHoverListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
@@ -106,7 +103,6 @@
import com.android.systemui.shared.navigationbar.KeyButtonRipple;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton;
-import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -304,8 +300,13 @@
.get(ALPHA_INDEX_SMALL_SCREEN),
flags -> (flags & FLAG_SMALL_SCREEN) == 0));
- mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
- .getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));
+ if (!mContext.isPhoneMode()) {
+ mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
+ .getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));
+ }
+
+ // Start at 1 because relevant flags are unset at init.
+ mOnBackgroundNavButtonColorOverrideMultiplier.value = 1;
// Force nav buttons (specifically back button) to be visible during setup wizard.
boolean isInSetup = !mContext.isUserSetupComplete();
@@ -317,39 +318,41 @@
// - IME is showing (add separate translation for IME)
// - VoiceInteractionWindow (assistant) is showing
// - Keyboard shortcuts helper is showing
- int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
- | FLAG_VOICE_INTERACTION_WINDOW_SHOWING | FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING;
- mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
- flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
- 1, 0));
- // Center nav buttons in new height for IME.
- float transForIme = (mContext.getDeviceProfile().taskbarHeight
- - mControllers.taskbarInsetsController.getTaskbarHeightForIme()) / 2f;
- // For gesture nav, nav buttons only show for IME anyway so keep them translated down.
- float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
- mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
- flags -> (flags & FLAG_IME_VISIBLE) != 0 && !isInKidsMode, AnimatedFloat.VALUE,
- transForIme, defaultButtonTransY));
+ if (!mContext.isPhoneMode()) {
+ int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
+ | FLAG_VOICE_INTERACTION_WINDOW_SHOWING | FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING;
+ mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
+ flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
+ 1, 0));
+ // Center nav buttons in new height for IME.
+ float transForIme = (mContext.getDeviceProfile().taskbarHeight
+ - mControllers.taskbarInsetsController.getTaskbarHeightForIme()) / 2f;
+ // For gesture nav, nav buttons only show for IME anyway so keep them translated down.
+ float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
+ mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
+ flags -> (flags & FLAG_IME_VISIBLE) != 0 && !isInKidsMode, AnimatedFloat.VALUE,
+ transForIme, defaultButtonTransY));
- // Start at 1 because relevant flags are unset at init.
- mOnBackgroundNavButtonColorOverrideMultiplier.value = 1;
- mPropertyHolders.add(new StatePropertyHolder(
- mOnBackgroundNavButtonColorOverrideMultiplier,
- flags -> (flags & FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED) == 0));
+ mPropertyHolders.add(new StatePropertyHolder(
+ mOnBackgroundNavButtonColorOverrideMultiplier,
+ flags -> (flags & FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED) == 0));
- mPropertyHolders.add(new StatePropertyHolder(
- mSlideInViewVisibleNavButtonColorOverride,
- flags -> (flags & FLAG_SLIDE_IN_VIEW_VISIBLE) != 0));
+ mPropertyHolders.add(new StatePropertyHolder(
+ mSlideInViewVisibleNavButtonColorOverride,
+ flags -> (flags & FLAG_SLIDE_IN_VIEW_VISIBLE) != 0));
+ }
if (alwaysShowButtons) {
initButtons(mNavButtonContainer, mEndContextualContainer,
mControllers.navButtonController);
updateButtonLayoutSpacing();
- updateStateForFlag(FLAG_SMALL_SCREEN, mContext.isPhoneButtonNavMode());
+ updateStateForFlag(FLAG_SMALL_SCREEN, mContext.isPhoneMode());
- mPropertyHolders.add(new StatePropertyHolder(
- mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
- flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
+ if (!mContext.isPhoneMode()) {
+ mPropertyHolders.add(new StatePropertyHolder(
+ mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
+ flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
+ }
} else if (!mIsImeRenderingNavButtons) {
View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
mStartContextualContainer, mControllers.navButtonController, R.id.back);
@@ -711,7 +714,7 @@
private void applyState() {
int count = mPropertyHolders.size();
for (int i = 0; i < count; i++) {
- mPropertyHolders.get(i).setState(mState);
+ mPropertyHolders.get(i).setState(mState, mContext.isGestureNav());
}
}
@@ -1177,83 +1180,6 @@
}
}
- private class RotationButtonImpl implements RotationButton {
-
- private final ImageView mButton;
- private AnimatedVectorDrawable mImageDrawable;
-
- RotationButtonImpl(ImageView button) {
- mButton = button;
- }
-
- @Override
- public void setRotationButtonController(RotationButtonController rotationButtonController) {
- // TODO(b/187754252) UI polish, different icons based on light/dark context, etc
- mImageDrawable = (AnimatedVectorDrawable) mButton.getContext()
- .getDrawable(rotationButtonController.getIconResId());
- mButton.setImageDrawable(mImageDrawable);
- mButton.setContentDescription(mButton.getResources()
- .getString(R.string.accessibility_rotate_button));
- mImageDrawable.setCallback(mButton);
- }
-
- @Override
- public View getCurrentView() {
- return mButton;
- }
-
- @Override
- public boolean show() {
- mButton.setVisibility(View.VISIBLE);
- mState |= FLAG_ROTATION_BUTTON_VISIBLE;
- applyState();
- return true;
- }
-
- @Override
- public boolean hide() {
- mButton.setVisibility(View.GONE);
- mState &= ~FLAG_ROTATION_BUTTON_VISIBLE;
- applyState();
- return true;
- }
-
- @Override
- public boolean isVisible() {
- return mButton.getVisibility() == View.VISIBLE;
- }
-
- @Override
- public void updateIcon(int lightIconColor, int darkIconColor) {
- // TODO(b/187754252): UI Polish
- }
-
- @Override
- public void setOnClickListener(OnClickListener onClickListener) {
- mButton.setOnClickListener(onClickListener);
- }
-
- @Override
- public void setOnHoverListener(OnHoverListener onHoverListener) {
- mButton.setOnHoverListener(onHoverListener);
- }
-
- @Override
- public AnimatedVectorDrawable getImageDrawable() {
- return mImageDrawable;
- }
-
- @Override
- public void setDarkIntensity(float darkIntensity) {
- // TODO(b/187754252) UI polish
- }
-
- @Override
- public boolean acceptRotationProposal() {
- return mButton.isAttachedToWindow();
- }
- }
-
private static class StatePropertyHolder {
private final float mEnabledValue, mDisabledValue;
@@ -1284,13 +1210,16 @@
mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue);
}
- public void setState(int flags) {
+ public void setState(int flags, boolean skipAnimation) {
boolean isEnabled = mEnableCondition.test(flags);
if (mIsEnabled != isEnabled) {
mIsEnabled = isEnabled;
mAnimator.cancel();
mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue);
mAnimator.start();
+ if (skipAnimation) {
+ mAnimator.end();
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 487bc54..0c1235c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -348,8 +348,9 @@
new KeyboardQuickSwitchController(),
new TaskbarPinningController(this, () ->
DisplayController.isInDesktopMode(this)),
- bubbleControllersOptional);
-
+ bubbleControllersOptional,
+ new TaskbarDesktopModeController(
+ LauncherActivityInterface.INSTANCE::getDesktopVisibilityController));
mLauncherPrefs = LauncherPrefs.get(this);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index ee64060..4ac7514 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -24,6 +24,7 @@
import android.graphics.Rect
import android.graphics.RectF
import com.android.app.animation.Interpolators
+import com.android.internal.policy.ScreenDecorationsUtils
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapRange
@@ -70,8 +71,8 @@
private var keyShadowDistance = 0f
private var bottomMargin = 0
- private val fullCornerRadius = context.cornerRadius.toFloat()
- private var cornerRadius = fullCornerRadius
+ private val fullCornerRadius: Float
+ private var cornerRadius = 0f
private var widthInsetPercentage = 0f
private val square = Path()
private val circle = Path()
@@ -101,7 +102,14 @@
shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
}
- setCornerRoundness(DEFAULT_ROUNDNESS)
+ if (DisplayController.isInDesktopMode(context)) {
+ fullCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+ cornerRadius = fullCornerRadius
+ } else {
+ fullCornerRadius = context.cornerRadius.toFloat()
+ cornerRadius = fullCornerRadius
+ setCornerRoundness(MAX_ROUNDNESS)
+ }
}
fun updateStashedHandleWidth(context: TaskbarActivityContext, res: Resources) {
@@ -273,7 +281,7 @@
}
companion object {
- const val DEFAULT_ROUNDNESS = 1f
+ const val MAX_ROUNDNESS = 1f
private const val DARK_THEME_STROKE_ALPHA = 51
private const val LIGHT_THEME_STROKE_ALPHA = 41
private const val DARK_THEME_SHADOW_ALPHA = 51f
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index d94d9175..34ab9f0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -25,6 +25,7 @@
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
+import com.android.launcher3.util.DisplayController;
import com.android.systemui.shared.rotation.RotationButtonController;
import java.io.PrintWriter;
@@ -64,6 +65,7 @@
public final KeyboardQuickSwitchController keyboardQuickSwitchController;
public final TaskbarPinningController taskbarPinningController;
public final Optional<BubbleControllers> bubbleControllers;
+ public final TaskbarDesktopModeController taskbarDesktopModeController;
@Nullable private LoggableTaskbarController[] mControllersToLog = null;
@Nullable private BackgroundRendererController[] mBackgroundRendererControllers = null;
@@ -111,7 +113,8 @@
TaskbarEduTooltipController taskbarEduTooltipController,
KeyboardQuickSwitchController keyboardQuickSwitchController,
TaskbarPinningController taskbarPinningController,
- Optional<BubbleControllers> bubbleControllers) {
+ Optional<BubbleControllers> bubbleControllers,
+ TaskbarDesktopModeController taskbarDesktopModeController) {
this.taskbarActivityContext = taskbarActivityContext;
this.taskbarDragController = taskbarDragController;
this.navButtonController = navButtonController;
@@ -138,6 +141,7 @@
this.keyboardQuickSwitchController = keyboardQuickSwitchController;
this.taskbarPinningController = taskbarPinningController;
this.bubbleControllers = bubbleControllers;
+ this.taskbarDesktopModeController = taskbarDesktopModeController;
}
/**
@@ -173,6 +177,7 @@
taskbarEduTooltipController.init(this);
keyboardQuickSwitchController.init(this);
taskbarPinningController.init(this, mSharedState);
+ taskbarDesktopModeController.init(this, mSharedState);
mControllersToLog = new LoggableTaskbarController[] {
taskbarDragController, navButtonController, navbarButtonsViewController,
@@ -188,7 +193,13 @@
taskbarDragLayerController, taskbarScrimViewController,
voiceInteractionWindowController
};
- mCornerRoundness.updateValue(TaskbarBackgroundRenderer.DEFAULT_ROUNDNESS);
+
+ if (DisplayController.isInDesktopMode(taskbarActivityContext)) {
+ mCornerRoundness.updateValue(taskbarDesktopModeController.getTaskbarCornerRoundness(
+ mSharedState.showCornerRadiusInDesktopMode));
+ } else {
+ mCornerRoundness.updateValue(TaskbarBackgroundRenderer.MAX_ROUNDNESS);
+ }
mAreAllControllersInitialized = true;
for (Runnable postInitCallback : mPostInitCallbacks) {
@@ -248,6 +259,7 @@
keyboardQuickSwitchController.onDestroy();
taskbarStashController.onDestroy();
bubbleControllers.ifPresent(controllers -> controllers.onDestroy());
+ taskbarDesktopModeController.onDestroy();
mControllersToLog = null;
mBackgroundRendererControllers = null;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
new file mode 100644
index 0000000..a376531
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
@@ -0,0 +1,54 @@
+/*
+ * 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
+
+import com.android.launcher3.statehandlers.DesktopVisibilityController
+import com.android.launcher3.statehandlers.DesktopVisibilityController.TaskbarDesktopModeListener
+import com.android.launcher3.taskbar.TaskbarBackgroundRenderer.Companion.MAX_ROUNDNESS
+
+/** Handles Taskbar in Desktop Windowing mode. */
+class TaskbarDesktopModeController(
+ private val desktopVisibilityControllerProvider: () -> DesktopVisibilityController?
+) : TaskbarDesktopModeListener {
+ private lateinit var taskbarControllers: TaskbarControllers
+ private lateinit var taskbarSharedState: TaskbarSharedState
+
+ private val desktopVisibilityController: DesktopVisibilityController?
+ get() = desktopVisibilityControllerProvider()
+
+ fun init(controllers: TaskbarControllers, sharedState: TaskbarSharedState) {
+ taskbarControllers = controllers
+ taskbarSharedState = sharedState
+ desktopVisibilityController?.registerTaskbarDesktopModeListener(this)
+ }
+
+ override fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {
+ taskbarSharedState.showCornerRadiusInDesktopMode = doesAnyTaskRequireTaskbarRounding
+ val cornerRadius = getTaskbarCornerRoundness(doesAnyTaskRequireTaskbarRounding)
+ taskbarControllers.taskbarCornerRoundness.animateToValue(cornerRadius).start()
+ }
+
+ fun getTaskbarCornerRoundness(doesAnyTaskRequireTaskbarRounding: Boolean): Float {
+ return if (doesAnyTaskRequireTaskbarRounding) {
+ MAX_ROUNDNESS
+ } else {
+ 0f
+ }
+ }
+
+ fun onDestroy() = desktopVisibilityController?.unregisterTaskbarDesktopModeListener(this)
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 0eb8890..63fae8c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -48,6 +48,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationController;
@@ -591,6 +592,12 @@
float cornerRoundness = isInLauncher ? 0 : 1;
+ if (DisplayController.isInDesktopMode(mLauncher) && mControllers.getSharedState() != null) {
+ cornerRoundness =
+ mControllers.taskbarDesktopModeController.getTaskbarCornerRoundness(
+ mControllers.getSharedState().showCornerRadiusInDesktopMode);
+ }
+
// Don't animate if corner roundness has reached desired value.
if (mTaskbarCornerRoundness.isAnimating()
|| mTaskbarCornerRoundness.value != cornerRoundness) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index 77bd35f..729cbe9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -102,5 +102,8 @@
// To track if taskbar was stashed / unstashed between configuration changes (which recreates
// the task bar).
- public Boolean taskbarWasStashedAuto = true;
+ public boolean taskbarWasStashedAuto = true;
+
+ // should show corner radius on persistent taskbar when in desktop mode.
+ public boolean showCornerRadiusInDesktopMode = false;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index aef21aa..27c1d9c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -219,8 +219,8 @@
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
// This gets modified in NavbarButtonsViewController, but the initial value it reads
// may be incorrect since it's state gets destroyed on taskbar recreate, so reset here
- mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
- .animateToValue(mActivity.isPhoneButtonNavMode() ? 0 : 1).start();
+ mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN).setValue(
+ mActivity.isPhoneMode() ? 0 : 1);
}
if (enableTaskbarPinning()) {
mTaskbarView.addOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 84f6b55..a03c0f8 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -37,6 +37,7 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.window.CachedDisplayInfo;
+import com.android.systemui.shared.Flags;
import java.io.PrintWriter;
import java.util.HashMap;
@@ -242,7 +243,8 @@
int rotation = display.rotation;
int touchHeight = mNavBarGesturalHeight;
OrientationRectF orientationRectF = new OrientationRectF(0, 0, size.x, size.y, rotation);
- if (mMode == NavigationMode.NO_BUTTON) {
+ if (mMode == NavigationMode.NO_BUTTON
+ || (mMode == NavigationMode.THREE_BUTTONS && Flags.threeButtonCornerSwipe())) {
orientationRectF.top = orientationRectF.bottom - touchHeight;
updateAssistantRegions(orientationRectF);
} else {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index f902284..5131774 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -74,6 +74,7 @@
import com.android.quickstep.util.GestureExclusionManager;
import com.android.quickstep.util.GestureExclusionManager.ExclusionListener;
import com.android.quickstep.util.NavBarPosition;
+import com.android.systemui.shared.Flags;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -548,6 +549,13 @@
}
/**
+ * @return whether the Assistant gesture can be used in 3 button navigation mode.
+ */
+ public boolean supportsAssistantGestureInButtonNav() {
+ return Flags.threeButtonCornerSwipe();
+ }
+
+ /**
* @param ev An ACTION_DOWN motion event
* @return whether the given motion event can trigger the assistant over the current task.
*/
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index db03dac..a01ceb2 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -43,6 +43,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.recents.data.RecentTasksDataSource;
+import com.android.quickstep.recents.data.TaskVisualsChangeNotifier;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TaskVisualsChangeListener;
@@ -65,7 +66,8 @@
*/
@TargetApi(Build.VERSION_CODES.O)
public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
- TaskStackChangeListener, TaskVisualsChangeListener, SafeCloseable {
+ TaskStackChangeListener, TaskVisualsChangeListener, TaskVisualsChangeNotifier,
+ SafeCloseable {
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
@@ -287,6 +289,7 @@
/**
* Adds a listener for visuals changes
*/
+ @Override
public void addThumbnailChangeListener(TaskVisualsChangeListener listener) {
mThumbnailChangeListeners.add(listener);
}
@@ -294,6 +297,7 @@
/**
* Removes a previously added listener
*/
+ @Override
public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) {
mThumbnailChangeListeners.remove(listener);
}
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 6f1ab7d..80c07196 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -41,6 +41,7 @@
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.util.RecentsOrientedState;
+import com.android.systemui.shared.Flags;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -157,7 +158,7 @@
// Register for navigation mode changes
mDisplayController.addChangeListener(this);
DisplayController.Info info = mDisplayController.getInfo();
- onDisplayInfoChangedInternal(info, CHANGE_ALL, info.getNavigationMode().hasGestures);
+ onDisplayInfoChangedInternal(info, CHANGE_ALL, hasGestures(info.getNavigationMode()));
runOnDestroy(() -> mDisplayController.removeChangeListener(this));
mOrientationListener = new OrientationEventListener(mContext) {
@@ -229,7 +230,7 @@
* Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
*/
public void updateGestureTouchRegions() {
- if (!mMode.hasGestures) {
+ if (!hasGestures(mMode)) {
return;
}
@@ -268,7 +269,7 @@
| CHANGE_SUPPORTED_BOUNDS)) != 0) {
mDisplayRotation = info.rotation;
- if (mMode.hasGestures) {
+ if (hasGestures(mMode)) {
updateGestureTouchRegions();
mOrientationTouchTransformer.createOrAddTouchRegion(info);
mCurrentAppRotation = mDisplayRotation;
@@ -295,9 +296,9 @@
mOrientationTouchTransformer.setNavigationMode(newMode, mDisplayController.getInfo(),
mContext.getResources());
- if (forceRegister || (!mMode.hasGestures && newMode.hasGestures)) {
+ if (forceRegister || (!hasGestures(mMode) && hasGestures(newMode))) {
setupOrientationSwipeHandler();
- } else if (mMode.hasGestures && !newMode.hasGestures) {
+ } else if (hasGestures(mMode) && !hasGestures(newMode)) {
destroyOrientationSwipeHandlerCallback();
}
@@ -399,7 +400,7 @@
}
public int getCurrentActiveRotation() {
- if (!mMode.hasGestures) {
+ if (!hasGestures(mMode)) {
// touch rotation should always match that of display for 3 button
return mDisplayRotation;
}
@@ -416,4 +417,8 @@
public OrientationTouchTransformer getOrientationTouchTransformer() {
return mOrientationTouchTransformer;
}
+
+ private boolean hasGestures(NavigationMode mode) {
+ return mode.hasGestures || (mode == THREE_BUTTONS && Flags.threeButtonCornerSwipe());
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 3c6c3e4..580dcc2 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -27,6 +27,7 @@
import com.android.launcher3.R;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.recents.data.HighResLoadingStateNotifier;
import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource;
import com.android.quickstep.util.TaskKeyByLastActiveTimeCache;
import com.android.quickstep.util.TaskKeyCache;
@@ -48,7 +49,7 @@
private final boolean mEnableTaskSnapshotPreloading;
private final Context mContext;
- public static class HighResLoadingState {
+ public static class HighResLoadingState implements HighResLoadingStateNotifier {
private boolean mForceHighResThumbnails;
private boolean mVisible;
private boolean mFlingingFast;
@@ -65,11 +66,13 @@
mForceHighResThumbnails = !supportsLowResThumbnails();
}
- public void addCallback(HighResLoadingStateChangedCallback callback) {
+ @Override
+ public void addCallback(@NonNull HighResLoadingStateChangedCallback callback) {
mCallbacks.add(callback);
}
- public void removeCallback(HighResLoadingStateChangedCallback callback) {
+ @Override
+ public void removeCallback(@NonNull HighResLoadingStateChangedCallback callback) {
mCallbacks.remove(callback);
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 2b5aa71..b321b3e 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -89,6 +89,7 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ConstantItem;
import com.android.launcher3.EncryptionType;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.config.FeatureFlags;
@@ -674,8 +675,9 @@
private void initInputMonitor(String reason) {
disposeEventHandlers("Initializing input monitor due to: " + reason);
- if (mDeviceState.isButtonNavMode() && (!ENABLE_TRACKPAD_GESTURE.get()
- || mTrackpadsConnected.isEmpty())) {
+ if (mDeviceState.isButtonNavMode()
+ && !mDeviceState.supportsAssistantGestureInButtonNav()
+ && (!ENABLE_TRACKPAD_GESTURE.get() || mTrackpadsConnected.isEmpty())) {
return;
}
@@ -857,7 +859,9 @@
.append("); cancelling gesture."),
NAVIGATION_MODE_SWITCHED);
event.setAction(ACTION_CANCEL);
- } else if (mDeviceState.isButtonNavMode() && !isTrackpadMotionEvent(event)) {
+ } else if (mDeviceState.isButtonNavMode()
+ && !mDeviceState.supportsAssistantGestureInButtonNav()
+ && !isTrackpadMotionEvent(event)) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
.append("Cannot process input event: ")
.append("using 3-button nav and event is not a trackpad event"));
@@ -909,7 +913,22 @@
if (isInSwipeUpTouchRegion && tac != null) {
tac.closeKeyboardQuickSwitchView();
}
- if ((!isOneHandedModeActive && isInSwipeUpTouchRegion)
+ if (mDeviceState.isButtonNavMode()
+ && mDeviceState.supportsAssistantGestureInButtonNav()) {
+ reasonString.append("in three button mode which supports Assistant gesture");
+ // Consume gesture event for Assistant (all other gestures should do nothing).
+ if (mDeviceState.canTriggerAssistantAction(event)) {
+ reasonString.append(" and event can trigger assistant action")
+ .append(", consuming gesture for assistant action");
+ mGestureState =
+ createGestureState(mGestureState, getTrackpadGestureType(event));
+ mUncheckedConsumer = tryCreateAssistantInputConsumer(mGestureState, event);
+ } else {
+ reasonString.append(" but event cannot trigger Assistant")
+ .append(", consuming gesture as no-op");
+ mUncheckedConsumer = InputConsumer.NO_OP;
+ }
+ } else if ((!isOneHandedModeActive && isInSwipeUpTouchRegion)
|| isHoverActionWithoutConsumer || isOnBubbles) {
reasonString.append(!isOneHandedModeActive && isInSwipeUpTouchRegion
? "one handed mode is not active and event is in swipe up region"
@@ -931,8 +950,7 @@
: "event is a trackpad multi-finger swipe")
.append(" and event can trigger assistant action")
.append(", consuming gesture for assistant action");
- mGestureState = createGestureState(mGestureState,
- getTrackpadGestureType(event));
+ mGestureState = createGestureState(mGestureState, getTrackpadGestureType(event));
// Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
// should not interrupt it. QuickSwitch assumes that interruption can only
// happen if the next gesture is also quick switch.
@@ -1193,7 +1211,8 @@
NavHandle navHandle = tac != null ? tac.getNavHandle()
: SystemUiProxy.INSTANCE.get(this);
if (canStartSystemGesture && !previousGestureState.isRecentsAnimationRunning()
- && navHandle.canNavHandleBeLongPressed()) {
+ && navHandle.canNavHandleBeLongPressed()
+ && !ignoreThreeFingerTrackpadForNavHandleLongPress(mGestureState)) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
.append(SUBSTRING_PREFIX)
@@ -1289,6 +1308,11 @@
return new CompoundString(NEWLINE_PREFIX).append(substring);
}
+ private boolean ignoreThreeFingerTrackpadForNavHandleLongPress(GestureState gestureState) {
+ return Flags.ignoreThreeFingerTrackpadForNavHandleLongPress()
+ && gestureState.isThreeFingerTrackpadGesture();
+ }
+
private void logInputConsumerSelectionReason(
InputConsumer consumer, CompoundString reasonString) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("setInputConsumer: ")
diff --git a/quickstep/src/com/android/quickstep/recents/data/HighResLoadingStateNotifier.kt b/quickstep/src/com/android/quickstep/recents/data/HighResLoadingStateNotifier.kt
new file mode 100644
index 0000000..df546ca
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/HighResLoadingStateNotifier.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.quickstep.recents.data
+
+import com.android.quickstep.TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback
+
+/** Notifies added callbacks that high res state has changed */
+interface HighResLoadingStateNotifier {
+ /** Adds a callback for high res loading state */
+ fun addCallback(callback: HighResLoadingStateChangedCallback)
+
+ /** Removes a callback for high res loading state */
+ fun removeCallback(callback: HighResLoadingStateChangedCallback)
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
index 4f7a541..9c4248c 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
@@ -41,10 +41,4 @@
* populated e.g. icons/thumbnails etc.
*/
fun setVisibleTasks(visibleTaskIdList: List<Int>)
-
- /**
- * Override [ThumbnailData] with a map of taskId to [ThumbnailData]. The override only applies
- * if the tasks are already visible, and will be invalidated when tasks become invisible.
- */
- fun addOrUpdateThumbnailOverride(thumbnailOverride: Map<Int, ThumbnailData>)
}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangeNotifier.kt b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangeNotifier.kt
new file mode 100644
index 0000000..6e7789d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangeNotifier.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.quickstep.recents.data
+
+import com.android.quickstep.util.TaskVisualsChangeListener
+
+/** Notifies added listeners that task visuals have changed */
+interface TaskVisualsChangeNotifier {
+ /** Adds a listener for visuals changes */
+ fun addThumbnailChangeListener(listener: TaskVisualsChangeListener)
+
+ /** Removes a listener for visuals changes */
+ fun removeThumbnailChangeListener(listener: TaskVisualsChangeListener)
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
new file mode 100644
index 0000000..a141e89
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.quickstep.recents.data
+
+import android.os.UserHandle
+import com.android.quickstep.TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
+import com.android.quickstep.util.TaskVisualsChangeListener
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/** Delegates the checking of task visuals (thumbnails, high res changes, icons) */
+interface TaskVisualsChangedDelegate :
+ TaskVisualsChangeListener, HighResLoadingStateChangedCallback {
+ /** Registers a callback for visuals relating to icons */
+ fun registerTaskIconChangedCallback(
+ taskKey: Task.TaskKey,
+ taskIconChangedCallback: TaskIconChangedCallback
+ )
+
+ /** Unregisters a callback for visuals relating to icons */
+ fun unregisterTaskIconChangedCallback(taskKey: Task.TaskKey)
+
+ /** Registers a callback for visuals relating to thumbnails */
+ fun registerTaskThumbnailChangedCallback(
+ taskKey: Task.TaskKey,
+ taskThumbnailChangedCallback: TaskThumbnailChangedCallback
+ )
+
+ /** Unregisters a callback for visuals relating to thumbnails */
+ fun unregisterTaskThumbnailChangedCallback(taskKey: Task.TaskKey)
+
+ /** A callback for task icon changes */
+ interface TaskIconChangedCallback {
+ /** Informs the listener that the task icon has changed */
+ fun onTaskIconChanged()
+ }
+
+ /** A callback for task thumbnail changes */
+ interface TaskThumbnailChangedCallback {
+ /** Informs the listener that the task thumbnail data has changed to [thumbnailData] */
+ fun onTaskThumbnailChanged(thumbnailData: ThumbnailData?)
+
+ /** Informs the listener that the default resolution for loading thumbnails has changed */
+ fun onHighResLoadingStateChanged()
+ }
+}
+
+class TaskVisualsChangedDelegateImpl(
+ private val taskVisualsChangeNotifier: TaskVisualsChangeNotifier,
+ private val highResLoadingStateNotifier: HighResLoadingStateNotifier,
+) : TaskVisualsChangedDelegate {
+ private val taskIconChangedCallbacks =
+ mutableMapOf<Int, Pair<Task.TaskKey, TaskIconChangedCallback>>()
+ private val taskThumbnailChangedCallbacks =
+ mutableMapOf<Int, Pair<Task.TaskKey, TaskThumbnailChangedCallback>>()
+ private var isListening = false
+
+ @Synchronized
+ private fun onCallbackRegistered() {
+ if (isListening) return
+
+ taskVisualsChangeNotifier.addThumbnailChangeListener(this)
+ highResLoadingStateNotifier.addCallback(this)
+ isListening = true
+ }
+
+ @Synchronized
+ private fun onCallbackUnregistered() {
+ if (!isListening) return
+
+ if (taskIconChangedCallbacks.size + taskThumbnailChangedCallbacks.size == 0) {
+ taskVisualsChangeNotifier.removeThumbnailChangeListener(this)
+ highResLoadingStateNotifier.removeCallback(this)
+ }
+
+ isListening = false
+ }
+
+ override fun onTaskIconChanged(taskId: Int) {
+ taskIconChangedCallbacks[taskId]?.let { (_, callback) -> callback.onTaskIconChanged() }
+ }
+
+ override fun onTaskIconChanged(pkg: String, user: UserHandle) {
+ taskIconChangedCallbacks.values
+ .filter { (taskKey, _) ->
+ pkg == taskKey.packageName && user.identifier == taskKey.userId
+ }
+ .forEach { (_, callback) -> callback.onTaskIconChanged() }
+ }
+
+ override fun onTaskThumbnailChanged(taskId: Int, thumbnailData: ThumbnailData?): Task? {
+ taskThumbnailChangedCallbacks[taskId]?.let { (_, callback) ->
+ callback.onTaskThumbnailChanged(thumbnailData)
+ }
+ return null
+ }
+
+ override fun onHighResLoadingStateChanged(enabled: Boolean) {
+ taskThumbnailChangedCallbacks.values.forEach { (_, callback) ->
+ callback.onHighResLoadingStateChanged()
+ }
+ }
+
+ override fun registerTaskIconChangedCallback(
+ taskKey: Task.TaskKey,
+ taskIconChangedCallback: TaskIconChangedCallback
+ ) {
+ taskIconChangedCallbacks[taskKey.id] = taskKey to taskIconChangedCallback
+ onCallbackRegistered()
+ }
+
+ override fun unregisterTaskIconChangedCallback(taskKey: Task.TaskKey) {
+ taskIconChangedCallbacks.remove(taskKey.id)
+ onCallbackUnregistered()
+ }
+
+ override fun registerTaskThumbnailChangedCallback(
+ taskKey: Task.TaskKey,
+ taskThumbnailChangedCallback: TaskThumbnailChangedCallback
+ ) {
+ taskThumbnailChangedCallbacks[taskKey.id] = taskKey to taskThumbnailChangedCallback
+ onCallbackRegistered()
+ }
+
+ override fun unregisterTaskThumbnailChangedCallback(taskKey: Task.TaskKey) {
+ taskThumbnailChangedCallbacks.remove(taskKey.id)
+ onCallbackUnregistered()
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 6f9d157..eb3c2d1 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -18,6 +18,8 @@
import android.graphics.drawable.Drawable
import com.android.launcher3.util.coroutines.DispatcherProvider
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
import com.android.quickstep.task.thumbnail.data.TaskIconDataSource
import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
import com.android.quickstep.util.GroupTask
@@ -26,18 +28,20 @@
import kotlin.coroutines.resume
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -46,12 +50,12 @@
private val recentsModel: RecentTasksDataSource,
private val taskThumbnailDataSource: TaskThumbnailDataSource,
private val taskIconDataSource: TaskIconDataSource,
+ private val taskVisualsChangedDelegate: TaskVisualsChangedDelegate,
recentsCoroutineScope: CoroutineScope,
private val dispatcherProvider: DispatcherProvider,
) : RecentTasksRepository {
private val groupedTaskData = MutableStateFlow(emptyList<GroupTask>())
private val visibleTaskIds = MutableStateFlow(emptySet<Int>())
- private val thumbnailOverride = MutableStateFlow(mapOf<Int, ThumbnailData>())
private val taskData =
groupedTaskData.map { groupTaskList -> groupTaskList.flatMap { it.tasks } }
@@ -85,15 +89,13 @@
.distinctUntilChanged()
private val augmentedTaskData: Flow<List<Task>> =
- combine(taskData, thumbnailQueryResults, iconQueryResults, thumbnailOverride) {
+ combine(taskData, thumbnailQueryResults, iconQueryResults) {
tasks,
thumbnailQueryResults,
- iconQueryResults,
- thumbnailOverride ->
+ iconQueryResults ->
tasks.onEach { task ->
// Add retrieved thumbnails + remove unnecessary thumbnails (e.g. invisible)
- task.thumbnail =
- thumbnailOverride[task.key.id] ?: thumbnailQueryResults[task.key.id]
+ task.thumbnail = thumbnailQueryResults[task.key.id]
// TODO(b/352331675) don't load icons for DesktopTaskView
// Add retrieved icons + remove unnecessary icons
@@ -121,61 +123,76 @@
override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
this.visibleTaskIds.value = visibleTaskIdList.toSet()
- addOrUpdateThumbnailOverride(emptyMap())
- }
-
- override fun addOrUpdateThumbnailOverride(thumbnailOverride: Map<Int, ThumbnailData>) {
- this.thumbnailOverride.value =
- this.thumbnailOverride.value
- .toMutableMap()
- .apply { putAll(thumbnailOverride) }
- .filterKeys(this.visibleTaskIds.value::contains)
}
/** Flow wrapper for [TaskThumbnailDataSource.getThumbnailInBackground] api */
- private fun getThumbnailDataRequest(task: Task): ThumbnailDataRequest = flow {
- emit(task.key.id to task.thumbnail)
- val thumbnailDataResult: ThumbnailData? =
- withContext(dispatcherProvider.main) {
- suspendCancellableCoroutine { continuation ->
- val cancellableTask =
- taskThumbnailDataSource.getThumbnailInBackground(task) {
- continuation.resume(it)
- }
- continuation.invokeOnCancellation { cancellableTask?.cancel() }
+ private fun getThumbnailDataRequest(task: Task): ThumbnailDataRequest = callbackFlow {
+ trySend(task.key.id to task.thumbnail)
+ trySend(task.key.id to getThumbnailFromDataSource(task))
+
+ val callback =
+ object : TaskThumbnailChangedCallback {
+ override fun onTaskThumbnailChanged(thumbnailData: ThumbnailData?) {
+ trySend(task.key.id to thumbnailData)
+ }
+
+ override fun onHighResLoadingStateChanged() {
+ launch { trySend(task.key.id to getThumbnailFromDataSource(task)) }
}
}
- emit(task.key.id to thumbnailDataResult)
+ taskVisualsChangedDelegate.registerTaskThumbnailChangedCallback(task.key, callback)
+ awaitClose { taskVisualsChangedDelegate.unregisterTaskThumbnailChangedCallback(task.key) }
}
- /** Flow wrapper for [TaskThumbnailDataSource.getThumbnailInBackground] api */
+ /** Flow wrapper for [TaskIconDataSource.getIconInBackground] api */
private fun getIconDataRequest(task: Task): IconDataRequest =
- flow {
- emit(task.key.id to task.getTaskIconQueryResponse())
- val iconDataResponse: TaskIconQueryResponse? =
- withContext(dispatcherProvider.main) {
- suspendCancellableCoroutine { continuation ->
- val cancellableTask =
- taskIconDataSource.getIconInBackground(task) {
- icon,
- contentDescription,
- title ->
- icon.constantState?.let {
- continuation.resume(
- TaskIconQueryResponse(
- it.newDrawable().mutate(),
- contentDescription,
- title
- )
- )
- }
- }
- continuation.invokeOnCancellation { cancellableTask?.cancel() }
+ callbackFlow {
+ trySend(task.key.id to task.getTaskIconQueryResponse())
+ trySend(task.key.id to getIconFromDataSource(task))
+
+ val callback =
+ object : TaskIconChangedCallback {
+ override fun onTaskIconChanged() {
+ launch { trySend(task.key.id to getIconFromDataSource(task)) }
}
}
- emit(task.key.id to iconDataResponse)
+ taskVisualsChangedDelegate.registerTaskIconChangedCallback(task.key, callback)
+ awaitClose {
+ taskVisualsChangedDelegate.unregisterTaskIconChangedCallback(task.key)
+ }
}
.distinctUntilChanged()
+
+ private suspend fun getThumbnailFromDataSource(task: Task) =
+ withContext(dispatcherProvider.main) {
+ suspendCancellableCoroutine { continuation ->
+ val cancellableTask =
+ taskThumbnailDataSource.getThumbnailInBackground(task) {
+ continuation.resume(it)
+ }
+ continuation.invokeOnCancellation { cancellableTask?.cancel() }
+ }
+ }
+
+ private suspend fun getIconFromDataSource(task: Task) =
+ withContext(dispatcherProvider.main) {
+ suspendCancellableCoroutine { continuation ->
+ val cancellableTask =
+ taskIconDataSource.getIconInBackground(task) { icon, contentDescription, title
+ ->
+ icon.constantState?.let {
+ continuation.resume(
+ TaskIconQueryResponse(
+ it.newDrawable().mutate(),
+ contentDescription,
+ title
+ )
+ )
+ }
+ }
+ continuation.invokeOnCancellation { cancellableTask?.cancel() }
+ }
+ }
}
data class TaskIconQueryResponse(
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 0b672d1..0a5544f 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -22,6 +22,8 @@
import com.android.launcher3.util.coroutines.ProductionDispatchers
import com.android.quickstep.RecentsModel
import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegateImpl
import com.android.quickstep.recents.data.TasksRepository
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.GetThumbnailUseCase
@@ -60,14 +62,22 @@
val recentsCoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("RecentsView"))
set(CoroutineScope::class.java.simpleName, recentsCoroutineScope)
+ val recentsModel = RecentsModel.INSTANCE.get(appContext)
+ val taskVisualsChangedDelegate =
+ TaskVisualsChangedDelegateImpl(
+ recentsModel,
+ recentsModel.thumbnailCache.highResLoadingState
+ )
+ set(TaskVisualsChangedDelegate::class.java.simpleName, taskVisualsChangedDelegate)
// Create RecentsTaskRepository singleton
val recentTasksRepository: RecentTasksRepository =
- with(RecentsModel.INSTANCE.get(appContext)) {
+ with(recentsModel) {
TasksRepository(
this,
thumbnailCache,
iconCache,
+ taskVisualsChangedDelegate,
recentsCoroutineScope,
ProductionDispatchers
)
@@ -155,6 +165,7 @@
thumbnailCache,
iconCache,
get(),
+ get(),
ProductionDispatchers
)
}
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index b1f46a3..1716f2e 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -58,10 +58,6 @@
recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
}
- fun addOrUpdateThumbnailOverride(thumbnailOverride: Map<Int, ThumbnailData>) {
- recentsTasksRepository.addOrUpdateThumbnailOverride(thumbnailOverride)
- }
-
suspend fun waitForThumbnailsToUpdate(updatedThumbnails: Map<Int, ThumbnailData>) {
combine(
updatedThumbnails.map {
diff --git a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java
index 66bff73..519ef60 100644
--- a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java
+++ b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java
@@ -16,6 +16,7 @@
package com.android.quickstep.util;
+import android.annotation.NonNull;
import android.os.UserHandle;
import com.android.systemui.shared.recents.model.Task;
@@ -36,7 +37,7 @@
/**
* Called when the icon for a task changes
*/
- default void onTaskIconChanged(String pkg, UserHandle user) {}
+ default void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {}
/**
* Called when the icon for a task changes
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 69a9690..255619a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1079,7 +1079,6 @@
@Nullable
public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
if (enableRefactorTaskThumbnail()) {
- mHelper.onTaskThumbnailChanged(taskId, thumbnailData);
return null;
}
if (mHandleTaskStackChanges) {
@@ -1100,8 +1099,7 @@
}
@Override
- public void onTaskIconChanged(String pkg, UserHandle user) {
- // TODO(b/342560598): Listen in TaskRepository and reload.
+ public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView tv = requireTaskViewAt(i);
Task task = tv.getFirstTask();
@@ -2549,7 +2547,6 @@
}
if (enableRefactorTaskThumbnail()) {
- // TODO(b/342560598): Listen in TaskRepository and reload.
return;
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
index 5b71da1..f5b2176 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
@@ -68,8 +68,4 @@
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/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt
new file mode 100644
index 0000000..72bbfc9
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.taskbar.TaskbarBackgroundRenderer.Companion.MAX_ROUNDNESS
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@LauncherMultivalentJUnit.EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+class TaskbarDesktopModeControllerTest {
+
+ private val context =
+ TaskbarWindowSandboxContext.create(
+ InstrumentationRegistry.getInstrumentation().targetContext
+ )
+
+ @get:Rule(order = 0) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+ @TaskbarUnitTestRule.InjectController
+ lateinit var taskbarDesktopModeController: TaskbarDesktopModeController
+
+ @Test
+ fun whenTaskbarRequiresCornerRoundness_shouldReturnDefaultCornerRoundness() {
+ assertThat(taskbarDesktopModeController.getTaskbarCornerRoundness(true))
+ .isEqualTo(MAX_ROUNDNESS)
+ }
+
+ @Test
+ fun whenTaskbarRequiresCornerRoundness_shouldReturnZeroAsCornerRoundness() {
+ assertThat(taskbarDesktopModeController.getTaskbarCornerRoundness(false)).isEqualTo(0f)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt
new file mode 100644
index 0000000..7d09efd
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.quickstep.recents.data
+
+import com.android.quickstep.TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback
+
+class FakeHighResLoadingStateNotifier : HighResLoadingStateNotifier {
+ val listeners = mutableListOf<HighResLoadingStateChangedCallback>()
+
+ override fun addCallback(callback: HighResLoadingStateChangedCallback) {
+ listeners.add(callback)
+ }
+
+ override fun removeCallback(callback: HighResLoadingStateChangedCallback) {
+ listeners.remove(callback)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
index fee4979..5de876a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
@@ -27,7 +27,8 @@
class FakeTaskIconDataSource : TaskIconDataSource {
- val taskIdToDrawable: Map<Int, Drawable> = (0..10).associateWith { mockCopyableDrawable() }
+ val taskIdToDrawable: MutableMap<Int, Drawable> =
+ (0..10).associateWith { mockCopyableDrawable() }.toMutableMap()
val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
var shouldLoadSynchronously: Boolean = true
@@ -52,15 +53,17 @@
return null
}
- private fun mockCopyableDrawable(): Drawable {
- val mutableDrawable = mock<Drawable>()
- val immutableDrawable =
- mock<Drawable>().apply { whenever(mutate()).thenReturn(mutableDrawable) }
- val constantState =
- mock<Drawable.ConstantState>().apply {
- whenever(newDrawable()).thenReturn(immutableDrawable)
- }
- return mutableDrawable.apply { whenever(this.constantState).thenReturn(constantState) }
+ companion object {
+ fun mockCopyableDrawable(): Drawable {
+ val mutableDrawable = mock<Drawable>()
+ val immutableDrawable =
+ mock<Drawable>().apply { whenever(mutate()).thenReturn(mutableDrawable) }
+ val constantState =
+ mock<Drawable.ConstantState>().apply {
+ whenever(newDrawable()).thenReturn(immutableDrawable)
+ }
+ return mutableDrawable.apply { whenever(this.constantState).thenReturn(constantState) }
+ }
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
index 30fc491..d12c0b0 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
@@ -27,7 +27,8 @@
class FakeTaskThumbnailDataSource : TaskThumbnailDataSource {
- val taskIdToBitmap: Map<Int, Bitmap> = (0..10).associateWith { mock() }
+ val taskIdToBitmap: MutableMap<Int, Bitmap> =
+ (0..10).associateWith { mock<Bitmap>() }.toMutableMap()
val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
var shouldLoadSynchronously: Boolean = true
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskVisualsChangeNotifier.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskVisualsChangeNotifier.kt
new file mode 100644
index 0000000..765f0d1
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskVisualsChangeNotifier.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.quickstep.recents.data
+
+import com.android.quickstep.util.TaskVisualsChangeListener
+
+class FakeTaskVisualsChangeNotifier : TaskVisualsChangeNotifier {
+ val listeners = mutableListOf<TaskVisualsChangeListener>()
+
+ override fun addThumbnailChangeListener(listener: TaskVisualsChangeListener) {
+ listeners.add(listener)
+ }
+
+ override fun removeThumbnailChangeListener(listener: TaskVisualsChangeListener) {
+ listeners.remove(listener)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index d94a351..7a17872 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -28,7 +28,6 @@
private var taskIconDataMap: Map<Int, TaskIconQueryResponse> = emptyMap()
private var tasks: MutableStateFlow<List<Task>> = MutableStateFlow(emptyList())
private var visibleTasks: MutableStateFlow<List<Int>> = MutableStateFlow(emptyList())
- private var thumbnailOverrideMap: Map<Int, ThumbnailData> = emptyMap()
override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> = tasks
@@ -39,7 +38,7 @@
.map { taskList ->
val task = taskList.firstOrNull { it.key.id == taskId } ?: return@map null
Task(task).apply {
- thumbnail = thumbnailOverrideMap[taskId] ?: task.thumbnail
+ thumbnail = task.thumbnail
icon = task.icon
titleDescription = task.titleDescription
title = task.title
@@ -62,16 +61,6 @@
}
}
}
- setThumbnailOverrideInternal(thumbnailOverrideMap)
- }
-
- override fun addOrUpdateThumbnailOverride(thumbnailOverride: Map<Int, ThumbnailData>) {
- setThumbnailOverrideInternal(thumbnailOverride)
- }
-
- private fun setThumbnailOverrideInternal(thumbnailOverride: Map<Int, ThumbnailData>) {
- thumbnailOverrideMap =
- thumbnailOverride.filterKeys(this.visibleTasks.value::contains).toMap()
}
fun seedTasks(tasks: List<Task>) {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
new file mode 100644
index 0000000..41f6bfd
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.quickstep.recents.data
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.UserHandle
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
+import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
+
+class TaskVisualsChangedDelegateTest {
+ private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier()
+ private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier()
+
+ val systemUnderTest =
+ TaskVisualsChangedDelegateImpl(taskVisualsChangeNotifier, highResLoadingStateNotifier)
+
+ @Test
+ fun addingFirstListener_addsListenerToNotifiers() {
+ systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), mock())
+
+ assertThat(taskVisualsChangeNotifier.listeners.single()).isEqualTo(systemUnderTest)
+ assertThat(highResLoadingStateNotifier.listeners.single()).isEqualTo(systemUnderTest)
+ }
+
+ @Test
+ fun addingAndRemovingListener_removesListenerFromNotifiers() {
+ systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), mock())
+ systemUnderTest.unregisterTaskThumbnailChangedCallback(createTaskKey(id = 1))
+
+ assertThat(taskVisualsChangeNotifier.listeners).isEmpty()
+ assertThat(highResLoadingStateNotifier.listeners).isEmpty()
+ }
+
+ @Test
+ fun addingTwoAndRemovingOneListener_doesNotRemoveListenerFromNotifiers() {
+ systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), mock())
+ systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 2), mock())
+ systemUnderTest.unregisterTaskThumbnailChangedCallback(createTaskKey(id = 1))
+
+ assertThat(taskVisualsChangeNotifier.listeners.single()).isEqualTo(systemUnderTest)
+ assertThat(highResLoadingStateNotifier.listeners.single()).isEqualTo(systemUnderTest)
+ }
+
+ @Test
+ fun onTaskIconChangedWithTaskId_notifiesCorrectListenerOnly() {
+ val expectedListener = mock<TaskIconChangedCallback>()
+ val additionalListener = mock<TaskIconChangedCallback>()
+ systemUnderTest.registerTaskIconChangedCallback(createTaskKey(id = 1), expectedListener)
+ systemUnderTest.registerTaskIconChangedCallback(createTaskKey(id = 2), additionalListener)
+
+ systemUnderTest.onTaskIconChanged(1)
+
+ verify(expectedListener).onTaskIconChanged()
+ verifyNoMoreInteractions(additionalListener)
+ }
+
+ @Test
+ fun onTaskIconChangedWithoutTaskId_notifiesCorrectListenerOnly() {
+ val expectedListener = mock<TaskIconChangedCallback>()
+ val listener = mock<TaskIconChangedCallback>()
+ // Correct match
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
+ expectedListener
+ )
+ // 1 out of 2 match
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 2, pkg = PACKAGE_NAME, userId = 1),
+ listener
+ )
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 3, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 2),
+ listener
+ )
+ // 0 out of 2 match
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 4, pkg = PACKAGE_NAME, userId = 2),
+ listener
+ )
+
+ systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1))
+
+ verify(expectedListener).onTaskIconChanged()
+ verifyNoMoreInteractions(listener)
+ }
+
+ @Test
+ fun replacedTaskIconChangedCallbacks_notCalled() {
+ val replacedListener = mock<TaskIconChangedCallback>()
+ val newListener = mock<TaskIconChangedCallback>()
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
+ replacedListener
+ )
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
+ newListener
+ )
+
+ systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1))
+
+ verifyNoMoreInteractions(replacedListener)
+ verify(newListener).onTaskIconChanged()
+ }
+
+ @Test
+ fun onTaskThumbnailChanged_notifiesCorrectListenerOnly() {
+ val expectedListener = mock<TaskThumbnailChangedCallback>()
+ val additionalListener = mock<TaskThumbnailChangedCallback>()
+ val expectedThumbnailData = ThumbnailData(snapshotId = 12345)
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 1),
+ expectedListener
+ )
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 2),
+ additionalListener
+ )
+
+ systemUnderTest.onTaskThumbnailChanged(1, expectedThumbnailData)
+
+ verify(expectedListener).onTaskThumbnailChanged(expectedThumbnailData)
+ verifyNoMoreInteractions(additionalListener)
+ }
+
+ @Test
+ fun onHighResLoadingStateChanged_notifiesAllListeners() {
+ val expectedListener = mock<TaskThumbnailChangedCallback>()
+ val additionalListener = mock<TaskThumbnailChangedCallback>()
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 1),
+ expectedListener
+ )
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 2),
+ additionalListener
+ )
+
+ systemUnderTest.onHighResLoadingStateChanged(true)
+
+ verify(expectedListener).onHighResLoadingStateChanged()
+ verify(additionalListener).onHighResLoadingStateChanged()
+ }
+
+ @Test
+ fun replacedTaskThumbnailChangedCallbacks_notCalled() {
+ val replacedListener1 = mock<TaskThumbnailChangedCallback>()
+ val newListener1 = mock<TaskThumbnailChangedCallback>()
+ val expectedThumbnailData = ThumbnailData(snapshotId = 12345)
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 1),
+ replacedListener1
+ )
+ systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), newListener1)
+
+ systemUnderTest.onTaskThumbnailChanged(1, expectedThumbnailData)
+
+ verifyNoMoreInteractions(replacedListener1)
+ verify(newListener1).onTaskThumbnailChanged(expectedThumbnailData)
+ }
+
+ private fun createTaskKey(id: Int = 1, pkg: String = PACKAGE_NAME, userId: Int = 1) =
+ TaskKey(id, 0, Intent().setPackage(pkg), ComponentName("", ""), userId, 0)
+
+ private companion object {
+ const val PACKAGE_NAME = "com.test.test"
+ const val ALTERNATIVE_PACKAGE_NAME = "com.test.test2"
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index e6534eb..f31467f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -19,7 +19,7 @@
import android.content.ComponentName
import android.content.Intent
import android.graphics.Bitmap
-import android.view.Surface
+import android.graphics.drawable.Drawable
import com.android.launcher3.util.TestDispatcherProvider
import com.android.quickstep.task.thumbnail.TaskThumbnailViewModelTest
import com.android.quickstep.util.DesktopTask
@@ -29,6 +29,9 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -48,6 +51,10 @@
private val recentsModel = FakeRecentTasksDataSource()
private val taskThumbnailDataSource = FakeTaskThumbnailDataSource()
private val taskIconDataSource = FakeTaskIconDataSource()
+ private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier()
+ private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier()
+ private val taskVisualsChangedDelegate =
+ TaskVisualsChangedDelegateImpl(taskVisualsChangeNotifier, highResLoadingStateNotifier)
private val dispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(dispatcher)
@@ -56,6 +63,7 @@
recentsModel,
taskThumbnailDataSource,
taskIconDataSource,
+ taskVisualsChangedDelegate,
testScope.backgroundScope,
TestDispatcherProvider(dispatcher)
)
@@ -203,87 +211,81 @@
}
@Test
- fun addThumbnailOverrideOverrideThumbnails() =
+ fun onTaskThumbnailChanged_setsNewThumbnailDataOnTask() =
testScope.runTest {
recentsModel.seedTasks(defaultTaskList)
- val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
- val thumbnailOverride2 = createThumbnailData()
- systemUnderTest.getAllTaskData(forceRefresh = true)
-
- systemUnderTest.setVisibleTasks(listOf(1, 2))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(2 to thumbnailOverride2))
-
- assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
- assertThat(systemUnderTest.getThumbnailById(2).first()!!.thumbnail)
- .isEqualTo(thumbnailOverride2.thumbnail)
- }
-
- @Test
- fun addThumbnailOverrideMultipleOverrides() =
- testScope.runTest {
- recentsModel.seedTasks(defaultTaskList)
- val thumbnailOverride1 = createThumbnailData()
- val thumbnailOverride2 = createThumbnailData()
- val thumbnailOverride3 = createThumbnailData()
- systemUnderTest.getAllTaskData(forceRefresh = true)
-
- systemUnderTest.setVisibleTasks(listOf(1, 2))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(1 to thumbnailOverride1))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(2 to thumbnailOverride2))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(2 to thumbnailOverride3))
-
- assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail)
- .isEqualTo(thumbnailOverride1.thumbnail)
- assertThat(systemUnderTest.getThumbnailById(2).first()!!.thumbnail)
- .isEqualTo(thumbnailOverride3.thumbnail)
- }
-
- @Test
- fun addThumbnailOverrideClearedWhenTaskBecomeInvisible() =
- testScope.runTest {
- recentsModel.seedTasks(defaultTaskList)
- val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
- val thumbnailOverride1 = createThumbnailData()
- val thumbnailOverride2 = createThumbnailData()
- systemUnderTest.getAllTaskData(forceRefresh = true)
-
- systemUnderTest.setVisibleTasks(listOf(1, 2))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(1 to thumbnailOverride1))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(2 to thumbnailOverride2))
- // Making task 2 invisible and visible again should clear the override
- systemUnderTest.setVisibleTasks(listOf(1))
- systemUnderTest.setVisibleTasks(listOf(1, 2))
-
- assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail)
- .isEqualTo(thumbnailOverride1.thumbnail)
- assertThat(systemUnderTest.getThumbnailById(2).first()!!.thumbnail).isEqualTo(bitmap2)
- }
-
- @Test
- fun addThumbnailOverrideDoesNotOverrideInvisibleTasks() =
- testScope.runTest {
- recentsModel.seedTasks(defaultTaskList)
- val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
- val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
- val thumbnailOverride = createThumbnailData()
systemUnderTest.getAllTaskData(forceRefresh = true)
systemUnderTest.setVisibleTasks(listOf(1))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(2 to thumbnailOverride))
- systemUnderTest.setVisibleTasks(listOf(1, 2))
- assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
- assertThat(systemUnderTest.getThumbnailById(2).first()!!.thumbnail).isEqualTo(bitmap2)
+ val expectedThumbnailData = createThumbnailData()
+ val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1]
+ val taskDataFlow = systemUnderTest.getTaskDataById(1)
+
+ val task1ThumbnailValues = mutableListOf<ThumbnailData?>()
+ testScope.backgroundScope.launch {
+ taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues)
+ }
+ taskVisualsChangedDelegate.onTaskThumbnailChanged(1, expectedThumbnailData)
+
+ assertThat(task1ThumbnailValues[1]!!.thumbnail).isEqualTo(expectedPreviousBitmap)
+ assertThat(task1ThumbnailValues.last()).isEqualTo(expectedThumbnailData)
+ }
+
+ @Test
+ fun onHighResLoadingStateChanged_setsNewThumbnailDataOnTask() =
+ testScope.runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(listOf(1))
+
+ val expectedBitmap = mock<Bitmap>()
+ val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1]
+ val taskDataFlow = systemUnderTest.getTaskDataById(1)
+
+ val task1ThumbnailValues = mutableListOf<Bitmap?>()
+ testScope.backgroundScope.launch {
+ taskDataFlow.map { it?.thumbnail?.thumbnail }.toList(task1ThumbnailValues)
+ }
+ taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap
+ taskVisualsChangedDelegate.onHighResLoadingStateChanged(true)
+
+ assertThat(task1ThumbnailValues[1]).isEqualTo(expectedPreviousBitmap)
+ assertThat(task1ThumbnailValues.last()).isEqualTo(expectedBitmap)
+ }
+
+ @Test
+ fun onTaskIconChanged_setsNewIconOnTask() =
+ testScope.runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(listOf(1))
+
+ val expectedIcon = FakeTaskIconDataSource.mockCopyableDrawable()
+ val expectedPreviousIcon = taskIconDataSource.taskIdToDrawable[1]
+ val taskDataFlow = systemUnderTest.getTaskDataById(1)
+
+ val task1IconValues = mutableListOf<Drawable?>()
+ testScope.backgroundScope.launch {
+ taskDataFlow.map { it?.icon }.toList(task1IconValues)
+ }
+ taskIconDataSource.taskIdToDrawable[1] = expectedIcon
+ taskVisualsChangedDelegate.onTaskIconChanged(1)
+
+ assertThat(task1IconValues[1]).isEqualTo(expectedPreviousIcon)
+ assertThat(task1IconValues.last()).isEqualTo(expectedIcon)
}
private fun createTaskWithId(taskId: Int) =
Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000))
- private fun createThumbnailData(rotation: Int = Surface.ROTATION_0): ThumbnailData {
+ private fun createThumbnailData(): ThumbnailData {
val bitmap = mock<Bitmap>()
whenever(bitmap.width).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_WIDTH)
whenever(bitmap.height).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_HEIGHT)
- return ThumbnailData(thumbnail = bitmap, rotation = rotation)
+ return ThumbnailData(thumbnail = bitmap)
}
}
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 dc16475..fe67313 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
@@ -70,44 +70,6 @@
assertThat(thumbnailDataFlow2.first()).isNull()
}
- @Test
- fun thumbnailOverrideWaitAndReset() = runTest {
- val thumbnailData1 = createThumbnailData().apply { snapshotId = 1 }
- val thumbnailData2 = createThumbnailData().apply { snapshotId = 2 }
- tasksRepository.seedTasks(tasks)
- tasksRepository.seedThumbnailData(mapOf(1 to thumbnailData1, 2 to thumbnailData2))
-
- val thumbnailDataFlow1 = tasksRepository.getThumbnailById(1)
- val thumbnailDataFlow2 = tasksRepository.getThumbnailById(2)
-
- systemUnderTest.refreshAllTaskData()
- systemUnderTest.updateVisibleTasks(listOf(1, 2))
-
- assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1)
- assertThat(thumbnailDataFlow2.first()).isEqualTo(thumbnailData2)
-
- systemUnderTest.setRunningTaskShowScreenshot(true)
- val thumbnailOverride = mapOf(2 to createThumbnailData().apply { snapshotId = 3 })
- systemUnderTest.addOrUpdateThumbnailOverride(thumbnailOverride)
-
- systemUnderTest.waitForRunningTaskShowScreenshotToUpdate()
- val expectedUpdate = mapOf(2 to createThumbnailData().apply { snapshotId = 3 })
- systemUnderTest.waitForThumbnailsToUpdate(expectedUpdate)
-
- assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1)
- assertThat(thumbnailDataFlow2.first()?.snapshotId).isEqualTo(3)
-
- systemUnderTest.onReset()
-
- assertThat(thumbnailDataFlow1.first()).isNull()
- assertThat(thumbnailDataFlow2.first()).isNull()
-
- systemUnderTest.updateVisibleTasks(listOf(1, 2))
-
- assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1)
- assertThat(thumbnailDataFlow2.first()).isEqualTo(thumbnailData2)
- }
-
private fun createTaskWithId(taskId: Int) =
Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
colorBackground = Color.argb(taskId, taskId, taskId, taskId)
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
index 15b1e53..d064f4a 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
@@ -57,6 +57,7 @@
@Mock lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController
@Mock lateinit var taskbarPinningController: TaskbarPinningController
@Mock lateinit var optionalBubbleControllers: Optional<BubbleControllers>
+ @Mock lateinit var taskbarDesktopModeController: TaskbarDesktopModeController
lateinit var taskbarControllers: TaskbarControllers
@@ -98,6 +99,7 @@
keyboardQuickSwitchController,
taskbarPinningController,
optionalBubbleControllers,
+ taskbarDesktopModeController
)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
index 2c23f86..710ad6f 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTrackpad.java
@@ -16,8 +16,6 @@
package com.android.quickstep;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
import static org.junit.Assert.assertNotNull;
@@ -32,8 +30,6 @@
import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.util.rule.ScreenRecordRule;
-import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import org.junit.After;
@@ -95,8 +91,6 @@
@Test
@PortraitLandscape
@NavigationModeSwitch
- @ScreenRecordRule.ScreenRecord // b/336606166
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/336606166
public void switchToOverview() throws Exception {
assumeTrue(mLauncher.isTablet());
diff --git a/src/com/android/launcher3/LauncherApplication.java b/src/com/android/launcher3/LauncherApplication.java
index 40873be..8969b60 100644
--- a/src/com/android/launcher3/LauncherApplication.java
+++ b/src/com/android/launcher3/LauncherApplication.java
@@ -17,14 +17,23 @@
import android.app.Application;
+import com.android.launcher3.dagger.DaggerLauncherAppComponent;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
+
/**
* Main application class for Launcher
*/
public class LauncherApplication extends Application {
+ private LauncherBaseAppComponent mAppComponent;
@Override
public void onCreate() {
super.onCreate();
MainProcessInitializer.initialize(this);
+ mAppComponent = DaggerLauncherAppComponent.builder().build();
+ }
+
+ public LauncherBaseAppComponent getAppComponent() {
+ return mAppComponent;
}
}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
new file mode 100644
index 0000000..3488c95
--- /dev/null
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -0,0 +1,32 @@
+/*
+ * 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.dagger;
+
+/**
+ * Launcher base component for Dagger injection.
+ *
+ * This class is not actually annotated as a Dagger component, since it is not used directly as one.
+ * Doing so generates unnecessary code bloat.
+ *
+ * See {@link LauncherAppComponent} for the one actually used by AOSP.
+ */
+public interface LauncherBaseAppComponent {
+ /** Builder for LauncherBaseAppComponent. */
+ interface Builder {
+ LauncherBaseAppComponent build();
+ }
+}
diff --git a/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java b/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java
new file mode 100644
index 0000000..4d7f937
--- /dev/null
+++ b/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java
@@ -0,0 +1,35 @@
+/*
+ * 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.dagger;
+
+import dagger.Component;
+
+import javax.inject.Singleton;
+
+/**
+ * Root component for Dagger injection for Launcher AOSP.
+ */
+@Singleton
+@Component
+public interface LauncherAppComponent extends LauncherBaseAppComponent {
+ /** Builder for aosp LauncherAppComponent. */
+ @Component.Builder
+ interface Builder extends LauncherBaseAppComponent.Builder {
+ LauncherAppComponent build();
+ }
+}
+
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 907aa50..7c87c65 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -98,7 +98,6 @@
@Test
@PortraitLandscape
@PlatinumTest(focusArea = "launcher")
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/311099513
public void testUninstallFromWorkspace() throws Exception {
installDummyAppAndWaitForUIUpdate();
try {