From 1fc6f916d333b72a12449971b2f061a4ef010178 Mon Sep 17 00:00:00 2001 From: Brandon Dayauon Date: Sun, 30 Mar 2025 00:22:38 +0000 Subject: [PATCH] Add nudgeView to Taskbar.xml Aiai will send a broadcast to Launcher and will animate the stashHandleView and nudgeView accordingly. Verified on large screen device that transient taskbar is not affected as its not in scope at the moment. bug:409121556 Test: manual - video in bug Flag: com.android.launcher3.nudge_pill Change-Id: I740e5d81dd008a230aa089f311d38bac665fc227 --- aconfig/launcher_search.aconfig | 9 + quickstep/res/drawable/bg_nudge_view.xml | 70 ++++++ quickstep/res/layout/taskbar.xml | 11 + quickstep/res/values/colors.xml | 5 + quickstep/res/values/dimens.xml | 3 + .../android/launcher3/taskbar/NudgeView.java | 207 ++++++++++++++++++ .../taskbar/NudgeViewController.java | 100 +++++++++ .../taskbar/StashedHandleViewController.java | 6 +- .../taskbar/TaskbarActivityContext.java | 4 +- .../launcher3/taskbar/TaskbarControllers.java | 6 +- .../launcher3/taskbar/TaskbarBaseTestCase.kt | 2 + res/drawable/ic_game.xml | 37 ++++ res/drawable/ic_translate.xml | 27 +++ 13 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 quickstep/res/drawable/bg_nudge_view.xml create mode 100644 quickstep/src/com/android/launcher3/taskbar/NudgeView.java create mode 100644 quickstep/src/com/android/launcher3/taskbar/NudgeViewController.java create mode 100644 res/drawable/ic_game.xml create mode 100644 res/drawable/ic_translate.xml diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig index b98eee6e32..4fa91a4c4b 100644 --- a/aconfig/launcher_search.aconfig +++ b/aconfig/launcher_search.aconfig @@ -43,6 +43,15 @@ flag { bug: "289223923" } +flag { + name: "nudge_pill" + namespace: "launcher_search" + description: "This flag enables change the nav or 3 button to nudge related edu (pill or icon) for CtS." + bug: "409121556" + metadata { + purpose: PURPOSE_BUGFIX + } +} flag { name: "private_space_add_floating_mask_view" diff --git a/quickstep/res/drawable/bg_nudge_view.xml b/quickstep/res/drawable/bg_nudge_view.xml new file mode 100644 index 0000000000..9b75803436 --- /dev/null +++ b/quickstep/res/drawable/bg_nudge_view.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml index 54f9ae87df..7fb2945413 100644 --- a/quickstep/res/layout/taskbar.xml +++ b/quickstep/res/layout/taskbar.xml @@ -83,6 +83,17 @@ android:layout_gravity="end"/> + + @color/materialColorPrimary + + #4285F4 + #7430E2 + #2A7EC8 + #2DB8BD \ No newline at end of file diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 75bf331e0e..73fbe7d091 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -363,6 +363,9 @@ 108dp 316dp 4dp + 24dp + 108dp + 200% 10dp 1dp 112dp diff --git a/quickstep/src/com/android/launcher3/taskbar/NudgeView.java b/quickstep/src/com/android/launcher3/taskbar/NudgeView.java new file mode 100644 index 0000000000..543475935e --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/NudgeView.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2025 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 static com.android.launcher3.anim.AnimatedFloat.VALUE; +import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; +import static com.android.launcher3.taskbar.StashedHandleViewController.ALPHA_INDEX_NUDGED; +import static com.android.launcher3.taskbar.StashedHandleViewController.ALPHA_INDEX_STASHED; + +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.Nullable; + +import com.android.launcher3.Flags; +import com.android.launcher3.R; +import com.android.launcher3.anim.AnimatedFloat; +import com.android.launcher3.anim.SpringAnimationBuilder; +import com.android.launcher3.util.MultiValueAlpha; + +public class NudgeView extends ImageView { + + private static final float DAMPING_RATIO = 0.6f; + private static final float STIFFNESS = 380f; + + private AnimatorSet mAnimatorSet; + private boolean mNudgeShown; + private int mNudgePillWidth; + private int mNudgePillHeight; + private int mStashedHandleWidth; + private int mStashedHandleHeight; + private ImageView mNudgeIcon; + private MultiValueAlpha mStashedHandleViewAlpha; + + private final AnimatedFloat mAlphaForHandleView = new AnimatedFloat( + this::updateAlphaForHandleView); + private final AnimatedFloat mAlphaForNudgeIcon = new AnimatedFloat( + this::updateAlphaForNudgeIcon); + private final AnimatedFloat mWidthForNudgePill = new AnimatedFloat( + this::updateWidthForNudge); + private final AnimatedFloat mHeightForNudgePill = new AnimatedFloat( + this::updateHeightForNudge); + + public NudgeView(Context context) { + this(context, null); + } + + public NudgeView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NudgeView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public NudgeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + if (Flags.nudgePill()) { + mNudgePillWidth = context.getResources().getDimensionPixelSize( + R.dimen.taskbar_nudge_pill_width); + mNudgePillHeight = context.getResources().getDimensionPixelSize( + R.dimen.taskbar_nudge_pill_height); + mStashedHandleWidth = context.getResources().getDimensionPixelSize( + R.dimen.taskbar_nudge_pill_width); + mStashedHandleHeight = context.getResources().getDimensionPixelSize( + R.dimen.taskbar_stashed_handle_height); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mNudgeIcon = findViewById(R.id.nudge_icon); + } + + void updateNudgeIcon(boolean showNudge, @Nullable Drawable icon, + @Nullable MultiValueAlpha stashedHandleViewAlpha) { + if (!Flags.nudgePill() || stashedHandleViewAlpha == null) { + return; + } + mStashedHandleViewAlpha = stashedHandleViewAlpha; + // Set update visibility to false when showing the nudge otherwise + // StashHandleView will be INVISIBLE at low alpha resulting in being unable to invoke CtS. + mStashedHandleViewAlpha.setUpdateVisibility(false); + mNudgeIcon.setImageDrawable(null); + if (icon != null) { + mNudgeIcon.setImageDrawable(icon); + } + mNudgeIcon.setVisibility(VISIBLE); + if (showNudge && !mNudgeShown) { + post(() -> animationBarToPill(true /* showNudge */)); + } else if (!showNudge){ + post(() -> animationBarToPill(false /* showNudge */)); + } + } + + private void animationBarToPill(boolean showNudge) { + if (!Flags.nudgePill()) { + return; + } + if (mAnimatorSet != null && mAnimatorSet.isRunning()) { + mAnimatorSet.cancel(); + } + mNudgeShown = showNudge; + // Alpha + float targetIconAlpha = showNudge ? 1.0f : 0; + float targetHandleViewAlpha = showNudge ? 0 : 1.0f; + // Animate from navBar -> nudgePill if showNudge is true. + float startWidth = showNudge ? mStashedHandleWidth : mNudgePillWidth; + float startHeight = showNudge ? mStashedHandleHeight : mNudgePillHeight; + float endWidth = showNudge ? mNudgePillWidth : mStashedHandleWidth; + float endHeight = showNudge ? mNudgePillHeight : mStashedHandleHeight; + + ValueAnimator alphaNudgeIcon = new SpringAnimationBuilder(mContext) + .setStartValue(mNudgeIcon.getAlpha()) + .setEndValue(targetIconAlpha) + .setDampingRatio(DAMPING_RATIO) + .setStiffness(STIFFNESS) + .build(mAlphaForNudgeIcon, VALUE); + if (!showNudge) { + alphaNudgeIcon.addListener(forEndCallback(() -> { + mNudgeIcon.setVisibility(GONE); + mStashedHandleViewAlpha.setUpdateVisibility(true); + })); + } + ValueAnimator alphaHandleView = new SpringAnimationBuilder(mContext) + .setStartValue(mStashedHandleViewAlpha.get(ALPHA_INDEX_STASHED).getValue()) + .setEndValue(targetHandleViewAlpha) + .setDampingRatio(DAMPING_RATIO) + .setStiffness(STIFFNESS) + .build(mAlphaForHandleView, VALUE); + ValueAnimator widthAnimation = new SpringAnimationBuilder(mContext) + .setStartValue(startWidth) + .setEndValue(endWidth) + .setDampingRatio(DAMPING_RATIO) + .setStiffness(STIFFNESS) + .build(mWidthForNudgePill, VALUE); + ValueAnimator heightAnimation = new SpringAnimationBuilder(mContext) + .setStartValue(startHeight) + .setEndValue(endHeight) + .setDampingRatio(DAMPING_RATIO) + .setStiffness(STIFFNESS) + .build(mHeightForNudgePill, VALUE); + mAnimatorSet = new AnimatorSet(); + mAnimatorSet.playTogether( + alphaNudgeIcon, alphaHandleView, + widthAnimation, heightAnimation + ); + mAnimatorSet.start(); + } + + private void updateAlphaForHandleView() { + if (!Flags.nudgePill()) { + return; + } + float alpha = mAlphaForHandleView.value; + mStashedHandleViewAlpha.get(ALPHA_INDEX_NUDGED).setValue(alpha); + } + + private void updateAlphaForNudgeIcon() { + if (!Flags.nudgePill()) { + return; + } + float alpha = mAlphaForNudgeIcon.value; + mNudgeIcon.setAlpha(alpha); + } + + private void updateWidthForNudge() { + if (!Flags.nudgePill()) { + return; + } + float width = mWidthForNudgePill.value; + ViewGroup.MarginLayoutParams nudgePillLayoutParams = + (ViewGroup.MarginLayoutParams) mNudgeIcon.getLayoutParams(); + nudgePillLayoutParams.width = (int) width; + mNudgeIcon.setLayoutParams(nudgePillLayoutParams); + } + + private void updateHeightForNudge() { + if (!Flags.nudgePill()) { + return; + } + float height = mHeightForNudgePill.value; + ViewGroup.MarginLayoutParams nudgePillLayoutParams = + (ViewGroup.MarginLayoutParams) mNudgeIcon.getLayoutParams(); + nudgePillLayoutParams.height = (int) height; + mNudgeIcon.setLayoutParams(nudgePillLayoutParams); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/NudgeViewController.java b/quickstep/src/com/android/launcher3/taskbar/NudgeViewController.java new file mode 100644 index 0000000000..89d4ec4476 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/NudgeViewController.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2025 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 static android.content.Context.RECEIVER_EXPORTED; + +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; + +import androidx.annotation.Nullable; + +import com.android.launcher3.Flags; +import com.android.launcher3.R; +import com.android.launcher3.util.SimpleBroadcastReceiver; + +import java.io.PrintWriter; + +/** + * NudgeViewController handles the broadcasts sent to launcher which will then update the view. + */ +public class NudgeViewController implements TaskbarControllers.LoggableTaskbarController{ + + private static final String NAV_UPDATE_ACTION = "android.app.action.UPDATE_NAVBAR"; + private static final String GAME = "game"; + private static final String TRANSLATE = "translate"; + private static final String SHOW_NUDGE = "showNudge"; + private static final String NUDGE_ICON = "nudgeIcon"; + private final TaskbarActivityContext mActivity; + @Nullable + private final NudgeView mNudgeView; + private final Drawable mTranslateIcon; + private final Drawable mGameIcon; + + @Nullable + private SimpleBroadcastReceiver mNudgeReceiver; + + public NudgeViewController(TaskbarActivityContext activity, + @Nullable NudgeView nudgeView) { + mActivity = activity; + mNudgeView = nudgeView; + final Resources resources = mActivity.getResources(); + if (Flags.nudgePill() && mNudgeView != null) { + mNudgeReceiver = new SimpleBroadcastReceiver( + mActivity, UI_HELPER_EXECUTOR, this::shouldChangeNavBar); + mNudgeReceiver.register(RECEIVER_EXPORTED, NAV_UPDATE_ACTION); + } + mTranslateIcon = resources.getDrawable(R.drawable.ic_translate); + mGameIcon = resources.getDrawable(R.drawable.ic_game); + } + + private void shouldChangeNavBar(Intent i) { + if (!mActivity.isPhoneGestureNavMode() || !Flags.nudgePill()) { + return; + } + Bundle bundle = i.getExtras(); + boolean showNudge = bundle.getBoolean(SHOW_NUDGE, false); + String iconToUse = bundle.getString(NUDGE_ICON, ""); + Drawable icon = null; + if (GAME.equals(iconToUse)) { + icon = mGameIcon; + } else if (TRANSLATE.equals(iconToUse)) { + icon = mTranslateIcon; + } + // Nudge icon will only show if there is a valid icon to show. + mNudgeView.updateNudgeIcon(icon != null && showNudge /* showNudge */, icon, + mActivity.getControllers().stashedHandleViewController.getStashedHandleAlpha()); + } + + public void onDestroy() { + if (mNudgeReceiver != null) { + mNudgeReceiver.unregisterReceiverSafely(); + } + mNudgeReceiver = null; + } + + @Override + public void dumpLogs(String prefix, PrintWriter pw) { + pw.println(prefix + "NudgeViewController:"); + pw.println(prefix + "\tmNudgeView=" + mNudgeView); + pw.println(prefix + "\tmNudgeReceiver=" + mNudgeReceiver); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java index dd0ce27c91..0fa5fd5370 100644 --- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java @@ -39,7 +39,6 @@ import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.anim.RevealOutlineAnimation; import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.util.Executors; -import com.android.launcher3.util.MultiPropertyFactory; import com.android.launcher3.util.MultiValueAlpha; import com.android.quickstep.NavHandle; import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; @@ -57,7 +56,8 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT public static final int ALPHA_INDEX_HOME_DISABLED = 1; public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 2; public static final int ALPHA_INDEX_HIDDEN_WHILE_DREAMING = 3; - private static final int NUM_ALPHA_CHANNELS = 4; + public static final int ALPHA_INDEX_NUDGED = 4; + private static final int NUM_ALPHA_CHANNELS = 5; // Values for long press animations, picked to most closely match navbar spec. private static final float SCALE_TOUCH_ANIMATION_SHRINK = 0.85f; @@ -203,7 +203,7 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT mRegionSamplingHelper = null; } - public MultiPropertyFactory getStashedHandleAlpha() { + public MultiValueAlpha getStashedHandleAlpha() { return mTaskbarStashedHandleAlpha; } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index ba1ce18893..0044a809d0 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -307,6 +307,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext { TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim); NearestTouchFrame navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view); StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle); + NudgeView nudgeView = mDragLayer.findViewById(R.id.nudge_icon); BubbleBarView bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles); FrameLayout bubbleBarContainer = mDragLayer.findViewById(R.id.taskbar_bubbles_container); StashedHandleView bubbleHandleView = mDragLayer.findViewById(R.id.stashed_bubble_handle); @@ -391,7 +392,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { bubbleControllersOptional, new TaskbarDesktopModeController(this, DesktopVisibilityController.INSTANCE.get(this)), - new NudgeController(this)); + new NudgeController(this), + new NudgeViewController(this, nudgeView)); mLauncherPrefs = LauncherPrefs.get(this); onViewCreated(); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index bd6bf68f0d..503cb7e663 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -69,6 +69,7 @@ public class TaskbarControllers { public final Optional bubbleControllers; public final TaskbarDesktopModeController taskbarDesktopModeController; public final NudgeController nudgeController; + public final NudgeViewController nudgeViewController; @Nullable private LoggableTaskbarController[] mControllersToLog = null; @Nullable private BackgroundRendererController[] mBackgroundRendererControllers = null; @@ -118,7 +119,8 @@ public class TaskbarControllers { TaskbarPinningController taskbarPinningController, Optional bubbleControllers, TaskbarDesktopModeController taskbarDesktopModeController, - NudgeController nudgeController) { + NudgeController nudgeController, + NudgeViewController nudgeViewController) { this.taskbarActivityContext = taskbarActivityContext; this.taskbarDragController = taskbarDragController; this.navButtonController = navButtonController; @@ -147,6 +149,7 @@ public class TaskbarControllers { this.bubbleControllers = bubbleControllers; this.taskbarDesktopModeController = taskbarDesktopModeController; this.nudgeController = nudgeController; + this.nudgeViewController = nudgeViewController; } /** @@ -273,6 +276,7 @@ public class TaskbarControllers { taskbarUnfoldAnimationController.onDestroy(); taskbarViewController.onDestroy(); stashedHandleViewController.onDestroy(); + nudgeViewController.onDestroy(); taskbarAutohideSuspendController.onDestroy(); taskbarPopupController.onDestroy(); taskbarForceVisibleImmersiveController.onDestroy(); diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt index 52d288a723..bfd5d76923 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt @@ -60,6 +60,7 @@ abstract class TaskbarBaseTestCase { @Mock lateinit var optionalBubbleControllers: Optional @Mock lateinit var taskbarDesktopModeController: TaskbarDesktopModeController @Mock lateinit var nudgeController: NudgeController + @Mock lateinit var nudgeViewController: NudgeViewController lateinit var taskbarControllers: TaskbarControllers @@ -103,6 +104,7 @@ abstract class TaskbarBaseTestCase { optionalBubbleControllers, taskbarDesktopModeController, nudgeController, + nudgeViewController ) } } diff --git a/res/drawable/ic_game.xml b/res/drawable/ic_game.xml new file mode 100644 index 0000000000..fcfa8f78de --- /dev/null +++ b/res/drawable/ic_game.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + diff --git a/res/drawable/ic_translate.xml b/res/drawable/ic_translate.xml new file mode 100644 index 0000000000..add47bee8d --- /dev/null +++ b/res/drawable/ic_translate.xml @@ -0,0 +1,27 @@ + + + + + + +