Merge "5/ Remove unused recents code" 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 be8ebbb..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"
@@ -339,3 +340,23 @@
description: "Change fast scroller to a lettered list"
bug: "358673724"
}
+
+flag {
+ name: "enable_desktop_task_alpha_animation"
+ namespace: "launcher"
+ description: "Enables the animation of the desktop task's background view"
+ bug: "320307666"
+ metadata {
+ 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/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 5fec1e3..1e5df41 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -92,7 +92,7 @@
<string name="allset_hint" msgid="459504134589971527">"Přejetím nahoru se vrátíte na plochu"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Klepnutím na tlačítko plochy se vrátíte na plochu"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> můžete začít používat"</string>
- <string name="default_device_name" msgid="6660656727127422487">"zařízení"</string>
+ <string name="default_device_name" msgid="6660656727127422487">"Zařízení"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Nastavení navigace v systému"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Sdílet"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snímek obrazovky"</string>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index 1a84776..b68378b 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -97,7 +97,7 @@
<string name="action_share" msgid="2648470652637092375">"Partager"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Capture d\'écran"</string>
<string name="action_split" msgid="2098009717623550676">"Partager"</string>
- <string name="action_save_app_pair" msgid="5974823919237645229">"Enregistrer la paire d\'applis"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Enregistrer une paire d\'applis"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Appuyez sur autre appli pour l\'écran partagé"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Sélectionnez une autre appli pour utiliser l\'écran partagé."</string>
<string name="toast_split_select_app_cancel" msgid="1939025102486630426">"Annuler"</string>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index 6fa5cc6..6178570 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -92,7 +92,7 @@
<string name="allset_hint" msgid="459504134589971527">"Prijeđite prstom prema gore da biste otvorili početni zaslon"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Dodirnite gumb početnog zaslona da biste prešli na početni zaslon"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> je spreman za početak upotrebe"</string>
- <string name="default_device_name" msgid="6660656727127422487">"uređaj"</string>
+ <string name="default_device_name" msgid="6660656727127422487">"Uređaj"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Postavke navigacije sustavom"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Podijeli"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Snimka zaslona"</string>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 4b5f71f..2be5e2b 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -22,8 +22,7 @@
<string name="recent_task_option_pin" msgid="7929860679018978258">"Gozhdo"</string>
<string name="recent_task_option_freeform" msgid="48863056265284071">"Formë e lirë"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopi"</string>
- <!-- no translation found for recent_task_desktop (8081113562549637334) -->
- <skip />
+ <string name="recent_task_desktop" msgid="8081113562549637334">"Desktopi"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Nuk ka asnjë artikull të fundit"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cilësimet e përdorimit të aplikacionit"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Pastroji të gjitha"</string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index e691134..d981882 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -376,6 +376,7 @@
<dimen name="transient_taskbar_stash_spring_velocity_dp_per_s">400dp</dimen>
<dimen name="taskbar_tooltip_vertical_padding">8dp</dimen>
<dimen name="taskbar_tooltip_horizontal_padding">16dp</dimen>
+ <dimen name="taskbar_tooltip_y_offset">4dp</dimen>
<!-- An additional touch slop to prevent x-axis movement during the swipe up to show taskbar -->
<dimen name="transient_taskbar_clamped_offset_bound">16dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index e925af6..50e8e5e 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -20,7 +20,6 @@
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
-import static com.android.launcher3.Flags.enablePredictiveBackGesture;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -170,11 +169,6 @@
@Override
protected void registerBackDispatcher() {
- if (!enablePredictiveBackGesture()) {
- super.registerBackDispatcher();
- return;
- }
-
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
new BackAnimationCallback());
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/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 02ece8e..69da7b6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -245,7 +245,7 @@
}
mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, isVisible);
- if (fromInit) {
+ if (fromInit || mControllers == null) {
duration = 0;
}
return mTaskbarLauncherStateController.applyState(duration, startAnimation);
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..e19042e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -29,7 +29,6 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.Flags.enableTaskbarCustomization;
import static com.android.launcher3.Utilities.calculateTextHeight;
import static com.android.launcher3.Utilities.isRunningInTestHarness;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
@@ -230,15 +229,12 @@
mNavigationBarPanelContext = navigationBarPanelContext;
applyDeviceProfile(launcherDp);
final Resources resources = getResources();
-
- if (enableTaskbarCustomization()) {
- mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
- mTaskbarSpecsEvaluator = new TaskbarSpecsEvaluator(
- this,
- mTaskbarFeatureEvaluator,
- mDeviceProfile.inv.numRows,
- mDeviceProfile.inv.numColumns);
- }
+ mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
+ mTaskbarSpecsEvaluator = new TaskbarSpecsEvaluator(
+ this,
+ mTaskbarFeatureEvaluator,
+ mDeviceProfile.inv.numRows,
+ mDeviceProfile.inv.numColumns);
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
@@ -348,8 +344,9 @@
new KeyboardQuickSwitchController(),
new TaskbarPinningController(this, () ->
DisplayController.isInDesktopMode(this)),
- bubbleControllersOptional);
-
+ bubbleControllersOptional,
+ new TaskbarDesktopModeController(
+ LauncherActivityInterface.INSTANCE::getDesktopVisibilityController));
mLauncherPrefs = LauncherPrefs.get(this);
}
@@ -1760,12 +1757,10 @@
return mControllers.taskbarStashController.isInStashedLauncherState();
}
- @Nullable
public TaskbarFeatureEvaluator getTaskbarFeatureEvaluator() {
return mTaskbarFeatureEvaluator;
}
- @Nullable
public TaskbarSpecsEvaluator getTaskbarSpecsEvaluator() {
return mTaskbarSpecsEvaluator;
}
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/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index a635537..b5a3314 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -169,8 +169,11 @@
override fun addArrow() {
super.addArrow()
+ val location = IntArray(2)
+ popupContainer.getLocationInDragLayer(dividerView, location)
+ val dividerViewX = location[0].toFloat()
// Change arrow location to the middle of popup.
- mArrow.x = (dividerView.x + dividerView.width / 2) - (mArrowWidth / 2)
+ mArrow.x = (dividerViewX + dividerView.width / 2) - (mArrowWidth / 2)
}
override fun updateArrowColor() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
index 8c7879d..3bff31f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
@@ -50,6 +50,7 @@
private final View mHoverView;
private final ArrowTipView mHoverToolTipView;
private final String mToolTipText;
+ private final int mYOffset;
public TaskbarHoverToolTipController(TaskbarActivityContext activity, TaskbarView taskbarView,
View hoverView) {
@@ -79,6 +80,8 @@
mHoverToolTipView.findViewById(R.id.text).setPadding(horizontalPadding, verticalPadding,
horizontalPadding, verticalPadding);
mHoverToolTipView.setAlpha(0);
+ mYOffset = arrowContextWrapper.getResources().getDimensionPixelSize(
+ R.dimen.taskbar_tooltip_y_offset);
AnimatorSet hoverOpenAnimator = new AnimatorSet();
ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 0f, 1f);
@@ -89,7 +92,7 @@
mHoverToolTipView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
mHoverToolTipView.setPivotY(bottom);
- mHoverToolTipView.setY(mTaskbarView.getTop() - (bottom - top));
+ mHoverToolTipView.setY(mTaskbarView.getTop() - mYOffset - (bottom - top));
});
}
@@ -121,6 +124,6 @@
}
Rect iconViewBounds = Utilities.getViewBounds(mHoverView);
mHoverToolTipView.showAtLocation(mToolTipText, iconViewBounds.centerX(),
- mTaskbarView.getTop(), /* shouldAutoClose= */ false);
+ mTaskbarView.getTop() - mYOffset, /* shouldAutoClose= */ false);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index eb1165a..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;
@@ -246,7 +247,9 @@
resetIconAlignment();
- mLauncher.getStateManager().addStateListener(mStateListener);
+ if (!mControllers.taskbarActivityContext.isPhoneMode()) {
+ mLauncher.getStateManager().addStateListener(mStateListener);
+ }
mLauncherState = launcher.getStateManager().getState();
updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false);
@@ -412,7 +415,7 @@
}
public Animator applyState(long duration, boolean start) {
- if (mIsDestroyed) {
+ if (mIsDestroyed || mControllers.taskbarActivityContext.isPhoneMode()) {
return null;
}
Animator animator = null;
@@ -589,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) {
@@ -861,7 +870,8 @@
"%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value));
pw.println(String.format(
"%s\tmIconAlphaForHome=%.2f", prefix, mIconAlphaForHome.getValue()));
- pw.println(String.format("%s\tmPrevState=%s", prefix, getStateString(mPrevState)));
+ pw.println(String.format("%s\tmPrevState=%s", prefix,
+ mPrevState == null ? null : getStateString(mPrevState)));
pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState)));
pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState));
pw.println(String.format(
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/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 54a7fdc..8b8b4da 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -22,10 +22,8 @@
import static com.android.launcher3.Flags.enableRecentsInTaskbar;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
import android.content.res.Resources;
@@ -39,12 +37,9 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
-import androidx.annotation.DimenRes;
-import androidx.annotation.DrawableRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -62,13 +57,12 @@
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer;
+import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.IconButtonView;
-import com.android.quickstep.DeviceConfigWrapper;
-import com.android.quickstep.util.AssistStateManager;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
@@ -100,16 +94,13 @@
private View.OnLongClickListener mIconLongClickListener;
// Only non-null when the corresponding Folder is open.
- private @Nullable FolderIcon mLeaveBehindFolderIcon;
+ @Nullable private FolderIcon mLeaveBehindFolderIcon;
// Only non-null when device supports having an All Apps button.
- private @Nullable IconButtonView mAllAppsButton;
- private Runnable mAllAppsTouchRunnable;
- private long mAllAppsButtonTouchDelayMs;
- private boolean mAllAppsTouchTriggered;
+ @Nullable private final TaskbarAllAppsButtonContainer mAllAppsButtonContainer;
- // Only non-null when device supports having an All Apps button, or Recent Apps.
- private @Nullable IconButtonView mTaskbarDivider;
+ // Only non-null when device supports having an All Apps button.
+ @Nullable private TaskbarDividerContainer mTaskbarDividerContainer;
/**
* Whether the divider is between Hotseat icons and Recents,
@@ -173,55 +164,14 @@
// Needed to draw folder leave-behind when opening one.
setWillNotDraw(false);
- mAllAppsButton = (IconButtonView) LayoutInflater.from(context)
- .inflate(R.layout.taskbar_all_apps_button, this, false);
- mAllAppsButton.setIconDrawable(resources.getDrawable(
- getAllAppsButton(isTransientTaskbar)));
- mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
- mAllAppsButton.setForegroundTint(
- mActivityContext.getColor(R.color.all_apps_button_color));
+ mAllAppsButtonContainer = new TaskbarAllAppsButtonContainer(context);
if (enableTaskbarPinning() || enableRecentsInTaskbar()) {
- mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
- R.layout.taskbar_divider,
- this, false);
- mTaskbarDivider.setIconDrawable(
- resources.getDrawable(R.drawable.taskbar_divider_button));
- mTaskbarDivider.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
+ mTaskbarDividerContainer = new TaskbarDividerContainer(context);
}
// TODO: Disable touch events on QSB otherwise it can crash.
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
-
- // Default long press (touch) delay = 400ms
- mAllAppsButtonTouchDelayMs = ViewConfiguration.getLongPressTimeout();
- }
-
- @DrawableRes
- private int getAllAppsButton(boolean isTransientTaskbar) {
- boolean shouldSelectTransientIcon =
- (isTransientTaskbar || enableTaskbarPinning())
- && !mActivityContext.isThreeButtonNav();
- if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
- return shouldSelectTransientIcon
- ? R.drawable.ic_transient_taskbar_all_apps_search_button
- : R.drawable.ic_taskbar_all_apps_search_button;
- } else {
- return shouldSelectTransientIcon
- ? R.drawable.ic_transient_taskbar_all_apps_button
- : R.drawable.ic_taskbar_all_apps_button;
- }
- }
-
- @DimenRes
- public int getAllAppsButtonTranslationXOffset(boolean isTransientTaskbar) {
- if (isTransientTaskbar) {
- return R.dimen.transient_taskbar_all_apps_button_translation_x_offset;
- } else {
- return ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()
- ? R.dimen.taskbar_all_apps_search_button_translation_x_offset
- : R.dimen.taskbar_all_apps_button_translation_x_offset;
- }
}
@Override
@@ -306,27 +256,11 @@
mIconClickListener = mControllerCallbacks.getIconOnClickListener();
mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
- if (mAllAppsButton != null) {
- mAllAppsButton.setOnClickListener(this::onAllAppsButtonClick);
- mAllAppsButton.setOnLongClickListener(this::onAllAppsButtonLongClick);
- mAllAppsButton.setOnTouchListener(this::onAllAppsButtonTouch);
- mAllAppsButton.setHapticFeedbackEnabled(
- mControllerCallbacks.isAllAppsButtonHapticFeedbackEnabled());
- mAllAppsTouchRunnable = () -> {
- mControllerCallbacks.triggerAllAppsButtonLongClick();
- mAllAppsTouchTriggered = true;
- };
- AssistStateManager assistStateManager = AssistStateManager.INSTANCE.get(mContext);
- if (DeviceConfigWrapper.get().getCustomLpaaThresholds()
- && assistStateManager.getLPNHDurationMillis().isPresent()) {
- mAllAppsButtonTouchDelayMs = assistStateManager.getLPNHDurationMillis().get();
- }
+ if (mAllAppsButtonContainer != null) {
+ mAllAppsButtonContainer.setUpCallbacks(callbacks);
}
- if (mTaskbarDivider != null && !mActivityContext.isThreeButtonNav()) {
- mTaskbarDivider.setOnLongClickListener(
- mControllerCallbacks.getTaskbarDividerLongClickListener());
- mTaskbarDivider.setOnTouchListener(
- mControllerCallbacks.getTaskbarDividerRightClickListener());
+ if (mTaskbarDividerContainer != null && callbacks.supportsDividerLongPress()) {
+ mTaskbarDividerContainer.setUpCallbacks(callbacks);
}
}
@@ -348,11 +282,11 @@
int numViewsAnimated = 0;
mAddedDividerForRecents = false;
- if (mAllAppsButton != null) {
- removeView(mAllAppsButton);
+ if (mAllAppsButtonContainer != null) {
+ removeView(mAllAppsButtonContainer);
- if (mTaskbarDivider != null) {
- removeView(mTaskbarDivider);
+ if (mTaskbarDividerContainer != null) {
+ removeView(mTaskbarDividerContainer);
}
}
removeView(mQsb);
@@ -439,8 +373,8 @@
nextViewIndex++;
}
- if (mTaskbarDivider != null && !recentTasks.isEmpty()) {
- addView(mTaskbarDivider, nextViewIndex++);
+ if (mTaskbarDividerContainer != null && !recentTasks.isEmpty()) {
+ addView(mTaskbarDividerContainer, nextViewIndex++);
mAddedDividerForRecents = true;
}
@@ -504,12 +438,14 @@
removeAndRecycle(getChildAt(nextViewIndex));
}
- if (mAllAppsButton != null) {
- addView(mAllAppsButton, mIsRtl ? hotseatItemInfos.length : 0);
+ if (mAllAppsButtonContainer != null) {
+ addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0);
// If there are no recent tasks, add divider after All Apps (unless it's the only view).
- if (!mAddedDividerForRecents && mTaskbarDivider != null && getChildCount() > 1) {
- addView(mTaskbarDivider, mIsRtl ? (getChildCount() - 1) : 1);
+ if (!mAddedDividerForRecents
+ && mTaskbarDividerContainer != null
+ && getChildCount() > 1) {
+ addView(mTaskbarDividerContainer, mIsRtl ? (getChildCount() - 1) : 1);
}
}
if (mActivityContext.getDeviceProfile().isQsbInline) {
@@ -637,7 +573,7 @@
int qsbTop = (bottom - top - deviceProfile.hotseatQsbHeight) / 2;
int qsbBottom = qsbTop + deviceProfile.hotseatQsbHeight;
child.layout(qsbStart, qsbTop, qsbEnd, qsbBottom);
- } else if (child == mTaskbarDivider) {
+ } else if (child == mTaskbarDividerContainer) {
iconEnd += mItemMarginLeftRight;
int iconStart = iconEnd - mIconTouchSize;
child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
@@ -727,16 +663,16 @@
* Returns the all apps button in the taskbar.
*/
@Nullable
- public View getAllAppsButtonView() {
- return mAllAppsButton;
+ public TaskbarAllAppsButtonContainer getAllAppsButtonContainer() {
+ return mAllAppsButtonContainer;
}
/**
* Returns the taskbar divider in the taskbar.
*/
@Nullable
- public View getTaskbarDividerView() {
- return mTaskbarDivider;
+ public TaskbarDividerContainer getTaskbarDividerViewContainer() {
+ return mTaskbarDividerContainer;
}
/**
@@ -832,48 +768,6 @@
}
}
}
- return mAllAppsButton;
- }
-
- private boolean onAllAppsButtonTouch(View view, MotionEvent ev) {
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mAllAppsTouchTriggered = false;
- MAIN_EXECUTOR.getHandler().postDelayed(
- mAllAppsTouchRunnable, mAllAppsButtonTouchDelayMs);
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- cancelAllAppsButtonTouch();
- }
- return false;
- }
-
- private void cancelAllAppsButtonTouch() {
- MAIN_EXECUTOR.getHandler().removeCallbacks(mAllAppsTouchRunnable);
- // ACTION_UP is first triggered, then click listener / long-click listener is triggered on
- // the next frame, so we need to post twice and delay the reset.
- if (mAllAppsButton != null) {
- mAllAppsButton.post(() -> {
- mAllAppsButton.post(() -> {
- mAllAppsTouchTriggered = false;
- });
- });
- }
- }
-
- private void onAllAppsButtonClick(View view) {
- if (!mAllAppsTouchTriggered) {
- mControllerCallbacks.triggerAllAppsButtonClick(view);
- }
- }
-
- // Handle long click from Switch Access and Voice Access
- private boolean onAllAppsButtonLongClick(View view) {
- if (!MAIN_EXECUTOR.getHandler().hasCallbacks(mAllAppsTouchRunnable)
- && !mAllAppsTouchTriggered) {
- mControllerCallbacks.triggerAllAppsButtonLongClick();
- }
- return true;
+ return mAllAppsButtonContainer;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index e6cac2f..296d379 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -51,7 +51,7 @@
}
/** Trigger All Apps button click action. */
- protected void triggerAllAppsButtonClick(View v) {
+ public void triggerAllAppsButtonClick(View v) {
InteractionJankMonitorWrapper.begin(v, Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS,
/* tag= */ "TASKBAR_BUTTON");
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
@@ -59,7 +59,7 @@
}
/** Trigger All Apps button long click action. */
- protected void triggerAllAppsButtonLongClick() {
+ public void triggerAllAppsButtonLongClick() {
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS);
}
@@ -74,6 +74,11 @@
};
}
+ /** Check to see if we support long press on taskbar divider */
+ public boolean supportsDividerLongPress() {
+ return !mActivity.isThreeButtonNav();
+ }
+
public View.OnTouchListener getTaskbarDividerRightClickListener() {
return (v, event) -> {
if (event.isFromSource(InputDevice.SOURCE_MOUSE)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index aef21aa..aa3e6bf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -72,7 +72,6 @@
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.views.IconButtonView;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
@@ -219,8 +218,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);
@@ -299,7 +298,7 @@
@Nullable
public View getAllAppsButtonView() {
- return mTaskbarView.getAllAppsButtonView();
+ return mTaskbarView.getAllAppsButtonContainer();
}
public AnimatedFloat getTaskbarIconScaleForStash() {
@@ -366,9 +365,9 @@
View[] iconViews = mTaskbarView.getIconViews();
float scale = mTaskbarIconTranslationXForPinning.value;
float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
- mTaskbarView.getAllAppsButtonTranslationXOffset(true));
+ mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(true));
float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
- mTaskbarView.getAllAppsButtonTranslationXOffset(false));
+ mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(false));
float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
persistentTaskbarAllAppsOffset);
@@ -383,7 +382,7 @@
}
if (mActivity.isThreeButtonNav()) {
- ((IconButtonView) mTaskbarView.getAllAppsButtonView())
+ mTaskbarView.getAllAppsButtonContainer()
.setTranslationXForTaskbarAllAppsIcon(allAppIconTranslateRange);
return;
}
@@ -408,8 +407,8 @@
-finalMarginScale * (iconIndex - halfIconCount));
}
- if (iconView.equals(mTaskbarView.getAllAppsButtonView())) {
- ((IconButtonView) iconView).setTranslationXForTaskbarAllAppsIcon(
+ if (iconView.equals(mTaskbarView.getAllAppsButtonContainer())) {
+ mTaskbarView.getAllAppsButtonContainer().setTranslationXForTaskbarAllAppsIcon(
allAppIconTranslateRange);
}
}
@@ -537,7 +536,7 @@
}
public View getTaskbarDividerView() {
- return mTaskbarView.getTaskbarDividerView();
+ return mTaskbarView.getTaskbarDividerViewContainer();
}
/**
@@ -753,8 +752,8 @@
int firstRecentTaskIndex = -1;
for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
View child = mTaskbarView.getChildAt(i);
- boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView();
- boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerView();
+ boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonContainer();
+ boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerViewContainer();
boolean isRecentTask = child.getTag() instanceof GroupTask;
// TODO(b/343522351): show recents on the home screen.
final boolean isRecentsInHotseat = false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 90ac872..f5ac66f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar.allapps;
import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.launcher3.Flags.enablePredictiveBackGesture;
import static com.android.launcher3.touch.AllAppsSwipeController.ALL_APPS_FADE_MANUAL;
import static com.android.launcher3.touch.AllAppsSwipeController.SCRIM_FADE_MANUAL;
@@ -193,14 +192,12 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mActivityContext.addOnDeviceProfileChangeListener(this);
- if (enablePredictiveBackGesture()) {
- mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(mViewOutlineProvider);
- mAppsView.getAppsRecyclerViewContainer().setClipToOutline(true);
- OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
- if (dispatcher != null) {
- dispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, this);
- }
+ mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(mViewOutlineProvider);
+ mAppsView.getAppsRecyclerViewContainer().setClipToOutline(true);
+ OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
+ if (dispatcher != null) {
+ dispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, this);
}
}
@@ -208,13 +205,11 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mActivityContext.removeOnDeviceProfileChangeListener(this);
- if (enablePredictiveBackGesture()) {
- mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(null);
- mAppsView.getAppsRecyclerViewContainer().setClipToOutline(false);
- OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
- if (dispatcher != null) {
- dispatcher.unregisterOnBackInvokedCallback(this);
- }
+ mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(null);
+ mAppsView.getAppsRecyclerViewContainer().setClipToOutline(false);
+ OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
+ if (dispatcher != null) {
+ dispatcher.unregisterOnBackInvokedCallback(this);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index c458936..71867fe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -1473,8 +1473,9 @@
pw.println("BubbleBarView state:");
pw.println(" visibility: " + getVisibility());
pw.println(" alpha: " + getAlpha());
- pw.println(" translation Y: " + getTranslationY());
- pw.println(" bubbles in bar (childCount = " + getChildCount() + ")");
+ pw.println(" translationY: " + getTranslationY());
+ pw.println(" childCount: " + getChildCount());
+ pw.println(" hasOverflow: " + hasOverflow());
for (BubbleView bubbleView: getBubbles()) {
BubbleBarItem bubble = bubbleView.getBubble();
String key = bubble == null ? "null" : bubble.getKey();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 5c1a546..65f857e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -793,6 +793,7 @@
pw.println(" mShouldShowEducation: " + mShouldShowEducation);
pw.println(" mBubbleBarTranslationY.value: " + mBubbleBarTranslationY.value);
pw.println(" mBubbleBarSwipeUpTranslationY: " + mBubbleBarSwipeUpTranslationY);
+ pw.println(" mOverflowAdded: " + mOverflowAdded);
if (mBarView != null) {
mBarView.dump(pw);
} else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
index 7d2d36d..726800c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt
@@ -18,18 +18,27 @@
import android.annotation.SuppressLint
import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color.TRANSPARENT
import android.util.AttributeSet
import android.view.LayoutInflater
-import android.widget.LinearLayout
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
import androidx.core.view.setPadding
import com.android.launcher3.R
import com.android.launcher3.Utilities.dpToPx
-import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR
+import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarViewCallbacks
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.views.ActivityContext
import com.android.launcher3.views.IconButtonView
+import com.android.quickstep.DeviceConfigWrapper
+import com.android.quickstep.util.AssistStateManager
/** Taskbar all apps button container for customizable taskbar. */
class TaskbarAllAppsButtonContainer
@@ -38,19 +47,21 @@
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
-) : LinearLayout(context, attrs), TaskbarContainer {
+) : IconButtonView(context, attrs), TaskbarContainer {
- private val allAppsButton: IconButtonView =
- LayoutInflater.from(context).inflate(R.layout.taskbar_all_apps_button, this, false)
- as IconButtonView
private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
+ private var allAppsTouchTriggered = false
+ private var allAppsTouchRunnable: Runnable? = null
+ private var allAppsButtonTouchDelayMs: Long = ViewConfiguration.getLongPressTimeout().toLong()
+ private lateinit var taskbarViewCallbacks: TaskbarViewCallbacks
override val spaceNeeded: Int
get() {
- return dpToPx(activityContext.taskbarSpecsEvaluator!!.taskbarIconSize.size.toFloat())
+ return dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconSize.size.toFloat())
}
init {
+ LayoutInflater.from(context).inflate(R.layout.taskbar_all_apps_button, null, false)
setUpIcon()
}
@@ -58,24 +69,39 @@
private fun setUpIcon() {
val drawable =
resources.getDrawable(
- getAllAppsButton(activityContext.taskbarFeatureEvaluator!!.isTransient)
+ getAllAppsButton(activityContext.taskbarFeatureEvaluator.isTransient)
)
- val padding = activityContext.taskbarSpecsEvaluator!!.taskbarIconPadding
+ backgroundTintList = ColorStateList.valueOf(TRANSPARENT)
+ setIconDrawable(drawable)
+ setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat()))
+ setForegroundTint(activityContext.getColor(R.color.all_apps_button_color))
+ }
- allAppsButton.setIconDrawable(drawable)
- allAppsButton.setPadding(padding)
- allAppsButton.setForegroundTint(activityContext.getColor(R.color.all_apps_button_color))
-
- // TODO(b/356465292) : add click listeners in future cl
- addView(allAppsButton)
+ @SuppressLint("ClickableViewAccessibility")
+ fun setUpCallbacks(callbacks: TaskbarViewCallbacks) {
+ taskbarViewCallbacks = callbacks
+ setOnClickListener(this::onAllAppsButtonClick)
+ setOnLongClickListener(this::onAllAppsButtonLongClick)
+ setOnTouchListener(this::onAllAppsButtonTouch)
+ isHapticFeedbackEnabled = taskbarViewCallbacks.isAllAppsButtonHapticFeedbackEnabled()
+ allAppsTouchRunnable = Runnable {
+ taskbarViewCallbacks.triggerAllAppsButtonLongClick()
+ allAppsTouchTriggered = true
+ }
+ val assistStateManager = AssistStateManager.INSTANCE[mContext]
+ if (
+ DeviceConfigWrapper.get().customLpaaThresholds &&
+ assistStateManager.lpnhDurationMillis.isPresent
+ ) {
+ allAppsButtonTouchDelayMs = assistStateManager.lpnhDurationMillis.get()
+ }
}
@DrawableRes
private fun getAllAppsButton(isTransientTaskbar: Boolean): Int {
val shouldSelectTransientIcon =
- isTransientTaskbar ||
- (FeatureFlags.enableTaskbarPinning() && !activityContext.isThreeButtonNav)
- return if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+ isTransientTaskbar || (enableTaskbarPinning() && !activityContext.isThreeButtonNav)
+ return if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
if (shouldSelectTransientIcon) R.drawable.ic_transient_taskbar_all_apps_search_button
else R.drawable.ic_taskbar_all_apps_search_button
} else {
@@ -88,10 +114,43 @@
fun getAllAppsButtonTranslationXOffset(isTransientTaskbar: Boolean): Int {
return if (isTransientTaskbar) {
R.dimen.transient_taskbar_all_apps_button_translation_x_offset
- } else if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
+ } else if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
R.dimen.taskbar_all_apps_search_button_translation_x_offset
} else {
R.dimen.taskbar_all_apps_button_translation_x_offset
}
}
+
+ private fun onAllAppsButtonTouch(view: View, ev: MotionEvent): Boolean {
+ when (ev.action) {
+ MotionEvent.ACTION_DOWN -> {
+ allAppsTouchTriggered = false
+ MAIN_EXECUTOR.handler.postDelayed(allAppsTouchRunnable!!, allAppsButtonTouchDelayMs)
+ }
+ MotionEvent.ACTION_UP,
+ MotionEvent.ACTION_CANCEL -> cancelAllAppsButtonTouch()
+ }
+ return false
+ }
+
+ private fun cancelAllAppsButtonTouch() {
+ MAIN_EXECUTOR.handler.removeCallbacks(allAppsTouchRunnable!!)
+ // ACTION_UP is first triggered, then click listener / long-click listener is triggered on
+ // the next frame, so we need to post twice and delay the reset.
+ this.post { this.post { allAppsTouchTriggered = false } }
+ }
+
+ private fun onAllAppsButtonClick(view: View) {
+ if (!allAppsTouchTriggered) {
+ taskbarViewCallbacks.triggerAllAppsButtonClick(view)
+ }
+ }
+
+ // Handle long click from Switch Access and Voice Access
+ private fun onAllAppsButtonLongClick(view: View): Boolean {
+ if (!MAIN_EXECUTOR.handler.hasCallbacks(allAppsTouchRunnable!!) && !allAppsTouchTriggered) {
+ taskbarViewCallbacks.triggerAllAppsButtonLongClick()
+ }
+ return true
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt
index 26e71f7..1fb835a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarDividerContainer.kt
@@ -18,13 +18,15 @@
import android.annotation.SuppressLint
import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color.TRANSPARENT
import android.util.AttributeSet
import android.view.LayoutInflater
-import android.widget.LinearLayout
import androidx.core.view.setPadding
import com.android.launcher3.R
import com.android.launcher3.Utilities.dpToPx
import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarViewCallbacks
import com.android.launcher3.views.ActivityContext
import com.android.launcher3.views.IconButtonView
@@ -35,31 +37,30 @@
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
-) : LinearLayout(context, attrs), TaskbarContainer {
-
- private val taskbarDivider: IconButtonView =
- LayoutInflater.from(context).inflate(R.layout.taskbar_divider, this, false)
- as IconButtonView
+) : IconButtonView(context, attrs), TaskbarContainer {
private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
override val spaceNeeded: Int
get() {
- return dpToPx(activityContext.taskbarSpecsEvaluator!!.taskbarIconSize.size.toFloat())
+ return dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconSize.size.toFloat())
}
init {
+ LayoutInflater.from(context).inflate(R.layout.taskbar_divider, null, false)
setUpIcon()
}
@SuppressLint("UseCompatLoadingForDrawables")
fun setUpIcon() {
+ backgroundTintList = ColorStateList.valueOf(TRANSPARENT)
val drawable = resources.getDrawable(R.drawable.taskbar_divider_button)
- val padding = activityContext.taskbarSpecsEvaluator!!.taskbarIconPadding
+ setIconDrawable(drawable)
+ setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat()))
+ }
- taskbarDivider.setIconDrawable(drawable)
- taskbarDivider.setPadding(padding)
-
- // TODO(b/356465292):: add click listeners in future cl
- addView(taskbarDivider)
+ @SuppressLint("ClickableViewAccessibility")
+ fun setUpCallbacks(callbacks: TaskbarViewCallbacks) {
+ setOnLongClickListener(callbacks.taskbarDividerLongClickListener)
+ setOnTouchListener(callbacks.taskbarDividerRightClickListener)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
index 887eb01..6be0828 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
@@ -19,10 +19,11 @@
/** Taskbar Icon Specs */
object TaskbarIconSpecs {
- val iconSize40dp = TaskbarIconSize(40)
- val iconSize44dp = TaskbarIconSize(44)
- val iconSize48dp = TaskbarIconSize(48)
- val iconSize52dp = TaskbarIconSize(52)
+ // Mapping of visual icon size to icon specs value http://b/235886078
+ val iconSize40dp = TaskbarIconSize(44)
+ val iconSize44dp = TaskbarIconSize(48)
+ val iconSize48dp = TaskbarIconSize(52)
+ val iconSize52dp = TaskbarIconSize(57)
val transientTaskbarIconSizes = arrayOf(iconSize44dp, iconSize48dp, iconSize52dp)
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
index 761b47e..f37b2c1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
@@ -25,14 +25,14 @@
numRows: Int = taskbarActivityContext.deviceProfile.inv.numRows,
numColumns: Int = taskbarActivityContext.deviceProfile.inv.numColumns,
) {
- var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numRows, numColumns)
+ var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numColumns, numRows)
// TODO(b/341146605) : initialize it to taskbar container in later cl.
private var taskbarContainer: List<TaskbarContainer> = emptyList()
val taskbarIconPadding: Int =
- if (TaskbarIconSpecs.minimumTaskbarIconTouchSize.size > taskbarIconSize.size) {
- (TaskbarIconSpecs.minimumTaskbarIconTouchSize.size - taskbarIconSize.size) / 2
+ if (TaskbarIconSpecs.iconSize52dp.size > taskbarIconSize.size) {
+ (TaskbarIconSpecs.iconSize52dp.size - taskbarIconSize.size) / 2
} else {
0
}
@@ -44,10 +44,10 @@
TaskbarIconSpecs.defaultPersistentIconMargin
}
- fun getIconSizeByGrid(row: Int, column: Int): TaskbarIconSize {
+ fun getIconSizeByGrid(columns: Int, rows: Int): TaskbarIconSize {
return if (taskbarFeatureEvaluator.isTransient) {
TaskbarIconSpecs.transientTaskbarIconSizeByGridSize.getOrDefault(
- TransientTaskbarIconSizeKey(row, column, taskbarFeatureEvaluator.isLandscape),
+ TransientTaskbarIconSizeKey(columns, rows, taskbarFeatureEvaluator.isLandscape),
TaskbarIconSpecs.defaultTransientIconSize,
)
} else {
@@ -102,6 +102,6 @@
data class TaskbarIconSize(val size: Int)
-data class TransientTaskbarIconSizeKey(val row: Int, val column: Int, val isLandscape: Boolean)
+data class TransientTaskbarIconSizeKey(val columns: Int, val rows: Int, val isLandscape: Boolean)
data class TaskbarIconMarginSize(val size: Int)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 37e0034..4590efe 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -379,12 +379,7 @@
}
@Override
- public void setIconVisible(boolean visible) {
- setForceHideRing(!visible);
- super.setIconVisible(visible);
- }
-
- private void setForceHideRing(boolean forceHideRing) {
+ public void setForceHideRing(boolean forceHideRing) {
if (mForceHideRing == forceHideRing) {
return;
}
@@ -417,7 +412,7 @@
private void drawEffect(Canvas canvas) {
// Don't draw ring effect if item is about to be dragged or if the icon is not visible.
- if (mDrawForDrag || !mIsIconVisible) {
+ if (mDrawForDrag || !mIsIconVisible || mForceHideRing) {
return;
}
mIconRingPaint.setColor(RING_SHADOW_COLOR);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f67d49a..55c1885 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -22,7 +22,6 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
-import static com.android.launcher3.Flags.enablePredictiveBackGesture;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -694,9 +693,7 @@
// Back dispatcher is registered in {@link BaseActivity#onCreate}. For predictive back to
// work, we must opt-in BEFORE registering back dispatcher. So we need to call
// setEnableOnBackInvokedCallback() before super.onCreate()
- if (Utilities.ATLEAST_U && enablePredictiveBackGesture()) {
- getApplicationInfo().setEnableOnBackInvokedCallback(true);
- }
+ getApplicationInfo().setEnableOnBackInvokedCallback(true);
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mPendingSplitSelectInfo = ObjectWrapper.unwrap(
@@ -908,8 +905,7 @@
// event won't go through ViewRootImpl#InputStage#onProcess.
// So when receive back key, try to do the same check thing in
// ViewRootImpl#NativePreImeInputStage#onProcess
- if (!Utilities.ATLEAST_U || !enablePredictiveBackGesture()
- || event.getKeyCode() != KeyEvent.KEYCODE_BACK
+ if (event.getKeyCode() != KeyEvent.KEYCODE_BACK
|| event.getAction() != KeyEvent.ACTION_UP || event.isCanceled()) {
return false;
}
@@ -920,10 +916,6 @@
@Override
protected void registerBackDispatcher() {
- if (!enablePredictiveBackGesture()) {
- super.registerBackDispatcher();
- return;
- }
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
new OnBackAnimationCallback() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3d8c1f9..05627be 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -734,11 +734,18 @@
}
private void maybeUpdateRecentsAttachedState() {
- maybeUpdateRecentsAttachedState(true /* animate */);
+ maybeUpdateRecentsAttachedState(/* animate= */ true);
}
protected void maybeUpdateRecentsAttachedState(boolean animate) {
- maybeUpdateRecentsAttachedState(animate, false /* moveRunningTask */);
+ maybeUpdateRecentsAttachedState(animate, /* moveRunningTask= */ false);
+ }
+
+ protected void maybeUpdateRecentsAttachedState(boolean animate, boolean moveRunningTask) {
+ maybeUpdateRecentsAttachedState(
+ animate,
+ moveRunningTask,
+ mRecentsView != null && mRecentsView.shouldUpdateRunningTaskAlpha());
}
/**
@@ -749,8 +756,10 @@
* Note this method has no effect unless the navigation mode is NO_BUTTON.
* @param animate whether to animate when attaching RecentsView
* @param moveRunningTask whether to move running task to front when attaching
+ * @param updateRunningTaskAlpha Whether to update the running task's attached alpha
*/
- private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveRunningTask) {
+ private void maybeUpdateRecentsAttachedState(
+ boolean animate, boolean moveRunningTask, boolean updateRunningTaskAlpha) {
if ((!mDeviceState.isFullyGesturalNavMode() && !mGestureState.isTrackpadGesture())
|| mRecentsView == null) {
return;
@@ -781,7 +790,8 @@
// TaskView jumping to new position as we move the tasks.
mRecentsView.moveRunningTaskToFront();
}
- mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
+ mAnimationFactory.setRecentsAttachedToAppWindow(
+ recentsAttachedToAppWindow, animate, updateRunningTaskAlpha);
// Reapply window transform throughout the attach animation, as the animation affects how
// much the window is bound by overscroll (vs moving freely).
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 293944d..8703843 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -21,11 +21,13 @@
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_ATTACHED_ALPHA_ANIM;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.animation.Animator;
@@ -187,8 +189,10 @@
* @param attached Whether to show RecentsView alongside the app window. If false, recents
* will be hidden by some property we can animate, e.g. alpha.
* @param animate Whether to animate recents to/from its new attached state.
+ * @param updateRunningTaskAlpha Whether to update the running task's attached alpha
*/
- default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
+ default void setRecentsAttachedToAppWindow(
+ boolean attached, boolean animate, boolean updateRunningTaskAlpha) { }
default boolean isRecentsAttachedToAppWindow() {
return false;
@@ -253,12 +257,14 @@
// (because we set the animation as the current state animation), so we reapply the
// attached state here as well to ensure recents is shown/hidden appropriately.
if (DisplayController.getNavigationMode(mActivity) == NavigationMode.NO_BUTTON) {
- setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
+ setRecentsAttachedToAppWindow(
+ mIsAttachedToWindow, false, recentsView.shouldUpdateRunningTaskAlpha());
}
}
@Override
- public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
+ public void setRecentsAttachedToAppWindow(
+ boolean attached, boolean animate, boolean updateRunningTaskAlpha) {
if (mIsAttachedToWindow == attached && animate) {
return;
}
@@ -266,6 +272,10 @@
.cancelStateElementAnimation(INDEX_RECENTS_FADE_ANIM);
mActivity.getStateManager()
.cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
+ if (updateRunningTaskAlpha) {
+ mActivity.getStateManager()
+ .cancelStateElementAnimation(INDEX_RECENTS_ATTACHED_ALPHA_ANIM);
+ }
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.addListener(new AnimatorListenerAdapter() {
@@ -280,19 +290,28 @@
long animationDuration = animate ? RECENTS_ATTACH_DURATION : 0;
Animator fadeAnim = mActivity.getStateManager()
- .createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
+ .createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1f : 0f);
fadeAnim.setInterpolator(attached ? INSTANT : ACCELERATE_2);
fadeAnim.setDuration(animationDuration);
animatorSet.play(fadeAnim);
float fromTranslation = ADJACENT_PAGE_HORIZONTAL_OFFSET.get(
mActivity.getOverviewPanel());
- float toTranslation = attached ? 0 : 1;
-
+ float toTranslation = attached ? 0f : 1f;
Animator translationAnimator = mActivity.getStateManager().createStateElementAnimation(
INDEX_RECENTS_TRANSLATE_X_ANIM, fromTranslation, toTranslation);
translationAnimator.setDuration(animationDuration);
animatorSet.play(translationAnimator);
+
+ if (updateRunningTaskAlpha) {
+ float fromAlpha = RUNNING_TASK_ATTACH_ALPHA.get(mActivity.getOverviewPanel());
+ float toAlpha = attached ? 1f : 0f;
+ Animator runningTaskAttachAlphaAnimator = mActivity.getStateManager()
+ .createStateElementAnimation(
+ INDEX_RECENTS_ATTACHED_ALPHA_ANIM, fromAlpha, toAlpha);
+ runningTaskAttachAlphaAnimator.setDuration(animationDuration);
+ animatorSet.play(runningTaskAttachAlphaAnimator);
+ }
animatorSet.start();
}
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/inputconsumers/BubbleBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
index dbe2068..92031c5 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
@@ -45,6 +45,7 @@
private boolean mSwipeUpOnBubbleHandle;
private boolean mPassedTouchSlop;
+ private boolean mStashedOrCollapsedOnDown;
private final int mTouchSlop;
private final PointF mDownPos = new PointF();
@@ -69,13 +70,13 @@
@Override
public void onMotionEvent(MotionEvent ev) {
- final boolean isStashed = mBubbleStashController.isStashed();
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
+ mStashedOrCollapsedOnDown = mBubbleStashController.isStashed() || isCollapsed();
break;
case MotionEvent.ACTION_MOVE:
int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -89,7 +90,7 @@
if (!mPassedTouchSlop) {
mPassedTouchSlop = Math.abs(dY) > mTouchSlop || Math.abs(dX) > mTouchSlop;
}
- if ((isCollapsed() || isStashed) && !mSwipeUpOnBubbleHandle && mPassedTouchSlop) {
+ if (mStashedOrCollapsedOnDown && !mSwipeUpOnBubbleHandle && mPassedTouchSlop) {
boolean verticalGesture = Math.abs(dY) > Math.abs(dX);
if (verticalGesture && !mBubbleDragController.isDragging()) {
mSwipeUpOnBubbleHandle = true;
@@ -102,11 +103,10 @@
break;
case MotionEvent.ACTION_UP:
boolean isWithinTapTime = ev.getEventTime() - ev.getDownTime() <= mTimeForTap;
- if (isWithinTapTime && !mSwipeUpOnBubbleHandle && !mPassedTouchSlop) {
+ if (isWithinTapTime && !mSwipeUpOnBubbleHandle && !mPassedTouchSlop
+ && mStashedOrCollapsedOnDown) {
// Taps on the handle / collapsed state should open the bar
- if (isStashed || isCollapsed()) {
- mBubbleStashController.showBubbleBar(/* expandBubbles= */ true);
- }
+ mBubbleStashController.showBubbleBar(/* expandBubbles= */ true);
}
break;
}
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/RecentsAtomicAnimationFactory.java b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
index 0b05c2e..63fe017 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
@@ -16,6 +16,7 @@
package com.android.quickstep.util;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
+import static com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA;
import android.animation.Animator;
import android.animation.ObjectAnimator;
@@ -33,8 +34,10 @@
public static final int INDEX_RECENTS_FADE_ANIM = AtomicAnimationFactory.NEXT_INDEX + 0;
public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = AtomicAnimationFactory.NEXT_INDEX + 1;
+ public static final int INDEX_RECENTS_ATTACHED_ALPHA_ANIM =
+ AtomicAnimationFactory.NEXT_INDEX + 2;
- private static final int MY_ANIM_COUNT = 2;
+ private static final int MY_ANIM_COUNT = 3;
protected final CONTAINER mContainer;
@@ -50,6 +53,7 @@
ObjectAnimator alpha = ObjectAnimator.ofFloat(mContainer.getOverviewPanel(),
RecentsView.CONTENT_ALPHA, values);
return alpha;
+ case INDEX_RECENTS_ATTACHED_ALPHA_ANIM:
case INDEX_RECENTS_TRANSLATE_X_ANIM: {
RecentsView rv = mContainer.getOverviewPanel();
return new SpringAnimationBuilder(mContainer)
@@ -57,7 +61,8 @@
.setDampingRatio(0.8f)
.setStiffness(250)
.setValues(values)
- .build(rv, ADJACENT_PAGE_HORIZONTAL_OFFSET);
+ .build(rv, index == INDEX_RECENTS_ATTACHED_ALPHA_ANIM
+ ? RUNNING_TASK_ATTACH_ALPHA : ADJACENT_PAGE_HORIZONTAL_OFFSET);
}
default:
return super.createStateElementAnimation(index, values);
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/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
deleted file mode 100644
index 8b87718..0000000
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ /dev/null
@@ -1,444 +0,0 @@
-/*
- * Copyright (C) 2018 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.views;
-
-import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS;
-
-import static com.android.launcher3.Utilities.prefixTextWithIcon;
-import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
-
-import android.app.ActivityOptions;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.AppUsageLimit;
-import android.graphics.Outline;
-import android.graphics.Paint;
-import android.icu.text.MeasureFormat;
-import android.icu.text.MeasureFormat.FormatWidth;
-import android.icu.util.Measure;
-import android.icu.util.MeasureUnit;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.Pair;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.quickstep.TaskUtils;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.systemui.shared.recents.model.Task;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.time.Duration;
-import java.util.Locale;
-
-public final class DigitalWellBeingToast {
-
- private static final float THRESHOLD_LEFT_ICON_ONLY = 0.4f;
- private static final float THRESHOLD_RIGHT_ICON_ONLY = 0.6f;
-
- /** Will span entire width of taskView with full text */
- private static final int SPLIT_BANNER_FULLSCREEN = 0;
- /** Used for grid task view, only showing icon and time */
- private static final int SPLIT_GRID_BANNER_LARGE = 1;
- /** Used for grid task view, only showing icon */
- private static final int SPLIT_GRID_BANNER_SMALL = 2;
-
- @IntDef(value = {
- SPLIT_BANNER_FULLSCREEN,
- SPLIT_GRID_BANNER_LARGE,
- SPLIT_GRID_BANNER_SMALL,
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface SplitBannerConfig {
- }
-
- static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
- static final int MINUTE_MS = 60000;
-
- private static final String TAG = "DigitalWellBeingToast";
-
- private final RecentsViewContainer mContainer;
- private final TaskView mTaskView;
- private final LauncherApps mLauncherApps;
-
- private final int mBannerHeight;
-
- private Task mTask;
- private boolean mHasLimit;
-
- private long mAppRemainingTimeMs;
- @Nullable
- private View mBanner;
- private ViewOutlineProvider mOldBannerOutlineProvider;
- private float mBannerOffsetPercentage;
- @Nullable
- private SplitBounds mSplitBounds;
- private float mSplitOffsetTranslationY;
- private float mSplitOffsetTranslationX;
-
- private boolean mIsDestroyed = false;
-
- public DigitalWellBeingToast(RecentsViewContainer container, TaskView taskView) {
- mContainer = container;
- mTaskView = taskView;
- mLauncherApps = container.asContext().getSystemService(LauncherApps.class);
- mBannerHeight = container.asContext().getResources().getDimensionPixelSize(
- R.dimen.digital_wellbeing_toast_height);
- }
-
- private void setNoLimit() {
- mHasLimit = false;
- mTaskView.setContentDescription(mTask.titleDescription);
- replaceBanner(null);
- mAppRemainingTimeMs = -1;
- }
-
- private void setLimit(long appUsageLimitTimeMs, long appRemainingTimeMs) {
- mAppRemainingTimeMs = appRemainingTimeMs;
- mHasLimit = true;
- TextView toast = mContainer.getViewCache().getView(R.layout.digital_wellbeing_toast,
- mContainer.asContext(), mTaskView);
- toast.setText(prefixTextWithIcon(mContainer.asContext(), R.drawable.ic_hourglass_top,
- getText()));
- toast.setOnClickListener(this::openAppUsageSettings);
- replaceBanner(toast);
-
- mTaskView.setContentDescription(
- getContentDescriptionForTask(mTask, appUsageLimitTimeMs, appRemainingTimeMs));
- }
-
- public String getText() {
- return getText(mAppRemainingTimeMs, false /* forContentDesc */);
- }
-
- public boolean hasLimit() {
- return mHasLimit;
- }
-
- public void initialize(Task task) {
- if (mIsDestroyed) {
- throw new IllegalStateException("Cannot re-initialize a destroyed toast");
- }
- mTask = task;
- ORDERED_BG_EXECUTOR.execute(() -> {
- AppUsageLimit usageLimit = null;
- try {
- usageLimit = mLauncherApps.getAppUsageLimit(
- mTask.getTopComponent().getPackageName(),
- UserHandle.of(mTask.key.userId));
- } catch (Exception e) {
- Log.e(TAG, "Error initializing digital well being toast", e);
- }
- final long appUsageLimitTimeMs =
- usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
- final long appRemainingTimeMs =
- usageLimit != null ? usageLimit.getUsageRemaining() : -1;
-
- mTaskView.post(() -> {
- if (mIsDestroyed) {
- return;
- }
- if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
- setNoLimit();
- } else {
- setLimit(appUsageLimitTimeMs, appRemainingTimeMs);
- }
- });
- });
- }
-
- /**
- * Mark the DWB toast as destroyed and remove banner from TaskView.
- */
- public void destroy() {
- mIsDestroyed = true;
- mTaskView.post(() -> replaceBanner(null));
- }
-
- public void setSplitBounds(@Nullable SplitBounds splitBounds) {
- mSplitBounds = splitBounds;
- }
-
- private @SplitBannerConfig int getSplitBannerConfig() {
- if (mSplitBounds == null
- || !mContainer.getDeviceProfile().isTablet
- || mTaskView.isLargeTile()) {
- return SPLIT_BANNER_FULLSCREEN;
- }
-
- // For portrait grid only height of task changes, not width. So we keep the text the same
- if (!mContainer.getDeviceProfile().isLeftRightSplit) {
- return SPLIT_GRID_BANNER_LARGE;
- }
-
- // For landscape grid, for 30% width we only show icon, otherwise show icon and time
- if (mTask.key.id == mSplitBounds.leftTopTaskId) {
- return mSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY
- ? SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
- } else {
- return mSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY
- ? SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
- }
- }
-
- private String getReadableDuration(
- Duration duration,
- @StringRes int durationLessThanOneMinuteStringId) {
- int hours = Math.toIntExact(duration.toHours());
- int minutes = Math.toIntExact(duration.minusHours(hours).toMinutes());
-
- // Apply FormatWidth.WIDE if both the hour part and the minute part are non-zero.
- if (hours > 0 && minutes > 0) {
- return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.NARROW)
- .formatMeasures(
- new Measure(hours, MeasureUnit.HOUR),
- new Measure(minutes, MeasureUnit.MINUTE));
- }
-
- // Apply FormatWidth.WIDE if only the hour part is non-zero (unless forced).
- if (hours > 0) {
- return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures(
- new Measure(hours, MeasureUnit.HOUR));
- }
-
- // Apply FormatWidth.WIDE if only the minute part is non-zero (unless forced).
- if (minutes > 0) {
- return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures(
- new Measure(minutes, MeasureUnit.MINUTE));
- }
-
- // Use a specific string for usage less than one minute but non-zero.
- if (duration.compareTo(Duration.ZERO) > 0) {
- return mContainer.asContext().getString(durationLessThanOneMinuteStringId);
- }
-
- // Otherwise, return 0-minute string.
- return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE).formatMeasures(
- new Measure(0, MeasureUnit.MINUTE));
- }
-
- /**
- * Returns text to show for the banner depending on {@link #getSplitBannerConfig()}
- * If {@param forContentDesc} is {@code true}, this will always return the full
- * string corresponding to {@link #SPLIT_BANNER_FULLSCREEN}
- */
- private String getText(long remainingTime, boolean forContentDesc) {
- final Duration duration = Duration.ofMillis(
- remainingTime > MINUTE_MS ?
- (remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS :
- remainingTime);
- String readableDuration = getReadableDuration(duration,
- R.string.shorter_duration_less_than_one_minute
- /* forceFormatWidth */);
- @SplitBannerConfig int splitBannerConfig = getSplitBannerConfig();
- if (forContentDesc || splitBannerConfig == SPLIT_BANNER_FULLSCREEN) {
- return mContainer.asContext().getString(
- R.string.time_left_for_app,
- readableDuration);
- }
-
- if (splitBannerConfig == SPLIT_GRID_BANNER_SMALL) {
- // show no text
- return "";
- } else { // SPLIT_GRID_BANNER_LARGE
- // only show time
- return readableDuration;
- }
- }
-
- public void openAppUsageSettings(View view) {
- final Intent intent = new Intent(OPEN_APP_USAGE_SETTINGS_TEMPLATE)
- .putExtra(Intent.EXTRA_PACKAGE_NAME,
- mTask.getTopComponent().getPackageName()).addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- try {
- final RecentsViewContainer container =
- RecentsViewContainer.containerFromContext(view.getContext());
- final ActivityOptions options = ActivityOptions.makeScaleUpAnimation(
- view, 0, 0,
- view.getWidth(), view.getHeight());
- container.asContext().startActivity(intent, options.toBundle());
-
- // TODO: add WW logging on the app usage settings click.
- } catch (ActivityNotFoundException e) {
- Log.e(TAG, "Failed to open app usage settings for task "
- + mTask.getTopComponent().getPackageName(), e);
- }
- }
-
- private String getContentDescriptionForTask(
- Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) {
- return appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0 ?
- mContainer.asContext().getString(
- R.string.task_contents_description_with_remaining_time,
- task.titleDescription,
- getText(appRemainingTimeMs, true /* forContentDesc */)) :
- task.titleDescription;
- }
-
- private void replaceBanner(@Nullable View view) {
- resetOldBanner();
- setBanner(view);
- }
-
- private void resetOldBanner() {
- if (mBanner != null) {
- mBanner.setOutlineProvider(mOldBannerOutlineProvider);
- mTaskView.removeView(mBanner);
- mBanner.setOnClickListener(null);
- mContainer.getViewCache().recycleView(R.layout.digital_wellbeing_toast, mBanner);
- }
- }
-
- private void setBanner(@Nullable View view) {
- mBanner = view;
- if (mBanner != null && mTaskView.getRecentsView() != null) {
- setupAndAddBanner();
- setBannerOutline();
- }
- }
-
- private void setupAndAddBanner() {
- FrameLayout.LayoutParams layoutParams =
- (FrameLayout.LayoutParams) mBanner.getLayoutParams();
- DeviceProfile deviceProfile = mContainer.getDeviceProfile();
- layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams)
- mTaskView.getFirstSnapshotView().getLayoutParams()).bottomMargin;
- RecentsPagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
- Pair<Float, Float> translations = orientationHandler
- .getDwbLayoutTranslations(mTaskView.getMeasuredWidth(),
- mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile,
- mTaskView.getSnapshotViews(), mTask.key.id, mBanner);
- mSplitOffsetTranslationX = translations.first;
- mSplitOffsetTranslationY = translations.second;
- updateTranslationY();
- updateTranslationX();
- mTaskView.addView(mBanner);
- }
-
- private void setBannerOutline() {
- // TODO(b\273367585) to investigate why mBanner.getOutlineProvider() can be null
- mOldBannerOutlineProvider = mBanner.getOutlineProvider() != null
- ? mBanner.getOutlineProvider()
- : ViewOutlineProvider.BACKGROUND;
-
- mBanner.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- mOldBannerOutlineProvider.getOutline(view, outline);
- float verticalTranslation = -view.getTranslationY() + mSplitOffsetTranslationY;
- outline.offset(0, Math.round(verticalTranslation));
- }
- });
- mBanner.setClipToOutline(true);
- }
-
- void updateBannerOffset(float offsetPercentage) {
- if (mBannerOffsetPercentage != offsetPercentage) {
- mBannerOffsetPercentage = offsetPercentage;
- if (mBanner != null) {
- updateTranslationY();
- mBanner.invalidateOutline();
- }
- }
- }
-
- private void updateTranslationY() {
- if (mBanner == null) {
- return;
- }
-
- mBanner.setTranslationY(
- (mBannerOffsetPercentage * mBannerHeight) + mSplitOffsetTranslationY);
- }
-
- private void updateTranslationX() {
- if (mBanner == null) {
- return;
- }
-
- mBanner.setTranslationX(mSplitOffsetTranslationX);
- }
-
- void setBannerColorTint(int color, float amount) {
- if (mBanner == null) {
- return;
- }
- if (amount == 0) {
- mBanner.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- Paint layerPaint = new Paint();
- layerPaint.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount));
- mBanner.setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint);
- mBanner.setLayerPaint(layerPaint);
- }
-
- void setBannerVisibility(int visibility) {
- if (mBanner == null) {
- return;
- }
-
- mBanner.setVisibility(visibility);
- }
-
- private int getAccessibilityActionId() {
- return (mSplitBounds != null
- && mSplitBounds.rightBottomTaskId == mTask.key.id)
- ? R.id.action_digital_wellbeing_bottom_right
- : R.id.action_digital_wellbeing_top_left;
- }
-
- @Nullable
- public AccessibilityNodeInfo.AccessibilityAction getDWBAccessibilityAction() {
- if (!hasLimit()) {
- return null;
- }
-
- Context context = mContainer.asContext();
- String label =
- (mTaskView.containsMultipleTasks())
- ? context.getString(
- R.string.split_app_usage_settings,
- TaskUtils.getTitle(context, mTask)
- ) : context.getString(R.string.accessibility_app_usage_settings);
- return new AccessibilityNodeInfo.AccessibilityAction(getAccessibilityActionId(), label);
- }
-
- public boolean handleAccessibilityAction(int action) {
- if (getAccessibilityActionId() == action) {
- openAppUsageSettings(mTaskView);
- return true;
- } else {
- return false;
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
new file mode 100644
index 0000000..0ab36c9
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2018 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.views
+
+import android.app.ActivityOptions
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.content.pm.LauncherApps
+import android.content.pm.LauncherApps.AppUsageLimit
+import android.graphics.Outline
+import android.graphics.Paint
+import android.icu.text.MeasureFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup.MarginLayoutParams
+import android.view.ViewOutlineProvider
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.FrameLayout
+import android.widget.TextView
+import androidx.annotation.StringRes
+import androidx.core.util.component1
+import androidx.core.util.component2
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.quickstep.TaskUtils
+import com.android.systemui.shared.recents.model.Task
+import java.time.Duration
+import java.util.Locale
+
+class DigitalWellBeingToast(
+ private val container: RecentsViewContainer,
+ private val taskView: TaskView
+) {
+ private val launcherApps: LauncherApps? =
+ container.asContext().getSystemService(LauncherApps::class.java)
+
+ private val bannerHeight =
+ container
+ .asContext()
+ .resources
+ .getDimensionPixelSize(R.dimen.digital_wellbeing_toast_height)
+
+ private lateinit var task: Task
+
+ private var appRemainingTimeMs: Long = 0
+ private var banner: View? = null
+ private var oldBannerOutlineProvider: ViewOutlineProvider? = null
+ private var splitOffsetTranslationY = 0f
+ private var splitOffsetTranslationX = 0f
+
+ private var isDestroyed = false
+
+ var hasLimit = false
+ var splitBounds: SplitConfigurationOptions.SplitBounds? = null
+ var bannerOffsetPercentage = 0f
+ set(value) {
+ if (field != value) {
+ field = value
+ banner?.let {
+ updateTranslationY()
+ it.invalidateOutline()
+ }
+ }
+ }
+
+ private fun setNoLimit() {
+ hasLimit = false
+ taskView.contentDescription = task.titleDescription
+ replaceBanner(null)
+ appRemainingTimeMs = -1
+ }
+
+ private fun setLimit(appUsageLimitTimeMs: Long, appRemainingTimeMs: Long) {
+ this.appRemainingTimeMs = appRemainingTimeMs
+ hasLimit = true
+ val toast =
+ container.viewCache
+ .getView<TextView>(
+ R.layout.digital_wellbeing_toast,
+ container.asContext(),
+ taskView
+ )
+ .apply {
+ text =
+ Utilities.prefixTextWithIcon(
+ container.asContext(),
+ R.drawable.ic_hourglass_top,
+ getBannerText()
+ )
+ setOnClickListener(::openAppUsageSettings)
+ }
+ replaceBanner(toast)
+
+ taskView.contentDescription =
+ getContentDescriptionForTask(task, appUsageLimitTimeMs, appRemainingTimeMs)
+ }
+
+ fun initialize(task: Task) {
+ check(!isDestroyed) { "Cannot re-initialize a destroyed toast" }
+ this.task = task
+ Executors.ORDERED_BG_EXECUTOR.execute {
+ var usageLimit: AppUsageLimit? = null
+ try {
+ usageLimit =
+ launcherApps?.getAppUsageLimit(
+ this.task.topComponent.packageName,
+ UserHandle.of(this.task.key.userId)
+ )
+ } catch (e: Exception) {
+ Log.e(TAG, "Error initializing digital well being toast", e)
+ }
+ val appUsageLimitTimeMs = usageLimit?.totalUsageLimit ?: -1
+ val appRemainingTimeMs = usageLimit?.usageRemaining ?: -1
+ taskView.post {
+ if (isDestroyed) {
+ return@post
+ }
+ if (appUsageLimitTimeMs < 0 || appRemainingTimeMs < 0) {
+ setNoLimit()
+ } else {
+ setLimit(appUsageLimitTimeMs, appRemainingTimeMs)
+ }
+ }
+ }
+ }
+
+ /** Mark the DWB toast as destroyed and remove banner from TaskView. */
+ fun destroy() {
+ isDestroyed = true
+ taskView.post { replaceBanner(null) }
+ }
+
+ private fun getSplitBannerConfig(): SplitBannerConfig {
+ val splitBounds = splitBounds
+ return when {
+ splitBounds == null || !container.deviceProfile.isTablet || taskView.isLargeTile ->
+ SplitBannerConfig.SPLIT_BANNER_FULLSCREEN
+ // For portrait grid only height of task changes, not width. So we keep the text the
+ // same
+ !container.deviceProfile.isLeftRightSplit -> SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
+ // For landscape grid, for 30% width we only show icon, otherwise show icon and time
+ task.key.id == splitBounds.leftTopTaskId ->
+ if (splitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY)
+ SplitBannerConfig.SPLIT_GRID_BANNER_SMALL
+ else SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
+ else ->
+ if (splitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY)
+ SplitBannerConfig.SPLIT_GRID_BANNER_SMALL
+ else SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
+ }
+ }
+
+ private fun getReadableDuration(
+ duration: Duration,
+ @StringRes durationLessThanOneMinuteStringId: Int
+ ): String {
+ val hours = Math.toIntExact(duration.toHours())
+ val minutes = Math.toIntExact(duration.minusHours(hours.toLong()).toMinutes())
+ return when {
+ // Apply FormatWidth.WIDE if both the hour part and the minute part are non-zero.
+ hours > 0 && minutes > 0 ->
+ MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.NARROW)
+ .formatMeasures(
+ Measure(hours, MeasureUnit.HOUR),
+ Measure(minutes, MeasureUnit.MINUTE)
+ )
+ // Apply FormatWidth.WIDE if only the hour part is non-zero (unless forced).
+ hours > 0 ->
+ MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(Measure(hours, MeasureUnit.HOUR))
+ // Apply FormatWidth.WIDE if only the minute part is non-zero (unless forced).
+ minutes > 0 ->
+ MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(Measure(minutes, MeasureUnit.MINUTE))
+ // Use a specific string for usage less than one minute but non-zero.
+ duration > Duration.ZERO ->
+ container.asContext().getString(durationLessThanOneMinuteStringId)
+ // Otherwise, return 0-minute string.
+ else ->
+ MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(Measure(0, MeasureUnit.MINUTE))
+ }
+ }
+
+ /**
+ * Returns text to show for the banner depending on [.getSplitBannerConfig] If {@param
+ * forContentDesc} is `true`, this will always return the full string corresponding to
+ * [.SPLIT_BANNER_FULLSCREEN]
+ */
+ @JvmOverloads
+ fun getBannerText(
+ remainingTime: Long = appRemainingTimeMs,
+ forContentDesc: Boolean = false
+ ): String {
+ val duration =
+ Duration.ofMillis(
+ if (remainingTime > MINUTE_MS)
+ (remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS
+ else remainingTime
+ )
+ val readableDuration =
+ getReadableDuration(
+ duration,
+ R.string.shorter_duration_less_than_one_minute /* forceFormatWidth */
+ )
+ val splitBannerConfig = getSplitBannerConfig()
+ return when {
+ forContentDesc || splitBannerConfig == SplitBannerConfig.SPLIT_BANNER_FULLSCREEN ->
+ container.asContext().getString(R.string.time_left_for_app, readableDuration)
+ // show no text
+ splitBannerConfig == SplitBannerConfig.SPLIT_GRID_BANNER_SMALL -> ""
+ // SPLIT_GRID_BANNER_LARGE only show time
+ else -> readableDuration
+ }
+ }
+
+ private fun openAppUsageSettings(view: View) {
+ val intent =
+ Intent(OPEN_APP_USAGE_SETTINGS_TEMPLATE)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, task.topComponent.packageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ try {
+ val options = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.width, view.height)
+ container.asContext().startActivity(intent, options.toBundle())
+
+ // TODO: add WW logging on the app usage settings click.
+ } catch (e: ActivityNotFoundException) {
+ Log.e(
+ TAG,
+ "Failed to open app usage settings for task " + task.topComponent.packageName,
+ e
+ )
+ }
+ }
+
+ private fun getContentDescriptionForTask(
+ task: Task,
+ appUsageLimitTimeMs: Long,
+ appRemainingTimeMs: Long
+ ): String? =
+ if (appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0)
+ container
+ .asContext()
+ .getString(
+ R.string.task_contents_description_with_remaining_time,
+ task.titleDescription,
+ getBannerText(appRemainingTimeMs, true /* forContentDesc */)
+ )
+ else task.titleDescription
+
+ private fun replaceBanner(view: View?) {
+ resetOldBanner()
+ setBanner(view)
+ }
+
+ private fun resetOldBanner() {
+ val banner = banner ?: return
+ banner.outlineProvider = oldBannerOutlineProvider
+ taskView.removeView(banner)
+ banner.setOnClickListener(null)
+ container.viewCache.recycleView(R.layout.digital_wellbeing_toast, banner)
+ }
+
+ private fun setBanner(banner: View?) {
+ this.banner = banner
+ if (banner != null && taskView.recentsView != null) {
+ setupAndAddBanner()
+ setBannerOutline()
+ }
+ }
+
+ private fun setupAndAddBanner() {
+ val banner = banner ?: return
+ banner.updateLayoutParams<FrameLayout.LayoutParams> {
+ bottomMargin =
+ (taskView.firstSnapshotView.layoutParams as MarginLayoutParams).bottomMargin
+ }
+ val (translationX, translationY) =
+ taskView.pagedOrientationHandler.getDwbLayoutTranslations(
+ taskView.measuredWidth,
+ taskView.measuredHeight,
+ splitBounds,
+ container.deviceProfile,
+ taskView.snapshotViews,
+ task.key.id,
+ banner
+ )
+ splitOffsetTranslationX = translationX
+ splitOffsetTranslationY = translationY
+ updateTranslationY()
+ updateTranslationX()
+ taskView.addView(banner)
+ }
+
+ private fun setBannerOutline() {
+ val banner = banner ?: return
+ // TODO(b\273367585) to investigate why mBanner.getOutlineProvider() can be null
+ val oldBannerOutlineProvider =
+ if (banner.outlineProvider != null) banner.outlineProvider
+ else ViewOutlineProvider.BACKGROUND
+ this.oldBannerOutlineProvider = oldBannerOutlineProvider
+
+ banner.outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ oldBannerOutlineProvider.getOutline(view, outline)
+ val verticalTranslation = -view.translationY + splitOffsetTranslationY
+ outline.offset(0, Math.round(verticalTranslation))
+ }
+ }
+ banner.clipToOutline = true
+ }
+
+ private fun updateTranslationY() {
+ banner?.translationY = bannerOffsetPercentage * bannerHeight + splitOffsetTranslationY
+ }
+
+ private fun updateTranslationX() {
+ banner?.translationX = splitOffsetTranslationX
+ }
+
+ fun setBannerColorTint(color: Int, amount: Float) {
+ val banner = banner ?: return
+ if (amount == 0f) {
+ banner.setLayerType(View.LAYER_TYPE_NONE, null)
+ }
+ val layerPaint = Paint()
+ layerPaint.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount))
+ banner.setLayerType(View.LAYER_TYPE_HARDWARE, layerPaint)
+ banner.setLayerPaint(layerPaint)
+ }
+
+ fun setBannerVisibility(visibility: Int) {
+ banner?.visibility = visibility
+ }
+
+ private fun getAccessibilityActionId(): Int =
+ if (splitBounds?.rightBottomTaskId == task.key.id)
+ R.id.action_digital_wellbeing_bottom_right
+ else R.id.action_digital_wellbeing_top_left
+
+ fun getDWBAccessibilityAction(): AccessibilityNodeInfo.AccessibilityAction? {
+ if (!hasLimit) return null
+ val context = container.asContext()
+ val label =
+ if ((taskView.containsMultipleTasks()))
+ context.getString(
+ R.string.split_app_usage_settings,
+ TaskUtils.getTitle(context, task)
+ )
+ else context.getString(R.string.accessibility_app_usage_settings)
+ return AccessibilityNodeInfo.AccessibilityAction(getAccessibilityActionId(), label)
+ }
+
+ fun handleAccessibilityAction(action: Int): Boolean {
+ if (getAccessibilityActionId() != action) return false
+ openAppUsageSettings(taskView)
+ return true
+ }
+
+ companion object {
+ private const val THRESHOLD_LEFT_ICON_ONLY = 0.4f
+ private const val THRESHOLD_RIGHT_ICON_ONLY = 0.6f
+
+ enum class SplitBannerConfig {
+ /** Will span entire width of taskView with full text */
+ SPLIT_BANNER_FULLSCREEN,
+ /** Used for grid task view, only showing icon and time */
+ SPLIT_GRID_BANNER_LARGE,
+ /** Used for grid task view, only showing icon */
+ SPLIT_GRID_BANNER_SMALL
+ }
+
+ val OPEN_APP_USAGE_SETTINGS_TEMPLATE: Intent = Intent(Settings.ACTION_APP_USAGE_SETTINGS)
+ const val MINUTE_MS: Int = 60000
+
+ private const val TAG = "DigitalWellBeingToast"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
index 4dde635..bdca596 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
@@ -123,8 +123,8 @@
@Override
public void onGlobalLayout() {
if (isUninitialized()) return;
- positionViews();
- if (mOnTargetChangeRunnable != null) {
+ boolean positionsChanged = positionViews();
+ if (mOnTargetChangeRunnable != null && positionsChanged) {
mOnTargetChangeRunnable.run();
}
}
@@ -212,21 +212,43 @@
onGlobalLayout();
}
- /** Sets the layout parameters of the floating view and its background view child. */
- private void positionViews() {
+ /**
+ * Sets the layout parameters of the floating view and its background view child.
+ * @return true if any of the views positions change due to this call.
+ */
+ private boolean positionViews() {
+ boolean positionsChanged = false;
+
LayoutParams layoutParams = (LayoutParams) getLayoutParams();
- layoutParams.setMargins(0, 0, 0, 0);
- setLayoutParams(layoutParams);
+
+ if (layoutParams.topMargin != 0 || layoutParams.bottomMargin != 0
+ || layoutParams.rightMargin != 0 || layoutParams.leftMargin != 0) {
+ positionsChanged = true;
+ layoutParams.setMargins(0, 0, 0, 0);
+ setLayoutParams(layoutParams);
+ }
// FloatingWidgetView layout is forced LTR
- mBackgroundView.setTranslationX(mBackgroundPosition.left);
- mBackgroundView.setTranslationY(mBackgroundPosition.top + mIconOffsetY);
+ float targetY = mBackgroundPosition.top + mIconOffsetY;
+ if (mBackgroundView.getTranslationX() != mBackgroundPosition.left
+ || mBackgroundView.getTranslationY() != targetY) {
+ positionsChanged = true;
+ mBackgroundView.setTranslationX(mBackgroundPosition.left);
+ mBackgroundView.setTranslationY(targetY);
+ }
+
LayoutParams backgroundParams = (LayoutParams) mBackgroundView.getLayoutParams();
- backgroundParams.leftMargin = 0;
- backgroundParams.topMargin = 0;
- backgroundParams.width = (int) mBackgroundPosition.width();
- backgroundParams.height = (int) mBackgroundPosition.height();
- mBackgroundView.setLayoutParams(backgroundParams);
+ if (backgroundParams.leftMargin != 0 || backgroundParams.topMargin != 0
+ || backgroundParams.width != Math.round(mBackgroundPosition.width())
+ || backgroundParams.height != Math.round(mBackgroundPosition.height())) {
+ positionsChanged = true;
+
+ backgroundParams.leftMargin = 0;
+ backgroundParams.topMargin = 0;
+ backgroundParams.width = Math.round(mBackgroundPosition.width());
+ backgroundParams.height = Math.round(mBackgroundPosition.height());
+ mBackgroundView.setLayoutParams(backgroundParams);
+ }
if (mForegroundOverlayView != null) {
sTmpMatrix.reset();
@@ -237,8 +259,15 @@
sTmpMatrix.postScale(foregroundScale, foregroundScale);
sTmpMatrix.postTranslate(mBackgroundPosition.left, mBackgroundPosition.top
+ mIconOffsetY);
- mForegroundOverlayView.setMatrix(sTmpMatrix);
+
+ // We use the animation matrix here, because calling setMatrix on the GhostView
+ // actually sets the animation matrix, not the regular one.
+ if (!sTmpMatrix.equals(mForegroundOverlayView.getAnimationMatrix())) {
+ positionsChanged = true;
+ mForegroundOverlayView.setMatrix(sTmpMatrix);
+ }
}
+ return positionsChanged;
}
private void finish(DragLayer dragLayer) {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index a046c42..ba4d786 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -130,7 +130,7 @@
taskContainers.forEach { it.bind() }
this.splitBoundsConfig = splitBoundsConfig
- taskContainers.forEach { it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig) }
+ taskContainers.forEach { it.digitalWellBeingToast?.splitBounds = splitBoundsConfig }
setOrientationState(orientedState)
}
@@ -210,7 +210,7 @@
fun updateSplitBoundsConfig(splitBounds: SplitConfigurationOptions.SplitBounds?) {
splitBoundsConfig = splitBounds
taskContainers.forEach {
- it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig)
+ it.digitalWellBeingToast?.splitBounds = splitBoundsConfig
it.digitalWellBeingToast?.initialize(it.task)
}
invalidate()
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f1ff026..255619a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -36,6 +36,7 @@
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
+import static com.android.launcher3.Flags.enableDesktopTaskAlphaAnimation;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
@@ -320,6 +321,27 @@
}
};
+ public static final FloatProperty<RecentsView> RUNNING_TASK_ATTACH_ALPHA =
+ new FloatProperty<RecentsView>("runningTaskAttachAlpha") {
+ @Override
+ public void setValue(RecentsView recentsView, float v) {
+ TaskView runningTask = recentsView.getRunningTaskView();
+ if (runningTask == null) {
+ return;
+ }
+ runningTask.setAttachAlpha(v);
+ }
+
+ @Override
+ public Float get(RecentsView recentsView) {
+ TaskView runningTask = recentsView.getRunningTaskView();
+ if (runningTask == null) {
+ return null;
+ }
+ return runningTask.getAttachAlpha();
+ }
+ };
+
public static final int SCROLL_VIBRATION_PRIMITIVE =
Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1;
public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f;
@@ -1057,7 +1079,6 @@
@Nullable
public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
if (enableRefactorTaskThumbnail()) {
- mHelper.onTaskThumbnailChanged(taskId, thumbnailData);
return null;
}
if (mHandleTaskStackChanges) {
@@ -1078,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();
@@ -2527,7 +2547,6 @@
}
if (enableRefactorTaskThumbnail()) {
- // TODO(b/342560598): Listen in TaskRepository and reload.
return;
}
@@ -2737,10 +2756,17 @@
showCurrentTask(mActiveGestureRunningTasks);
setEnableFreeScroll(false);
setEnableDrawingLiveTile(false);
- setRunningTaskHidden(true);
+ setRunningTaskHidden(!shouldUpdateRunningTaskAlpha());
setTaskIconScaledDown(true);
}
+ /**
+ * Returns whether the running task's attach alpha should be updated during the attach animation
+ */
+ public boolean shouldUpdateRunningTaskAlpha() {
+ return enableDesktopTaskAlphaAnimation() && getRunningTaskView() instanceof DesktopTaskView;
+ }
+
private boolean isGestureActive() {
return mActiveGestureRunningTasks != null;
}
@@ -3021,12 +3047,13 @@
public void setRunningTaskHidden(boolean isHidden) {
mRunningTaskTileHidden = isHidden;
TaskView runningTask = getRunningTaskView();
- if (runningTask != null) {
- runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha);
- if (!isHidden) {
- AccessibilityManagerCompat.sendCustomAccessibilityEvent(runningTask,
- AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
- }
+ if (runningTask == null) {
+ return;
+ }
+ runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha);
+ if (!isHidden) {
+ AccessibilityManagerCompat.sendCustomAccessibilityEvent(
+ runningTask, AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
}
}
@@ -4701,8 +4728,11 @@
: showAsGrid
? gridOffsetSize
: i < modalMidpoint ? modalLeftOffsetSize : modalRightOffsetSize;
- float totalTranslationX = translation + modalTranslation;
View child = getChildAt(i);
+ boolean skipTranslationOffset = enableDesktopTaskAlphaAnimation()
+ && i == getRunningTaskIndex()
+ && child instanceof DesktopTaskView;
+ float totalTranslationX = (skipTranslationOffset ? 0f : translation) + modalTranslation;
FloatProperty translationPropertyX = child instanceof TaskView
? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty()
: getPagedOrientationHandler().getPrimaryViewTranslate();
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/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 3c40602..1f46fa7 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -65,6 +65,7 @@
import com.android.launcher3.util.Executors
import com.android.launcher3.util.MultiPropertyFactory
import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
+import com.android.launcher3.util.MultiValueAlpha
import com.android.launcher3.util.RunnableList
import com.android.launcher3.util.SafeCloseable
import com.android.launcher3.util.SplitConfigurationOptions
@@ -391,11 +392,19 @@
applyTranslationX()
}
- protected var stableAlpha = 1f
+ private val taskViewAlpha = MultiValueAlpha(this, NUM_ALPHA_CHANNELS)
+
+ protected var stableAlpha
set(value) {
- field = value
- alpha = stableAlpha
+ taskViewAlpha.get(ALPHA_INDEX_STABLE).value = value
}
+ get() = taskViewAlpha.get(ALPHA_INDEX_STABLE).value
+
+ protected var attachAlpha
+ set(value) {
+ taskViewAlpha.get(ALPHA_INDEX_ATTACH).value = value
+ }
+ get() = taskViewAlpha.get(ALPHA_INDEX_ATTACH).value
protected var shouldShowScreenshot = false
get() = !isRunningTask || field
@@ -1396,7 +1405,7 @@
private fun onFocusTransitionProgressUpdated(focusTransitionProgress: Float) {
taskContainers.forEach {
it.iconView.setContentAlpha(focusTransitionProgress)
- it.digitalWellBeingToast?.updateBannerOffset(1f - focusTransitionProgress)
+ it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - focusTransitionProgress
}
}
@@ -1546,7 +1555,7 @@
private fun onModalnessUpdated(modalness: Float) {
taskContainers.forEach {
it.iconView.setModalAlpha(1 - modalness)
- it.digitalWellBeingToast?.updateBannerOffset(modalness)
+ it.digitalWellBeingToast?.bannerOffsetPercentage = modalness
}
}
@@ -1582,7 +1591,7 @@
}
dismissScale = 1f
translationZ = 0f
- alpha = stableAlpha
+ attachAlpha = 1f
setIconScaleAndDim(1f)
setColorTint(0f, 0)
}
@@ -1659,6 +1668,11 @@
const val FOCUS_TRANSITION_INDEX_SCALE_AND_DIM = 1
const val FOCUS_TRANSITION_INDEX_COUNT = 2
+ private const val ALPHA_INDEX_STABLE = 0
+ private const val ALPHA_INDEX_ATTACH = 1
+
+ private const val NUM_ALPHA_CHANNELS = 2
+
/** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
const val MAX_PAGE_SCRIM_ALPHA = 0.4f
const val SCALE_ICON_DURATION: Long = 120
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/TaplDigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
index 6e25b10..e981570 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
@@ -67,15 +67,15 @@
mLauncher.goHome();
final DigitalWellBeingToast toast = getToast();
- waitForLauncherCondition("Toast is not visible", launcher -> toast.hasLimit());
- assertEquals("Toast text: ", "5 minutes left today", toast.getText());
+ waitForLauncherCondition("Toast is not visible", launcher -> toast.getHasLimit());
+ assertEquals("Toast text: ", "5 minutes left today", toast.getBannerText());
// Unset time limit for app.
runWithShellPermission(
() -> usageStatsManager.unregisterAppUsageLimitObserver(observerId));
mLauncher.goHome();
- assertFalse("Toast is visible", getToast().hasLimit());
+ assertFalse("Toast is visible", getToast().getHasLimit());
} finally {
runWithShellPermission(
() -> usageStatsManager.unregisterAppUsageLimitObserver(observerId));
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 597227a..f971afb 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -266,9 +266,6 @@
return launcher.<RecentsView>getOverviewPanel().getBottomRowTaskCountForTablet();
}
- // Staging; will be promoted to presubmit if stable
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
-
@Test
@NavigationModeSwitch
@PortraitLandscape
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/res/layout/launcher.xml b/res/layout/launcher.xml
index a709fbc..83c8d6c 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -51,7 +51,7 @@
<!-- Keep these behind the workspace so that they are not visible when
we go into AllApps -->
- <com.android.launcher3.pageindicators.WorkspacePageIndicator
+ <com.android.launcher3.pageindicators.PageIndicatorDots
android:id="@+id/page_indicator"
android:layout_width="match_parent"
android:layout_height="@dimen/workspace_page_indicator_height"
diff --git a/res/layout/page_indicator_dots.xml b/res/layout/page_indicator_dots.xml
deleted file mode 100644
index d5fe51e..0000000
--- a/res/layout/page_indicator_dots.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 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.
--->
-
-<com.android.launcher3.pageindicators.PageIndicatorDots xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/page_indicator"
- android:layout_width="match_parent"
- android:layout_height="@dimen/workspace_page_indicator_height"
- android:layout_gravity="bottom | center_horizontal"
- android:theme="@style/HomeScreenElementTheme" />
\ No newline at end of file
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index a054450..4f5d111 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -31,7 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran partagé"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Infos sur l\'appli pour %1$s"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
- <string name="save_app_pair" msgid="5647523853662686243">"Enregistrer la paire d\'applis"</string>
+ <string name="save_app_pair" msgid="5647523853662686243">"Enregistrer une paire d\'applis"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Cette paire d\'applications n\'est pas prise en charge sur cet appareil"</string>
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"Dépliez l\'appareil pour utiliser cette paire d\'applications"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5ba00c0..f8c075f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -311,7 +311,7 @@
<dimen name="blur_size_medium_outline">2dp</dimen>
<dimen name="blur_size_click_shadow">4dp</dimen>
<dimen name="click_shadow_high_shift">2dp</dimen>
- <dimen name="app_title_icon_shadow_inset">1dp</dimen>
+ <dimen name="app_title_icon_shadow_inset">0.5dp</dimen>
<!-- Pending widget -->
<dimen name="pending_widget_min_padding">8dp</dimen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 26e900d..8121e53 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -89,7 +89,7 @@
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.IconLabelDotView;
+import com.android.launcher3.views.FloatingIconViewCompanion;
import java.text.NumberFormat;
import java.util.HashMap;
@@ -101,7 +101,7 @@
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
- IconLabelDotView, DraggableView, Reorderable {
+ FloatingIconViewCompanion, DraggableView, Reorderable {
public static final String TAG = "BubbleTextView";
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 9f122c1..bafb528 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -135,14 +135,12 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
-import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
@@ -214,7 +212,6 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.popup.PopupDataProvider;
@@ -438,6 +435,10 @@
mIsColdStartupAfterReboot = sIsNewProcess
&& !LockedUserState.get(this).isUserUnlockedAtLauncherStartup();
if (mIsColdStartupAfterReboot) {
+ /*
+ * This trace is used to calculate the time from create to the point that icons are
+ * visible.
+ */
Trace.beginAsyncSection(
COLD_STARTUP_TRACE_METHOD_NAME, COLD_STARTUP_TRACE_COOKIE);
}
@@ -1410,15 +1411,6 @@
this, R.attr.isWorkspaceDarkText) ? Color.BLACK : Color.WHITE);
}
- @Override
- public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
- if (WorkspacePageIndicator.class.getName().equals(name)) {
- return LayoutInflater.from(context).inflate(R.layout.page_indicator_dots,
- (ViewGroup) parent, false);
- }
- return super.onCreateView(parent, name, context, attrs);
- }
-
/**
* Add a shortcut to the workspace or to a Folder.
*
@@ -2396,10 +2388,6 @@
.logEnd(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
.log()
.reset();
- if (mIsColdStartupAfterReboot) {
- Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME,
- COLD_STARTUP_TRACE_COOKIE);
- }
});
}
@@ -2408,6 +2396,10 @@
RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) {
mModelCallbacks.onInitialBindComplete(boundPages, pendingTasks, onCompleteSignal,
workspaceItemCount, isBindSync);
+ if (mIsColdStartupAfterReboot) {
+ Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME,
+ COLD_STARTUP_TRACE_COOKIE);
+ }
}
/**
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/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index a0b695a..de1bcc3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -83,7 +83,7 @@
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.IconLabelDotView;
+import com.android.launcher3.views.FloatingIconViewCompanion;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.util.ArrayList;
@@ -93,7 +93,7 @@
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
-public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
+public class FolderIcon extends FrameLayout implements FolderListener, FloatingIconViewCompanion,
DraggableView, Reorderable {
private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
diff --git a/src/com/android/launcher3/model/ModelLauncherCallbacks.kt b/src/com/android/launcher3/model/ModelLauncherCallbacks.kt
index b12b2bc..2ee5b80 100644
--- a/src/com/android/launcher3/model/ModelLauncherCallbacks.kt
+++ b/src/com/android/launcher3/model/ModelLauncherCallbacks.kt
@@ -38,6 +38,7 @@
LauncherApps.Callback() {
override fun onPackageAdded(packageName: String, user: UserHandle) {
+ FileLog.d(TAG, "onPackageAdded triggered for packageName=$packageName, user=$user")
taskExecutor.accept(PackageUpdatedTask(OP_ADD, user, packageName))
}
@@ -54,7 +55,7 @@
}
override fun onPackageRemoved(packageName: String, user: UserHandle) {
- FileLog.d(TAG, "package removed received $packageName")
+ FileLog.d(TAG, "onPackageRemoved triggered for packageName=$packageName, user=$user")
taskExecutor.accept(PackageUpdatedTask(OP_REMOVE, user, packageName))
}
diff --git a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java b/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
deleted file mode 100644
index bde4e52..0000000
--- a/src/com/android/launcher3/pageindicators/WorkspacePageIndicator.java
+++ /dev/null
@@ -1,265 +0,0 @@
-package com.android.launcher3.pageindicators;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.util.Property;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.Themes;
-
-/**
- * A PageIndicator that briefly shows a fraction of a line when moving between pages
- *
- * The fraction is 1 / number of pages and the position is based on the progress of the page scroll.
- */
-public class WorkspacePageIndicator extends View implements Insettable, PageIndicator {
-
- private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
- private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
- public static final int WHITE_ALPHA = (int) (0.70f * 255);
- public static final int BLACK_ALPHA = (int) (0.65f * 255);
-
- private static final int LINE_ALPHA_ANIMATOR_INDEX = 0;
- private static final int NUM_PAGES_ANIMATOR_INDEX = 1;
- private static final int TOTAL_SCROLL_ANIMATOR_INDEX = 2;
- private static final int ANIMATOR_COUNT = 3;
-
- private ValueAnimator[] mAnimators = new ValueAnimator[ANIMATOR_COUNT];
-
- private final Handler mDelayedLineFadeHandler = new Handler(Looper.getMainLooper());
- private final Launcher mLauncher;
-
- private boolean mShouldAutoHide = true;
-
- // The alpha of the line when it is showing.
- private int mActiveAlpha = 0;
- // The alpha that the line is being animated to or already at (either 0 or mActiveAlpha).
- private int mToAlpha;
- // A float value representing the number of pages, to allow for an animation when it changes.
- private float mNumPagesFloat;
- private int mCurrentScroll;
- private int mTotalScroll;
- private Paint mLinePaint;
- private final int mLineHeight;
-
- private static final Property<WorkspacePageIndicator, Integer> PAINT_ALPHA
- = new Property<WorkspacePageIndicator, Integer>(Integer.class, "paint_alpha") {
- @Override
- public Integer get(WorkspacePageIndicator obj) {
- return obj.mLinePaint.getAlpha();
- }
-
- @Override
- public void set(WorkspacePageIndicator obj, Integer alpha) {
- obj.mLinePaint.setAlpha(alpha);
- obj.invalidate();
- }
- };
-
- private static final Property<WorkspacePageIndicator, Float> NUM_PAGES
- = new Property<WorkspacePageIndicator, Float>(Float.class, "num_pages") {
- @Override
- public Float get(WorkspacePageIndicator obj) {
- return obj.mNumPagesFloat;
- }
-
- @Override
- public void set(WorkspacePageIndicator obj, Float numPages) {
- obj.mNumPagesFloat = numPages;
- obj.invalidate();
- }
- };
-
- private static final Property<WorkspacePageIndicator, Integer> TOTAL_SCROLL
- = new Property<WorkspacePageIndicator, Integer>(Integer.class, "total_scroll") {
- @Override
- public Integer get(WorkspacePageIndicator obj) {
- return obj.mTotalScroll;
- }
-
- @Override
- public void set(WorkspacePageIndicator obj, Integer totalScroll) {
- obj.mTotalScroll = totalScroll;
- obj.invalidate();
- }
- };
-
- private Runnable mHideLineRunnable = () -> animateLineToAlpha(0);
-
- public WorkspacePageIndicator(Context context) {
- this(context, null);
- }
-
- public WorkspacePageIndicator(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public WorkspacePageIndicator(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- Resources res = context.getResources();
- mLinePaint = new Paint();
- mLinePaint.setAlpha(0);
-
- mLauncher = Launcher.getLauncher(context);
- mLineHeight = res.getDimensionPixelSize(R.dimen.workspace_page_indicator_line_height);
-
- boolean darkText = Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText);
- mActiveAlpha = darkText ? BLACK_ALPHA : WHITE_ALPHA;
- mLinePaint.setColor(darkText ? Color.BLACK : Color.WHITE);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (mTotalScroll == 0 || mNumPagesFloat == 0) {
- return;
- }
-
- // Compute and draw line rect.
- float progress = Utilities.boundToRange(((float) mCurrentScroll) / mTotalScroll, 0f, 1f);
- int availableWidth = getWidth();
- int lineWidth = (int) (availableWidth / mNumPagesFloat);
- int lineLeft = (int) (progress * (availableWidth - lineWidth));
- int lineRight = lineLeft + lineWidth;
-
- canvas.drawRoundRect(lineLeft, getHeight() / 2 - mLineHeight / 2, lineRight,
- getHeight() / 2 + mLineHeight / 2, mLineHeight, mLineHeight, mLinePaint);
- }
-
- @Override
- public void setScroll(int currentScroll, int totalScroll) {
- if (getAlpha() == 0) {
- return;
- }
- animateLineToAlpha(mActiveAlpha);
-
- mCurrentScroll = currentScroll;
- if (mTotalScroll == 0) {
- mTotalScroll = totalScroll;
- } else if (mTotalScroll != totalScroll) {
- animateToTotalScroll(totalScroll);
- } else {
- invalidate();
- }
-
- if (mShouldAutoHide) {
- hideAfterDelay();
- }
- }
-
- private void hideAfterDelay() {
- mDelayedLineFadeHandler.removeCallbacksAndMessages(null);
- mDelayedLineFadeHandler.postDelayed(mHideLineRunnable, LINE_FADE_DELAY);
- }
-
- @Override
- public void setActiveMarker(int activePage) { }
-
- @Override
- public void setMarkersCount(int numMarkers) {
- if (Float.compare(numMarkers, mNumPagesFloat) != 0) {
- setupAndRunAnimation(ObjectAnimator.ofFloat(this, NUM_PAGES, numMarkers),
- NUM_PAGES_ANIMATOR_INDEX);
- } else {
- if (mAnimators[NUM_PAGES_ANIMATOR_INDEX] != null) {
- mAnimators[NUM_PAGES_ANIMATOR_INDEX].cancel();
- mAnimators[NUM_PAGES_ANIMATOR_INDEX] = null;
- }
- }
- }
-
- @Override
- public void setShouldAutoHide(boolean shouldAutoHide) {
- mShouldAutoHide = shouldAutoHide;
- if (shouldAutoHide && mLinePaint.getAlpha() > 0) {
- hideAfterDelay();
- } else if (!shouldAutoHide) {
- mDelayedLineFadeHandler.removeCallbacksAndMessages(null);
- }
- }
-
- private void animateLineToAlpha(int alpha) {
- if (alpha == mToAlpha) {
- // Ignore the new animation if it is going to the same alpha as the current animation.
- return;
- }
- mToAlpha = alpha;
- setupAndRunAnimation(ObjectAnimator.ofInt(this, PAINT_ALPHA, alpha),
- LINE_ALPHA_ANIMATOR_INDEX);
- }
-
- private void animateToTotalScroll(int totalScroll) {
- setupAndRunAnimation(ObjectAnimator.ofInt(this, TOTAL_SCROLL, totalScroll),
- TOTAL_SCROLL_ANIMATOR_INDEX);
- }
-
- /**
- * Starts the given animator and stores it in the provided index in {@link #mAnimators} until
- * the animation ends.
- *
- * If an animator is already at the index (i.e. it is already playing), it is canceled and
- * replaced with the new animator.
- */
- private void setupAndRunAnimation(ValueAnimator animator, final int animatorIndex) {
- if (mAnimators[animatorIndex] != null) {
- mAnimators[animatorIndex].cancel();
- }
- mAnimators[animatorIndex] = animator;
- mAnimators[animatorIndex].addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimators[animatorIndex] = null;
- }
- });
- mAnimators[animatorIndex].setDuration(LINE_ANIMATE_DURATION);
- mAnimators[animatorIndex].start();
- }
-
- /**
- * Pauses all currently running animations.
- */
- @Override
- public void pauseAnimations() {
- for (int i = 0; i < ANIMATOR_COUNT; i++) {
- if (mAnimators[i] != null) {
- mAnimators[i].pause();
- }
- }
- }
-
- /**
- * Force-ends all currently running or paused animations.
- */
- @Override
- public void skipAnimationsToEnd() {
- for (int i = 0; i < ANIMATOR_COUNT; i++) {
- if (mAnimators[i] != null) {
- mAnimators[i].end();
- }
- }
- }
-
- /**
- * We need to override setInsets to prevent InsettableFrameLayout from applying different
- * margins on the page indicator.
- */
- @Override
- public void setInsets(Rect insets) {
- }
-}
diff --git a/src/com/android/launcher3/views/BubbleTextHolder.java b/src/com/android/launcher3/views/BubbleTextHolder.java
index 84f8049..d2ae93b 100644
--- a/src/com/android/launcher3/views/BubbleTextHolder.java
+++ b/src/com/android/launcher3/views/BubbleTextHolder.java
@@ -20,7 +20,7 @@
/**
* Views that contain {@link BubbleTextView} should implement this interface.
*/
-public interface BubbleTextHolder extends IconLabelDotView {
+public interface BubbleTextHolder extends FloatingIconViewCompanion {
BubbleTextView getBubbleText();
@Override
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 37482ac..4ee6aff 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -22,7 +22,7 @@
import static com.android.launcher3.Utilities.getFullDrawable;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
+import static com.android.launcher3.views.FloatingIconViewCompanion.setPropertiesVisible;
import android.animation.Animator;
import android.content.Context;
@@ -516,6 +516,10 @@
// When closing an app, we want the item on the workspace to be invisible immediately
updateViewsVisibility(false /* isVisible */);
}
+ if (mFadeOutView instanceof FloatingIconViewCompanion fivc) {
+ fivc.setForceHideDot(true);
+ fivc.setForceHideRing(true);
+ }
}
@Override
@@ -653,6 +657,10 @@
if (view.mFadeOutView != null) {
view.mFadeOutView.setAlpha(1f);
}
+ if (view.mFadeOutView instanceof FloatingIconViewCompanion fivc) {
+ fivc.setForceHideDot(false);
+ fivc.setForceHideRing(false);
+ }
if (hideOriginal) {
view.updateViewsVisibility(true /* isVisible */);
@@ -674,10 +682,10 @@
private void updateViewsVisibility(boolean isVisible) {
if (mOriginalIcon != null) {
- setIconAndDotVisible(mOriginalIcon, isVisible);
+ setPropertiesVisible(mOriginalIcon, isVisible);
}
if (mMatchVisibilityView != null) {
- setIconAndDotVisible(mMatchVisibilityView, isVisible);
+ setPropertiesVisible(mMatchVisibilityView, isVisible);
}
}
diff --git a/src/com/android/launcher3/views/FloatingIconViewCompanion.java b/src/com/android/launcher3/views/FloatingIconViewCompanion.java
new file mode 100644
index 0000000..fc23903
--- /dev/null
+++ b/src/com/android/launcher3/views/FloatingIconViewCompanion.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 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.views;
+
+import android.view.View;
+
+/**
+ * A view that can be drawn (in some capacity) via) {@link FloatingIconView}.
+ * This interface allows us to hide certain properties of the view that the FloatingIconView
+ * cannot draw, which allows us to make a seamless handoff between the FloatingIconView and
+ * the companion view.
+ */
+public interface FloatingIconViewCompanion {
+ void setIconVisible(boolean visible);
+ void setForceHideDot(boolean hide);
+ default void setForceHideRing(boolean hide) {}
+
+ /**
+ * Sets the visibility of icon and dot of the view
+ */
+ static void setPropertiesVisible(View view, boolean visible) {
+ if (view instanceof FloatingIconViewCompanion) {
+ ((FloatingIconViewCompanion) view).setIconVisible(visible);
+ ((FloatingIconViewCompanion) view).setForceHideDot(!visible);
+ ((FloatingIconViewCompanion) view).setForceHideRing(!visible);
+ } else {
+ view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index cab7982..7fa7517 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -17,7 +17,7 @@
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView;
-import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
+import static com.android.launcher3.views.FloatingIconViewCompanion.setPropertiesVisible;
import android.content.Context;
import android.graphics.Canvas;
@@ -237,7 +237,7 @@
private void setCurrentIconVisible(boolean isVisible) {
if (mIcon != null) {
- setIconAndDotVisible(mIcon, isVisible);
+ setPropertiesVisible(mIcon, isVisible);
}
}
}
diff --git a/src/com/android/launcher3/views/IconLabelDotView.java b/src/com/android/launcher3/views/IconLabelDotView.java
deleted file mode 100644
index e9113cf..0000000
--- a/src/com/android/launcher3/views/IconLabelDotView.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2019 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.views;
-
-import android.view.View;
-
-/**
- * A view that has an icon, label, and notification dot.
- */
-public interface IconLabelDotView {
- void setIconVisible(boolean visible);
- void setForceHideDot(boolean hide);
-
- /**
- * Sets the visibility of icon and dot of the view
- */
- static void setIconAndDotVisible(View view, boolean visible) {
- if (view instanceof IconLabelDotView) {
- ((IconLabelDotView) view).setIconVisible(visible);
- ((IconLabelDotView) view).setForceHideDot(!visible);
- } else {
- view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
- }
-}
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 {
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index 9b184ae..9c916fa 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -15,9 +15,6 @@
*/
package com.android.launcher3.ui.widget;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
-
import static org.junit.Assert.assertNotNull;
import android.platform.test.annotations.PlatinumTest;
@@ -33,7 +30,6 @@
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import org.junit.Assume;
@@ -102,7 +98,6 @@
/**
* Test dragging a widget to the workspace and resize it.
*/
- @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/316910614
@PlatinumTest(focusArea = "launcher")
@Test
public void testResizeWidget() throws Throwable {