Merge "Honors three_button_corner_swipe flag." into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 5b00b5d..40c3797 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -333,3 +333,30 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "letter_fast_scroller"
+ namespace: "launcher"
+ 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/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index d973149..e940553 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
+import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.RemoteAnimationTarget;
@@ -196,6 +197,7 @@
if (skipFirstFrame) {
// Because t=0 has the app icon in its original spot, we can skip the
// first frame and have the same movement one frame earlier.
+ Log.d("b/311077782", "LauncherAnimationRunner.setAnimation");
mAnimator.setCurrentPlayTime(
Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration()));
}
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index 189deda..f7da34a 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -30,7 +30,7 @@
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.views.DesktopTaskView
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import java.util.function.Consumer
/** Manage recents related operations with desktop tasks */
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index b63b9dd..96a6d28 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -248,7 +248,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/StashedHandleView.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
index 94e2244..caf3320 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleView.java
@@ -47,6 +47,7 @@
private final int[] mTmpArr = new int[2];
private @Nullable ObjectAnimator mColorChangeAnim;
+ private Boolean mIsRegionDark;
public StashedHandleView(Context context) {
this(context, null);
@@ -95,7 +96,11 @@
* @param animate Whether to animate the change, or apply it immediately.
*/
public void updateHandleColor(boolean isRegionDark, boolean animate) {
+ if (mIsRegionDark != null && mIsRegionDark == isRegionDark) {
+ return;
+ }
int newColor = isRegionDark ? mStashedHandleLightColor : mStashedHandleDarkColor;
+ mIsRegionDark = isRegionDark;
if (mColorChangeAnim != null) {
mColorChangeAnim.cancel();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 1471234..487bc54 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -186,6 +186,7 @@
private final WindowManager mWindowManager;
private DeviceProfile mDeviceProfile;
private WindowManager.LayoutParams mWindowLayoutParams;
+ private WindowManager.LayoutParams mLastUpdatedLayoutParams;
private boolean mIsFullscreen;
// The size we should return to when we call setTaskbarWindowFullscreen(false)
private int mLastRequestedNonFullscreenSize;
@@ -442,6 +443,7 @@
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false);
mLastRequestedNonFullscreenSize = getDefaultTaskbarWindowSize();
mWindowLayoutParams = createAllWindowParams();
+ mLastUpdatedLayoutParams = new WindowManager.LayoutParams();
// Initialize controllers after all are constructed.
mControllers.init(sharedState);
@@ -1727,6 +1729,12 @@
void notifyUpdateLayoutParams() {
if (mDragLayer.isAttachedToWindow()) {
+ // Copy the current windowLayoutParams to mLastUpdatedLayoutParams and compare the diff.
+ // If there is no change, we will skip the call to updateViewLayout.
+ int changes = mLastUpdatedLayoutParams.copyFrom(mWindowLayoutParams);
+ if (changes == 0) {
+ return;
+ }
if (enableTaskbarNoRecreate()) {
mWindowManager.updateViewLayout(mDragLayer.getRootView(), mWindowLayoutParams);
} else {
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/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index ff1ea98..221504d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -56,7 +56,6 @@
import com.android.launcher3.util.Executors
import java.io.PrintWriter
import kotlin.jvm.optionals.getOrNull
-import kotlin.math.max
/** Handles the insets that Taskbar provides to underlying apps and the IME. */
class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTaskbarController {
@@ -106,7 +105,8 @@
}
fun onTaskbarOrBubblebarWindowHeightOrInsetsChanged() {
- val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
+ val taskbarStashController = controllers.taskbarStashController
+ val tappableHeight = taskbarStashController.tappableHeightToReportToApps
// We only report tappableElement height for unstashed, persistent taskbar,
// which is also when we draw the rounded corners above taskbar.
val insetsRoundedCornerFlag =
@@ -133,7 +133,7 @@
}
val bubbleControllers = controllers.bubbleControllers.getOrNull()
- val taskbarTouchableHeight = controllers.taskbarStashController.touchableHeight
+ val taskbarTouchableHeight = taskbarStashController.touchableHeight
val bubblesTouchableHeight =
bubbleControllers?.bubbleStashController?.getTouchableHeight() ?: 0
// reset touch bounds
@@ -147,12 +147,10 @@
defaultTouchableRegion.addBoundsToRegion(bubbleBarViewController.bubbleBarBounds)
}
}
- val taskbarUIController = controllers.uiController as? LauncherTaskbarUIController
- if (taskbarUIController?.isOnHome != true) {
- // only add the bars touch region if not on home
- val touchableHeight = max(taskbarTouchableHeight, bubblesTouchableHeight)
+ if (taskbarStashController.isInApp || taskbarStashController.isInOverview) {
+ // only add the taskbar touch region if not on home
val bottom = windowLayoutParams.height
- val top = bottom - touchableHeight
+ val top = bottom - taskbarTouchableHeight
val right = context.deviceProfile.widthPx
defaultTouchableRegion.addBoundsToRegion(Rect(/* left= */ 0, top, right, bottom))
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index a5395d9..0eb8890 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK;
import static com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_AWAKE;
@@ -245,7 +246,9 @@
resetIconAlignment();
- mLauncher.getStateManager().addStateListener(mStateListener);
+ if (!mControllers.taskbarActivityContext.isPhoneMode()) {
+ mLauncher.getStateManager().addStateListener(mStateListener);
+ }
mLauncherState = launcher.getStateManager().getState();
updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false);
@@ -351,8 +354,10 @@
// interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in
// when the device is asleep, the second condition extends ensures that the transition from
// and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar
- // hide/reveal animation timings.
- boolean isTaskbarHidden = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING)
+ // hide/reveal animation timings. The Taskbar can show when dreaming if the glanceable hub
+ // is showing on top.
+ boolean isTaskbarHidden = (hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING)
+ && !hasAnyFlag(systemUiStateFlags, SYSUI_STATE_COMMUNAL_HUB_SHOWING))
|| (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE;
updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden);
@@ -409,7 +414,7 @@
}
public Animator applyState(long duration, boolean start) {
- if (mIsDestroyed) {
+ if (mIsDestroyed || mControllers.taskbarActivityContext.isPhoneMode()) {
return null;
}
Animator animator = null;
@@ -858,7 +863,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/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index b48ed60..56f88d1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -424,6 +424,11 @@
return hasAnyFlag(FLAGS_IN_APP);
}
+ /** Returns whether the taskbar is currently in overview screen. */
+ public boolean isInOverview() {
+ return hasAnyFlag(FLAG_IN_OVERVIEW);
+ }
+
/**
* Returns the height that taskbar will be touchable.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index d3b918e..27c1d9c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -150,6 +150,7 @@
private Runnable mOnControllerPreCreateCallback = NO_OP;
// Stored here as signals to determine if the mIconAlignController needs to be recreated.
+ private boolean mIsIconAlignedWithHotseat;
private boolean mIsHotseatIconOnTopWhenAligned;
private boolean mIsStashed;
@@ -218,8 +219,8 @@
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
// This gets modified in NavbarButtonsViewController, but the initial value it reads
// may be incorrect since it's state gets destroyed on taskbar recreate, so reset here
- mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
- .animateToValue(mActivity.isPhoneButtonNavMode() ? 0 : 1).start();
+ mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN).setValue(
+ mActivity.isPhoneMode() ? 0 : 1);
}
if (enableTaskbarPinning()) {
mTaskbarView.addOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
@@ -687,15 +688,17 @@
mIconAlignControllerLazy = null;
return;
}
-
boolean isHotseatIconOnTopWhenAligned =
mControllers.uiController.isHotseatIconOnTopWhenAligned();
+ boolean isIconAlignedWithHotseat = mControllers.uiController.isIconAlignedWithHotseat();
boolean isStashed = mControllers.taskbarStashController.isStashed();
- // Re-create animation when mIsHotseatIconOnTopWhenAligned or mIsStashed changes.
+ // Re-create animation when any of these values change.
if (mIconAlignControllerLazy == null
|| mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned
+ || mIsIconAlignedWithHotseat != isIconAlignedWithHotseat
|| mIsStashed != isStashed) {
mIsHotseatIconOnTopWhenAligned = isHotseatIconOnTopWhenAligned;
+ mIsIconAlignedWithHotseat = isIconAlignedWithHotseat;
mIsStashed = isStashed;
mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index a36d5f0..cdd3e13 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -496,6 +496,11 @@
() -> mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation));
}
+ /** Notifies WMShell to show the expanded view. */
+ void showExpandedView() {
+ mSystemUiProxy.showExpandedView();
+ }
+
//
// Loading data for the bubbles
//
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 1f0851f..c458936 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -244,7 +244,7 @@
if (mIsBarExpanded && mSelectedBubbleView != null) {
mSelectedBubbleView.markSeen();
}
- updateWidth();
+ updateLayoutParams();
},
/* onUpdate= */ animator -> {
updateBubblesLayoutProperties(mBubbleBarLocation);
@@ -733,7 +733,7 @@
@Override
public void onAnimationEnd() {
- updateWidth();
+ updateLayoutParams();
mBubbleAnimator = null;
}
@@ -791,7 +791,7 @@
@Override
public void onAnimationEnd() {
removeView(removedBubble);
- updateWidth();
+ updateLayoutParams();
mBubbleAnimator = null;
if (onEndRunnable != null) {
onEndRunnable.run();
@@ -823,7 +823,7 @@
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
- updateWidth();
+ updateLayoutParams();
updateBubbleAccessibilityStates();
updateContentDescription();
}
@@ -887,7 +887,7 @@
mSelectedBubbleView = null;
mBubbleBarBackground.showArrow(false);
}
- updateWidth();
+ updateLayoutParams();
updateBubbleAccessibilityStates();
updateContentDescription();
mDismissedByDragBubbleView = null;
@@ -937,12 +937,6 @@
}
}
- private void updateWidth() {
- LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
- lp.width = (int) (mIsBarExpanded ? expandedWidth() : collapsedWidth());
- setLayoutParams(lp);
- }
-
private void updateLayoutParams() {
LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
lp.height = (int) getBubbleBarExpandedHeight();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 1d74b28..5c1a546 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -119,7 +119,8 @@
mBubbleDragController = bubbleControllers.bubbleDragController;
mTaskbarStashController = controllers.taskbarStashController;
mTaskbarInsetsController = controllers.taskbarInsetsController;
- mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
+ mBubbleBarViewAnimator = new BubbleBarViewAnimator(
+ mBarView, mBubbleStashController, mBubbleBarController::showExpandedView);
mTaskbarViewPropertiesProvider = taskbarViewPropertiesProvider;
onBubbleBarConfigurationChanged(/* animate= */ false);
mActivity.addOnDeviceProfileChangeListener(
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index 2ed88d8..99c50f2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -36,6 +36,7 @@
constructor(
private val bubbleBarView: BubbleBarView,
private val bubbleStashController: BubbleStashController,
+ private val onExpanded: Runnable,
private val scheduler: Scheduler = HandlerScheduler(bubbleBarView)
) {
@@ -406,7 +407,7 @@
springBackAnimation.spring(DynamicAnimation.TRANSLATION_Y, ty)
springBackAnimation.addEndListener { _, _, _, _, _, _, _ ->
if (animatingBubble?.expand == true) {
- bubbleBarView.isExpanded = true
+ expandBubbleBar()
cancelHideAnimation()
} else {
moveToState(AnimatingBubble.State.IN)
@@ -417,7 +418,7 @@
ObjectAnimator.ofFloat(bubbleBarView, View.TRANSLATION_Y, ty - bubbleBarBounceDistanceInPx)
.withDuration(BUBBLE_BAR_BOUNCE_ANIMATION_DURATION_MS)
.withEndAction {
- if (animatingBubble?.expand == true) bubbleBarView.isExpanded = true
+ if (animatingBubble?.expand == true) expandBubbleBar()
springBackAnimation.start()
}
.start()
@@ -451,7 +452,7 @@
this.animatingBubble = animatingBubble.copy(expand = true)
// if we're fully in and waiting to hide, cancel the hide animation and clean up
if (animatingBubble.state == AnimatingBubble.State.IN) {
- bubbleBarView.isExpanded = true
+ expandBubbleBar()
cancelHideAnimation()
}
}
@@ -489,6 +490,11 @@
this.animatingBubble = animatingBubble.copy(state = state)
}
+ private fun expandBubbleBar() {
+ bubbleBarView.isExpanded = true
+ onExpanded.run()
+ }
+
/**
* Tracks the translation Y of the bubble bar during the animation. When the bubble bar expands
* as part of the animation, the expansion should start after the bubble bar reaches the peak
@@ -510,7 +516,7 @@
}
val expand = animatingBubble?.expand ?: false
if (reachedPeak && expand && !startedExpanding) {
- bubbleBarView.isExpanded = true
+ expandBubbleBar()
startedExpanding = true
}
previousTy = ty
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 5392b70..3d94442 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/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
index fbc0d14..94f4920 100644
--- a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -24,8 +24,8 @@
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskContainer
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
/** A menu item, "Desktop", that allows the user to bring the current app into Desktop Windowing. */
class DesktopSystemShortcut(
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
deleted file mode 100644
index 8f533a3..0000000
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ /dev/null
@@ -1,529 +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;
-
-import static com.android.launcher3.PagedView.INVALID_PAGE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.content.Intent;
-import android.graphics.PointF;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
-import com.android.internal.jank.Cuj;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.taskbar.TaskbarUIController;
-import com.android.launcher3.util.RunnableList;
-import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
-import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.RecentsViewContainer;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Helper class to handle various atomic commands for switching between Overview.
- */
-public class OverviewCommandHelper {
- private static final String TAG = "OverviewCommandHelper";
-
- public static final int TYPE_SHOW = 1;
- public static final int TYPE_KEYBOARD_INPUT = 2;
- public static final int TYPE_HIDE = 3;
- public static final int TYPE_TOGGLE = 4;
- public static final int TYPE_HOME = 5;
-
- /**
- * Use case for needing a queue is double tapping recents button in 3 button nav.
- * Size of 2 should be enough. We'll toss in one more because we're kind hearted.
- */
- private final static int MAX_QUEUE_SIZE = 3;
-
- private static final String TRANSITION_NAME = "Transition:toOverview";
-
- private final TouchInteractionService mService;
- private final OverviewComponentObserver mOverviewComponentObserver;
- private final TaskAnimationManager mTaskAnimationManager;
- private final ArrayList<CommandInfo> mPendingCommands = new ArrayList<>();
-
- /**
- * Index of the TaskView that should be focused when launching Overview. Persisted so that we
- * do not lose the focus across multiple calls of
- * {@link OverviewCommandHelper#executeCommand(CommandInfo)} for the same command
- */
- private int mKeyboardTaskFocusIndex = -1;
-
- /**
- * Whether we should incoming toggle commands while a previous toggle command is still ongoing.
- * This serves as a rate-limiter to prevent overlapping animations that can clobber each other
- * and prevent clean-up callbacks from running. This thus prevents a recurring set of bugs with
- * janky recents animations and unresponsive home and overview buttons.
- */
- private boolean mWaitForToggleCommandComplete = false;
-
- public OverviewCommandHelper(TouchInteractionService service,
- OverviewComponentObserver observer,
- TaskAnimationManager taskAnimationManager) {
- mService = service;
- mOverviewComponentObserver = observer;
- mTaskAnimationManager = taskAnimationManager;
- }
-
- /**
- * Called when the command finishes execution.
- */
- private void scheduleNextTask(CommandInfo command) {
- if (mPendingCommands.isEmpty()) {
- Log.d(TAG, "no pending commands to schedule");
- return;
- }
- if (mPendingCommands.get(0) != command) {
- Log.d(TAG, "next task not scheduled."
- + " mPendingCommands[0] type is " + mPendingCommands.get(0)
- + " - command type is: " + command);
- return;
- }
- Log.d(TAG, "scheduleNextTask called: " + command);
- mPendingCommands.remove(0);
- executeNext();
- }
-
- /**
- * Executes the next command from the queue. If the command finishes immediately (returns true),
- * it continues to execute the next command, until the queue is empty of a command defer's its
- * completion (returns false).
- */
- @UiThread
- private void executeNext() {
- if (mPendingCommands.isEmpty()) {
- Log.d(TAG, "executeNext - mPendingCommands is empty");
- return;
- }
- CommandInfo cmd = mPendingCommands.get(0);
-
- boolean result = executeCommand(cmd);
- Log.d(TAG, "executeNext cmd type: " + cmd + ", result: " + result);
- if (result) {
- scheduleNextTask(cmd);
- }
- }
-
- @UiThread
- private void addCommand(CommandInfo cmd) {
- boolean wasEmpty = mPendingCommands.isEmpty();
- mPendingCommands.add(cmd);
- if (wasEmpty) {
- executeNext();
- }
- }
-
- /**
- * Adds a command to be executed next, after all pending tasks are completed.
- * Max commands that can be queued is {@link #MAX_QUEUE_SIZE}.
- * Requests after reaching that limit will be silently dropped.
- */
- @BinderThread
- public void addCommand(int type) {
- if (mPendingCommands.size() >= MAX_QUEUE_SIZE) {
- Log.d(TAG, "the pending command queue is full (" + mPendingCommands.size() + "). "
- + "command not added: " + type);
- return;
- }
- Log.d(TAG, "adding command type: " + type);
- CommandInfo cmd = new CommandInfo(type);
- MAIN_EXECUTOR.execute(() -> addCommand(cmd));
- }
-
- @UiThread
- public void clearPendingCommands() {
- Log.d(TAG, "clearing pending commands - size: " + mPendingCommands.size());
- mPendingCommands.clear();
- }
-
- @UiThread
- public boolean canStartHomeSafely() {
- return mPendingCommands.isEmpty() || mPendingCommands.get(0).type == TYPE_HOME;
- }
-
- @Nullable
- private TaskView getNextTask(RecentsView view) {
- final TaskView runningTaskView = view.getRunningTaskView();
-
- if (runningTaskView == null) {
- return view.getTaskViewAt(0);
- } else {
- final TaskView nextTask = view.getNextTaskView();
- return nextTask != null ? nextTask : runningTaskView;
- }
- }
-
- private boolean launchTask(RecentsView recents, @Nullable TaskView taskView, CommandInfo cmd) {
- RunnableList callbackList = null;
- if (taskView != null) {
- mWaitForToggleCommandComplete = true;
- taskView.setEndQuickSwitchCuj(true);
- callbackList = taskView.launchTasks();
- }
-
- if (callbackList != null) {
- callbackList.add(() -> {
- Log.d(TAG, "launching task callback: " + cmd);
- scheduleNextTask(cmd);
- mWaitForToggleCommandComplete = false;
- });
- Log.d(TAG, "launching task - waiting for callback: " + cmd);
- return false;
- } else {
- recents.startHome();
- mWaitForToggleCommandComplete = false;
- return true;
- }
- }
-
- /**
- * Executes the task and returns true if next task can be executed. If false, then the next
- * task is deferred until {@link #scheduleNextTask} is called
- */
- private <T extends StatefulActivity<?> & RecentsViewContainer> boolean executeCommand(
- CommandInfo cmd) {
- if (mWaitForToggleCommandComplete && cmd.type == TYPE_TOGGLE) {
- Log.d(TAG, "executeCommand: " + cmd
- + " - waiting for toggle command complete");
- return true;
- }
- BaseActivityInterface<?, T> activityInterface =
- mOverviewComponentObserver.getActivityInterface();
-
- RecentsView<?, ?> visibleRecentsView = activityInterface.getVisibleRecentsView();
- RecentsView<?, ?> createdRecentsView;
-
- Log.d(TAG, "executeCommand: " + cmd
- + " - visibleRecentsView: " + visibleRecentsView);
- if (visibleRecentsView == null) {
- T activity = activityInterface.getCreatedContainer();
- createdRecentsView = activity == null ? null : activity.getOverviewPanel();
- DeviceProfile dp = activity == null ? null : activity.getDeviceProfile();
- TaskbarUIController uiController = activityInterface.getTaskbarController();
- boolean allowQuickSwitch = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
- && uiController != null
- && dp != null
- && (dp.isTablet || dp.isTwoPanels);
-
- switch (cmd.type) {
- case TYPE_HIDE:
- if (!allowQuickSwitch) {
- return true;
- }
- mKeyboardTaskFocusIndex = uiController.launchFocusedTask();
- if (mKeyboardTaskFocusIndex == -1) {
- return true;
- }
- break;
- case TYPE_KEYBOARD_INPUT:
- if (allowQuickSwitch) {
- uiController.openQuickSwitchView();
- return true;
- } else {
- mKeyboardTaskFocusIndex = 0;
- break;
- }
- case TYPE_HOME:
- ActiveGestureLog.INSTANCE.addLog(
- "OverviewCommandHelper.executeCommand(TYPE_HOME)");
- // Although IActivityTaskManager$Stub$Proxy.startActivity is a slow binder call,
- // we should still call it on main thread because launcher is waiting for
- // ActivityTaskManager to resume it. Also calling startActivity() on bg thread
- // could potentially delay resuming launcher. See b/348668521 for more details.
- mService.startActivity(mOverviewComponentObserver.getHomeIntent());
- return true;
- case TYPE_SHOW:
- // When Recents is not currently visible, the command's type is TYPE_SHOW
- // when overview is triggered via the keyboard overview button or Action+Tab
- // keys (Not Alt+Tab which is KQS). The overview button on-screen in 3-button
- // nav is TYPE_TOGGLE.
- mKeyboardTaskFocusIndex = 0;
- break;
- default:
- // continue below to handle displaying Recents.
- }
- } else {
- createdRecentsView = visibleRecentsView;
- switch (cmd.type) {
- case TYPE_SHOW:
- // already visible
- return true;
- case TYPE_KEYBOARD_INPUT: {
- if (visibleRecentsView.isHandlingTouch()) {
- return true;
- }
- }
- case TYPE_HIDE: {
- if (visibleRecentsView.isHandlingTouch()) {
- return true;
- }
- mKeyboardTaskFocusIndex = INVALID_PAGE;
- int currentPage = visibleRecentsView.getNextPage();
- TaskView tv = (currentPage >= 0
- && currentPage < visibleRecentsView.getTaskViewCount())
- ? (TaskView) visibleRecentsView.getPageAt(currentPage)
- : null;
- return launchTask(visibleRecentsView, tv, cmd);
- }
- case TYPE_TOGGLE:
- return launchTask(visibleRecentsView, getNextTask(visibleRecentsView), cmd);
- case TYPE_HOME:
- visibleRecentsView.startHome();
- return true;
- }
- }
-
- if (createdRecentsView != null) {
- createdRecentsView.setKeyboardTaskFocusIndex(mKeyboardTaskFocusIndex);
- }
- // Handle recents view focus when launching from home
- Animator.AnimatorListener animatorListener = new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationStart(Animator animation) {
- super.onAnimationStart(animation);
- updateRecentsViewFocus(cmd);
- logShowOverviewFrom(cmd.type);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- Log.d(TAG, "switching to Overview state - onAnimationEnd: " + cmd);
- super.onAnimationEnd(animation);
- onRecentsViewFocusUpdated(cmd);
- scheduleNextTask(cmd);
- }
- };
- if (activityInterface.switchToRecentsIfVisible(animatorListener)) {
- Log.d(TAG, "switching to Overview state - waiting: " + cmd);
- // If successfully switched, wait until animation finishes
- return false;
- }
-
- final T activity = activityInterface.getCreatedContainer();
- if (activity != null) {
- InteractionJankMonitorWrapper.begin(
- activity.getRootView(),
- Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
- }
-
- GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE,
- GestureState.TrackpadGestureType.NONE);
- gestureState.setHandlingAtomicEvent(true);
- AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory()
- .newHandler(gestureState, cmd.createTime);
- interactionHandler.setGestureEndCallback(
- () -> onTransitionComplete(cmd, interactionHandler));
- interactionHandler.initWhenReady("OverviewCommandHelper: cmd.type=" + cmd.type);
-
- RecentsAnimationListener recentAnimListener = new RecentsAnimationListener() {
- @Override
- public void onRecentsAnimationStart(RecentsAnimationController controller,
- RecentsAnimationTargets targets) {
- updateRecentsViewFocus(cmd);
- logShowOverviewFrom(cmd.type);
- activityInterface.runOnInitBackgroundStateUI(() ->
- interactionHandler.onGestureEnded(0, new PointF()));
- cmd.removeListener(this);
- }
-
- @Override
- public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
- interactionHandler.onGestureCancelled();
- cmd.removeListener(this);
-
- T createdActivity = activityInterface.getCreatedContainer();
- if (createdActivity == null) {
- return;
- }
- if (createdRecentsView != null) {
- createdRecentsView.onRecentsAnimationComplete();
- }
- }
- };
-
- if (visibleRecentsView != null) {
- visibleRecentsView.moveRunningTaskToFront();
- }
- if (mTaskAnimationManager.isRecentsAnimationRunning()) {
- cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
- cmd.mActiveCallbacks.addListener(interactionHandler);
- mTaskAnimationManager.notifyRecentsAnimationState(interactionHandler);
- interactionHandler.onGestureStarted(true /*isLikelyToStartNewTask*/);
-
- cmd.mActiveCallbacks.addListener(recentAnimListener);
- mTaskAnimationManager.notifyRecentsAnimationState(recentAnimListener);
- } else {
- Intent intent = new Intent(interactionHandler.getLaunchIntent());
- intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, gestureState.getGestureId());
- cmd.mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(
- gestureState, intent, interactionHandler);
- interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/);
- cmd.mActiveCallbacks.addListener(recentAnimListener);
- }
- Trace.beginAsyncSection(TRANSITION_NAME, 0);
- Log.d(TAG, "switching via recents animation - onGestureStarted: " + cmd);
- return false;
- }
-
- private void onTransitionComplete(CommandInfo cmd, AbsSwipeUpHandler handler) {
- Log.d(TAG, "switching via recents animation - onTransitionComplete: " + cmd);
- cmd.removeListener(handler);
- Trace.endAsyncSection(TRANSITION_NAME, 0);
- onRecentsViewFocusUpdated(cmd);
- scheduleNextTask(cmd);
- }
-
- private void updateRecentsViewFocus(CommandInfo cmd) {
- RecentsView recentsView =
- mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
- if (recentsView == null || (cmd.type != TYPE_KEYBOARD_INPUT && cmd.type != TYPE_HIDE
- && cmd.type != TYPE_SHOW)) {
- return;
- }
- // When the overview is launched via alt tab (cmd type is TYPE_KEYBOARD_INPUT),
- // the touch mode somehow is not change to false by the Android framework.
- // The subsequent tab to go through tasks in overview can only be dispatched to
- // focuses views, while focus can only be requested in
- // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
- // here we launch overview with live tile.
- recentsView.getViewRootImpl().touchModeChanged(false);
- // Ensure that recents view has focus so that it receives the followup key inputs
- if (requestFocus(recentsView.getTaskViewAt(mKeyboardTaskFocusIndex))) {
- return;
- }
- if (requestFocus(recentsView.getNextTaskView())) {
- return;
- }
- if (requestFocus(recentsView.getTaskViewAt(0))) {
- return;
- }
- requestFocus(recentsView);
- }
-
- private void onRecentsViewFocusUpdated(CommandInfo cmd) {
- RecentsView recentsView =
- mOverviewComponentObserver.getActivityInterface().getVisibleRecentsView();
- if (recentsView == null
- || cmd.type != TYPE_HIDE
- || mKeyboardTaskFocusIndex == INVALID_PAGE) {
- return;
- }
- recentsView.setKeyboardTaskFocusIndex(INVALID_PAGE);
- recentsView.setCurrentPage(mKeyboardTaskFocusIndex);
- mKeyboardTaskFocusIndex = INVALID_PAGE;
- }
-
- private boolean requestFocus(@Nullable View taskView) {
- if (taskView == null) {
- return false;
- }
- taskView.post(() -> {
- taskView.requestFocus();
- taskView.requestAccessibilityFocus();
- });
- return true;
- }
-
- private <T extends StatefulActivity<?> & RecentsViewContainer>
- void logShowOverviewFrom(int cmdType) {
- BaseActivityInterface<?, T> activityInterface =
- mOverviewComponentObserver.getActivityInterface();
- var container = activityInterface.getCreatedContainer();
- if (container != null) {
- StatsLogManager.LauncherEvent event;
- switch (cmdType) {
- case TYPE_SHOW -> event = LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT;
- case TYPE_HIDE ->
- event = LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH;
- case TYPE_TOGGLE -> event = LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON;
- default -> {
- return;
- }
- }
-
- StatsLogManager.newInstance(container.asContext())
- .logger()
- .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
- .setTaskSwitcherContainer(
- LauncherAtom.TaskSwitcherContainer.getDefaultInstance())
- .build())
- .log(event);
- }
- }
-
- public void dump(PrintWriter pw) {
- pw.println("OverviewCommandHelper:");
- pw.println(" mPendingCommands=" + mPendingCommands.size());
- if (!mPendingCommands.isEmpty()) {
- pw.println(" pendingCommandType=" + mPendingCommands.get(0).type);
- }
- pw.println(" mKeyboardTaskFocusIndex=" + mKeyboardTaskFocusIndex);
- pw.println(" mWaitForToggleCommandComplete=" + mWaitForToggleCommandComplete);
- }
-
- private static class CommandInfo {
- public final long createTime = SystemClock.elapsedRealtime();
- public final int type;
- RecentsAnimationCallbacks mActiveCallbacks;
-
- CommandInfo(int type) {
- this.type = type;
- }
-
- void removeListener(RecentsAnimationListener listener) {
- if (mActiveCallbacks != null) {
- mActiveCallbacks.removeListener(listener);
- }
- }
-
- @NonNull
- @Override
- public String toString() {
- return "CommandInfo("
- + "type=" + type + ", "
- + "createTime=" + createTime + ", "
- + "mActiveCallbacks=" + mActiveCallbacks
- + ")";
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
new file mode 100644
index 0000000..f6b9e4e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -0,0 +1,484 @@
+/*
+ * 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
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.content.Intent
+import android.graphics.PointF
+import android.os.SystemClock
+import android.os.Trace
+import android.util.Log
+import android.view.View
+import androidx.annotation.BinderThread
+import androidx.annotation.UiThread
+import com.android.internal.jank.Cuj
+import com.android.launcher3.PagedView
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.*
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.RunnableList
+import com.android.quickstep.util.ActiveGestureLog
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper
+import java.io.PrintWriter
+
+/** Helper class to handle various atomic commands for switching between Overview. */
+class OverviewCommandHelper(
+ private val touchInteractionService: TouchInteractionService,
+ private val overviewComponentObserver: OverviewComponentObserver,
+ private val taskAnimationManager: TaskAnimationManager
+) {
+ private val pendingCommands = mutableListOf<CommandInfo>()
+
+ /**
+ * Index of the TaskView that should be focused when launching Overview. Persisted so that we do
+ * not lose the focus across multiple calls of [OverviewCommandHelper.executeCommand] for the
+ * same command
+ */
+ private var keyboardTaskFocusIndex = -1
+
+ /**
+ * Whether we should incoming toggle commands while a previous toggle command is still ongoing.
+ * This serves as a rate-limiter to prevent overlapping animations that can clobber each other
+ * and prevent clean-up callbacks from running. This thus prevents a recurring set of bugs with
+ * janky recents animations and unresponsive home and overview buttons.
+ */
+ private var waitForToggleCommandComplete = false
+
+ /** Called when the command finishes execution. */
+ private fun scheduleNextTask(command: CommandInfo) {
+ if (pendingCommands.isEmpty()) {
+ Log.d(TAG, "no pending commands to schedule")
+ return
+ }
+ if (pendingCommands.first() !== command) {
+ Log.d(
+ TAG,
+ "next task not scheduled. First pending command type " +
+ "is ${pendingCommands.first()} - command type is: $command"
+ )
+ return
+ }
+ Log.d(TAG, "scheduleNextTask called: $command")
+ pendingCommands.removeFirst()
+ executeNext()
+ }
+
+ /**
+ * Executes the next command from the queue. If the command finishes immediately (returns true),
+ * it continues to execute the next command, until the queue is empty of a command defer's its
+ * completion (returns false).
+ */
+ @UiThread
+ private fun executeNext() {
+ if (pendingCommands.isEmpty()) {
+ Log.d(TAG, "executeNext - pendingCommands is empty")
+ return
+ }
+ val command = pendingCommands.first()
+ val result = executeCommand(command)
+ Log.d(TAG, "executeNext command type: $command, result: $result")
+ if (result) {
+ scheduleNextTask(command)
+ }
+ }
+
+ @UiThread
+ private fun addCommand(command: CommandInfo) {
+ val wasEmpty = pendingCommands.isEmpty()
+ pendingCommands.add(command)
+ if (wasEmpty) {
+ executeNext()
+ }
+ }
+
+ /**
+ * Adds a command to be executed next, after all pending tasks are completed. Max commands that
+ * can be queued is [.MAX_QUEUE_SIZE]. Requests after reaching that limit will be silently
+ * dropped.
+ */
+ @BinderThread
+ fun addCommand(type: Int) {
+ if (pendingCommands.size >= MAX_QUEUE_SIZE) {
+ Log.d(
+ TAG,
+ "the pending command queue is full (${pendingCommands.size}). command not added: $type"
+ )
+ return
+ }
+ Log.d(TAG, "adding command type: $type")
+ val command = CommandInfo(type)
+ Executors.MAIN_EXECUTOR.execute { addCommand(command) }
+ }
+
+ @UiThread
+ fun clearPendingCommands() {
+ Log.d(TAG, "clearing pending commands - size: ${pendingCommands.size}")
+ pendingCommands.clear()
+ }
+
+ @UiThread
+ fun canStartHomeSafely(): Boolean =
+ pendingCommands.isEmpty() || pendingCommands.first().type == TYPE_HOME
+
+ private fun getNextTask(view: RecentsView<*, *>): TaskView? {
+ val runningTaskView = view.runningTaskView
+
+ return if (runningTaskView == null) {
+ view.getTaskViewAt(0)
+ } else {
+ val nextTask = view.nextTaskView
+ nextTask ?: runningTaskView
+ }
+ }
+
+ private fun launchTask(
+ recents: RecentsView<*, *>,
+ taskView: TaskView?,
+ command: CommandInfo
+ ): Boolean {
+ var callbackList: RunnableList? = null
+ if (taskView != null) {
+ waitForToggleCommandComplete = true
+ taskView.isEndQuickSwitchCuj = true
+ callbackList = taskView.launchTasks()
+ }
+
+ if (callbackList != null) {
+ callbackList.add {
+ Log.d(TAG, "launching task callback: $command")
+ scheduleNextTask(command)
+ waitForToggleCommandComplete = false
+ }
+ Log.d(TAG, "launching task - waiting for callback: $command")
+ return false
+ } else {
+ recents.startHome()
+ waitForToggleCommandComplete = false
+ return true
+ }
+ }
+
+ /**
+ * Executes the task and returns true if next task can be executed. If false, then the next task
+ * is deferred until [.scheduleNextTask] is called
+ */
+ private fun executeCommand(command: CommandInfo): Boolean {
+ if (waitForToggleCommandComplete && command.type == TYPE_TOGGLE) {
+ Log.d(TAG, "executeCommand: $command - waiting for toggle command complete")
+ return true
+ }
+ val activityInterface: BaseActivityInterface<*, *> =
+ overviewComponentObserver.activityInterface
+
+ val visibleRecentsView: RecentsView<*, *>? =
+ activityInterface.getVisibleRecentsView<RecentsView<*, *>>()
+ val createdRecentsView: RecentsView<*, *>?
+
+ Log.d(TAG, "executeCommand: $command - visibleRecentsView: $visibleRecentsView")
+ if (visibleRecentsView == null) {
+ val activity = activityInterface.getCreatedContainer() as? RecentsViewContainer
+ createdRecentsView = activity?.getOverviewPanel()
+ val deviceProfile = activity?.getDeviceProfile()
+ val uiController = activityInterface.getTaskbarController()
+ val allowQuickSwitch =
+ FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get() &&
+ uiController != null &&
+ deviceProfile != null &&
+ (deviceProfile.isTablet || deviceProfile.isTwoPanels)
+
+ when (command.type) {
+ TYPE_HIDE -> {
+ if (!allowQuickSwitch) return true
+ keyboardTaskFocusIndex = uiController!!.launchFocusedTask()
+ if (keyboardTaskFocusIndex == -1) return true
+ }
+ TYPE_KEYBOARD_INPUT ->
+ if (allowQuickSwitch) {
+ uiController!!.openQuickSwitchView()
+ return true
+ } else {
+ keyboardTaskFocusIndex = 0
+ }
+ TYPE_HOME -> {
+ ActiveGestureLog.INSTANCE.addLog(
+ "OverviewCommandHelper.executeCommand(TYPE_HOME)"
+ )
+ // Although IActivityTaskManager$Stub$Proxy.startActivity is a slow binder call,
+ // we should still call it on main thread because launcher is waiting for
+ // ActivityTaskManager to resume it. Also calling startActivity() on bg thread
+ // could potentially delay resuming launcher. See b/348668521 for more details.
+ touchInteractionService.startActivity(overviewComponentObserver.homeIntent)
+ return true
+ }
+ TYPE_SHOW ->
+ // When Recents is not currently visible, the command's type is
+ // TYPE_SHOW
+ // when overview is triggered via the keyboard overview button or Action+Tab
+ // keys (Not Alt+Tab which is KQS). The overview button on-screen in 3-button
+ // nav is TYPE_TOGGLE.
+ keyboardTaskFocusIndex = 0
+ else -> {}
+ }
+ } else {
+ createdRecentsView = visibleRecentsView
+ when (command.type) {
+ TYPE_SHOW -> return true // already visible
+ TYPE_KEYBOARD_INPUT,
+ TYPE_HIDE -> {
+ if (visibleRecentsView.isHandlingTouch) return true
+
+ keyboardTaskFocusIndex = PagedView.INVALID_PAGE
+ val currentPage = visibleRecentsView.nextPage
+ val taskView = visibleRecentsView.getTaskViewAt(currentPage)
+ return launchTask(visibleRecentsView, taskView, command)
+ }
+ TYPE_TOGGLE ->
+ return launchTask(visibleRecentsView, getNextTask(visibleRecentsView), command)
+ TYPE_HOME -> {
+ visibleRecentsView.startHome()
+ return true
+ }
+ }
+ }
+
+ createdRecentsView?.setKeyboardTaskFocusIndex(keyboardTaskFocusIndex)
+ // Handle recents view focus when launching from home
+ val animatorListener: Animator.AnimatorListener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ super.onAnimationStart(animation)
+ updateRecentsViewFocus(command)
+ logShowOverviewFrom(command.type)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ Log.d(TAG, "switching to Overview state - onAnimationEnd: $command")
+ super.onAnimationEnd(animation)
+ onRecentsViewFocusUpdated(command)
+ scheduleNextTask(command)
+ }
+ }
+ if (activityInterface.switchToRecentsIfVisible(animatorListener)) {
+ Log.d(TAG, "switching to Overview state - waiting: $command")
+ // If successfully switched, wait until animation finishes
+ return false
+ }
+
+ val activity = activityInterface.getCreatedContainer()
+ if (activity != null) {
+ InteractionJankMonitorWrapper.begin(activity.rootView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+ }
+
+ val gestureState =
+ touchInteractionService.createGestureState(
+ GestureState.DEFAULT_STATE,
+ GestureState.TrackpadGestureType.NONE
+ )
+ gestureState.isHandlingAtomicEvent = true
+ val interactionHandler =
+ touchInteractionService.swipeUpHandlerFactory.newHandler(
+ gestureState,
+ command.createTime
+ )
+ interactionHandler.setGestureEndCallback {
+ onTransitionComplete(command, interactionHandler)
+ }
+ interactionHandler.initWhenReady("OverviewCommandHelper: command.type=${command.type}")
+
+ val recentAnimListener: RecentsAnimationCallbacks.RecentsAnimationListener =
+ object : RecentsAnimationCallbacks.RecentsAnimationListener {
+ override fun onRecentsAnimationStart(
+ controller: RecentsAnimationController,
+ targets: RecentsAnimationTargets
+ ) {
+ updateRecentsViewFocus(command)
+ logShowOverviewFrom(command.type)
+ activityInterface.runOnInitBackgroundStateUI {
+ interactionHandler.onGestureEnded(0f, PointF())
+ }
+ command.removeListener(this)
+ }
+
+ override fun onRecentsAnimationCanceled(
+ thumbnailDatas: HashMap<Int, ThumbnailData>
+ ) {
+ interactionHandler.onGestureCancelled()
+ command.removeListener(this)
+
+ activityInterface.getCreatedContainer() ?: return
+ createdRecentsView?.onRecentsAnimationComplete()
+ }
+ }
+
+ // TODO(b/361768912): Dead code. Remove or update after this bug is fixed.
+ // if (visibleRecentsView != null) {
+ // visibleRecentsView.moveRunningTaskToFront();
+ // }
+
+ if (taskAnimationManager.isRecentsAnimationRunning) {
+ command.setAnimationCallbacks(
+ taskAnimationManager.continueRecentsAnimation(gestureState)
+ )
+ command.addListener(interactionHandler)
+ taskAnimationManager.notifyRecentsAnimationState(interactionHandler)
+ interactionHandler.onGestureStarted(true /*isLikelyToStartNewTask*/)
+
+ command.addListener(recentAnimListener)
+ taskAnimationManager.notifyRecentsAnimationState(recentAnimListener)
+ } else {
+ val intent =
+ Intent(interactionHandler.launchIntent)
+ .putExtra(ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID, gestureState.gestureId)
+ command.setAnimationCallbacks(
+ taskAnimationManager.startRecentsAnimation(gestureState, intent, interactionHandler)
+ )
+ interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/)
+ command.addListener(recentAnimListener)
+ }
+ Trace.beginAsyncSection(TRANSITION_NAME, 0)
+ Log.d(TAG, "switching via recents animation - onGestureStarted: $command")
+ return false
+ }
+
+ private fun onTransitionComplete(command: CommandInfo, handler: AbsSwipeUpHandler<*, *, *>) {
+ Log.d(TAG, "switching via recents animation - onTransitionComplete: $command")
+ command.removeListener(handler)
+ Trace.endAsyncSection(TRANSITION_NAME, 0)
+ onRecentsViewFocusUpdated(command)
+ scheduleNextTask(command)
+ }
+
+ private fun updateRecentsViewFocus(command: CommandInfo) {
+ val recentsView: RecentsView<*, *> =
+ overviewComponentObserver.activityInterface.getVisibleRecentsView() ?: return
+ if (
+ command.type != TYPE_KEYBOARD_INPUT &&
+ command.type != TYPE_HIDE &&
+ command.type != TYPE_SHOW
+ ) {
+ return
+ }
+
+ // When the overview is launched via alt tab (command type is TYPE_KEYBOARD_INPUT),
+ // the touch mode somehow is not change to false by the Android framework.
+ // The subsequent tab to go through tasks in overview can only be dispatched to
+ // focuses views, while focus can only be requested in
+ // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
+ // here we launch overview with live tile.
+ recentsView.viewRootImpl.touchModeChanged(false)
+ // Ensure that recents view has focus so that it receives the followup key inputs
+ if (requestFocus(recentsView.getTaskViewAt(keyboardTaskFocusIndex))) return
+ if (requestFocus(recentsView.nextTaskView)) return
+ if (requestFocus(recentsView.getTaskViewAt(0))) return
+ requestFocus(recentsView)
+ }
+
+ private fun onRecentsViewFocusUpdated(command: CommandInfo) {
+ val recentsView: RecentsView<*, *> =
+ overviewComponentObserver.activityInterface.getVisibleRecentsView() ?: return
+ if (command.type != TYPE_HIDE || keyboardTaskFocusIndex == PagedView.INVALID_PAGE) {
+ return
+ }
+ recentsView.setKeyboardTaskFocusIndex(PagedView.INVALID_PAGE)
+ recentsView.currentPage = keyboardTaskFocusIndex
+ keyboardTaskFocusIndex = PagedView.INVALID_PAGE
+ }
+
+ private fun requestFocus(taskView: View?): Boolean {
+ if (taskView == null) return false
+ taskView.post {
+ taskView.requestFocus()
+ taskView.requestAccessibilityFocus()
+ }
+ return true
+ }
+
+ private fun logShowOverviewFrom(commandType: Int) {
+ val activityInterface: BaseActivityInterface<*, *> =
+ overviewComponentObserver.activityInterface
+ val container = activityInterface.getCreatedContainer() as? RecentsViewContainer ?: return
+ val event =
+ when (commandType) {
+ TYPE_SHOW -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
+ TYPE_HIDE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
+ TYPE_TOGGLE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
+ else -> return
+ }
+ StatsLogManager.newInstance(container.asContext())
+ .logger()
+ .withContainerInfo(
+ LauncherAtom.ContainerInfo.newBuilder()
+ .setTaskSwitcherContainer(
+ LauncherAtom.TaskSwitcherContainer.getDefaultInstance()
+ )
+ .build()
+ )
+ .log(event)
+ }
+
+ fun dump(pw: PrintWriter) {
+ pw.println("OverviewCommandHelper:")
+ pw.println(" pendingCommands=${pendingCommands.size}")
+ if (pendingCommands.isNotEmpty()) {
+ pw.println(" pendingCommandType=${pendingCommands.first().type}")
+ }
+ pw.println(" mKeyboardTaskFocusIndex=$keyboardTaskFocusIndex")
+ pw.println(" mWaitForToggleCommandComplete=$waitForToggleCommandComplete")
+ }
+
+ private data class CommandInfo(
+ val type: Int,
+ val createTime: Long = SystemClock.elapsedRealtime(),
+ private var animationCallbacks: RecentsAnimationCallbacks? = null
+ ) {
+ fun setAnimationCallbacks(recentsAnimationCallbacks: RecentsAnimationCallbacks) {
+ this.animationCallbacks = recentsAnimationCallbacks
+ }
+
+ fun addListener(listener: RecentsAnimationCallbacks.RecentsAnimationListener) {
+ animationCallbacks?.addListener(listener)
+ }
+
+ fun removeListener(listener: RecentsAnimationCallbacks.RecentsAnimationListener?) {
+ animationCallbacks?.removeListener(listener)
+ }
+ }
+
+ companion object {
+ private const val TAG = "OverviewCommandHelper"
+
+ const val TYPE_SHOW: Int = 1
+ const val TYPE_KEYBOARD_INPUT: Int = 2
+ const val TYPE_HIDE: Int = 3
+ const val TYPE_TOGGLE: Int = 4
+ const val TYPE_HOME: Int = 5
+
+ /**
+ * Use case for needing a queue is double tapping recents button in 3 button nav. Size of 2
+ * should be enough. We'll toss in one more because we're kind hearted.
+ */
+ private const val MAX_QUEUE_SIZE = 3
+
+ private const val TRANSITION_NAME = "Transition:toOverview"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index e66da52..05bef35 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -20,7 +20,7 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
-import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
+import static com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -40,8 +40,8 @@
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.recents.IRecentTasksListener;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.io.PrintWriter;
import java.util.ArrayList;
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 3dec381..1be60de 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -17,7 +17,7 @@
package com.android.quickstep;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
-import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
import android.app.WindowConfiguration;
import android.content.Context;
@@ -33,7 +33,7 @@
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
-import com.android.wm.shell.util.SplitBounds;
+import com.android.wm.shell.shared.split.SplitBounds;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index ead5b7b..4392255 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -82,7 +82,6 @@
import com.android.wm.shell.bubbles.IBubbles;
import com.android.wm.shell.bubbles.IBubblesListener;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.desktopmode.IDesktopMode;
@@ -91,16 +90,18 @@
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.recents.IRecentTasksListener;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.IShellTransitions;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
import com.android.wm.shell.splitscreen.ISplitSelectListener;
import com.android.wm.shell.startingsurface.IStartingWindow;
import com.android.wm.shell.startingsurface.IStartingWindowListener;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -934,6 +935,17 @@
}
}
+ /** Tells SysUI to show the expanded view. */
+ public void showExpandedView() {
+ try {
+ if (mBubbles != null) {
+ mBubbles.showExpandedView();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to call showExpandedView");
+ }
+ }
+
//
// Splitscreen
//
@@ -1517,7 +1529,7 @@
// Aidl bundles need to explicitly set class loader
// https://developer.android.com/guide/components/aidl#Bundles
if (extras != null) {
- extras.setClassLoader(getClass().getClassLoader());
+ extras.setClassLoader(SplitBounds.class.getClassLoader());
}
listener.onAnimationStart(new RecentsAnimationControllerCompat(controller), apps,
wallpapers, homeContentInsets, minimizedHomeBounds, extras);
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 9510998..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;
@@ -1210,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)
@@ -1306,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/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 3ca7191..14f47d1 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -277,7 +277,7 @@
}
private void endRemoteAnimation() {
- if (mRecentsAnimationController != null) {
+ if (!mHomeLaunched && mRecentsAnimationController != null) {
mRecentsAnimationController.finishController(
false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */);
}
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index ac4032c..e1013db 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -242,8 +242,7 @@
WorkspaceItemInfo app2 = appPairIcon.getInfo().getSecondApp();
ComponentKey app1Key = new ComponentKey(app1.getTargetComponent(), app1.user);
ComponentKey app2Key = new ComponentKey(app2.getTargetComponent(), app2.user);
- mSplitSelectStateController.setLaunchingCuj(cuj);
- InteractionJankMonitorWrapper.begin(appPairIcon, cuj);
+ mSplitSelectStateController.setLaunchingCuj(appPairIcon, cuj);
mSplitSelectStateController.findLastActiveTasksAndRunCallback(
Arrays.asList(app1Key, app2Key),
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/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
index d58cb91..d982e81 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -17,14 +17,13 @@
package com.android.quickstep.util
import android.util.Log
-import android.view.WindowManager
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionInfo.FLAG_FIRST_CUSTOM
import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.wm.shell.util.SplitBounds
+import com.android.wm.shell.shared.split.SplitBounds
import java.lang.IllegalStateException
class SplitScreenUtils {
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 431cfbe..ae6757f 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -59,6 +59,7 @@
import android.util.Log;
import android.util.Pair;
import android.view.SurfaceControl;
+import android.view.View;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.RemoteTransition;
import android.window.RemoteTransitionStub;
@@ -148,9 +149,10 @@
/**
* Should be a constant from {@link com.android.internal.jank.Cuj} or -1, does not need to be
- * set for all launches.
+ * set for all launches. Used in conjunction with {@link #mLaunchingViewCuj} below.
*/
private int mLaunchCuj = -1;
+ private View mLaunchingViewCuj;
private FloatingTaskView mFirstFloatingTaskView;
private SplitInstructionsView mSplitInstructionsView;
@@ -650,7 +652,12 @@
return mSplitAnimationController;
}
- public void setLaunchingCuj(int launchCuj) {
+ /**
+ * Set params to invoke a trace session for the given view and CUJ when we begin animating the
+ * split launch AFTER we get a response from Shell.
+ */
+ public void setLaunchingCuj(View launchingView, int launchCuj) {
+ mLaunchingViewCuj = launchingView;
mLaunchCuj = launchCuj;
}
@@ -688,6 +695,9 @@
&& mLaunchingTaskView.getRecentsView() != null
&& mLaunchingTaskView.getRecentsView().isTaskViewVisible(
mLaunchingTaskView);
+ if (mLaunchingViewCuj != null && mLaunchCuj != -1) {
+ InteractionJankMonitorWrapper.begin(mLaunchingViewCuj, mLaunchCuj);
+ }
mSplitAnimationController.playSplitLaunchAnimation(
shouldLaunchFromTaskView ? mLaunchingTaskView : null,
mLaunchingIconView,
@@ -750,6 +760,7 @@
InteractionJankMonitorWrapper.end(mLaunchCuj);
}
mLaunchCuj = -1;
+ mLaunchingViewCuj = null;
if (mSessionInstanceIds != null) {
mStatsLogManager.logger()
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 56e91ed..828322b 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -39,7 +39,7 @@
import com.android.quickstep.TaskAnimationManager;
import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.wm.shell.pip.PipContentOverlay;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
/**
* Subclass of {@link RectFSpringAnim} that animates an Activity to PiP (picture-in-picture) window
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..731b008
--- /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/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 e3c52d7..69a9690 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;
@@ -231,9 +232,9 @@
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import kotlin.Unit;
@@ -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;
@@ -2737,10 +2759,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 +3050,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);
}
}
@@ -3273,12 +3303,12 @@
longRowWidth = largeTaskWidthAndSpacing;
}
- if (longRowWidth < mLastComputedGridSize.width()) {
- mClearAllShortTotalWidthTranslation =
- (mIsRtl
- ? mLastComputedTaskSize.right
- : deviceProfile.widthPx - mLastComputedTaskSize.left)
- - longRowWidth - deviceProfile.overviewGridSideMargin;
+ // If first task is not in the expected position (mLastComputedTaskSize) and being too close
+ // to ClearAllButton, then apply extra translation to ClearAllButton.
+ int firstTaskStart = mLastComputedGridSize.left + longRowWidth;
+ int expectedFirstTaskStart = mLastComputedTaskSize.right;
+ if (firstTaskStart < expectedFirstTaskStart) {
+ mClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart;
clearAllShortTotalWidthTranslation = mIsRtl
? -mClearAllShortTotalWidthTranslation : mClearAllShortTotalWidthTranslation;
if (snappedTaskRowWidth == longRowWidth) {
@@ -4701,8 +4731,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/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index e43d7b4..815f8fa 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
@@ -1398,7 +1407,7 @@
private fun onFocusTransitionProgressUpdated(focusTransitionProgress: Float) {
taskContainers.forEach {
it.iconView.setContentAlpha(focusTransitionProgress)
- it.digitalWellBeingToast?.updateBannerOffset(1f - focusTransitionProgress)
+ it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - focusTransitionProgress
}
}
@@ -1548,7 +1557,7 @@
private fun onModalnessUpdated(modalness: Float) {
taskContainers.forEach {
it.iconView.setModalAlpha(1 - modalness)
- it.digitalWellBeingToast?.updateBannerOffset(modalness)
+ it.digitalWellBeingToast?.bannerOffsetPercentage = modalness
}
}
@@ -1584,7 +1593,7 @@
}
dismissScale = 1f
translationZ = 0f
- alpha = stableAlpha
+ attachAlpha = 1f
setIconScaleAndDim(1f)
setColorTint(0f, 0)
}
@@ -1661,6 +1670,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/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 4da06e1..7928ce9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -64,6 +64,7 @@
private lateinit var bubble: BubbleBarBubble
private lateinit var bubbleBarView: BubbleBarView
private lateinit var bubbleStashController: BubbleStashController
+ private val onExpandedNoOp = Runnable {}
@Before
fun setUp() {
@@ -81,7 +82,12 @@
whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpandedNoOp,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleInForStashed(bubble, isExpanding = false)
@@ -125,7 +131,12 @@
whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpandedNoOp,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleInForStashed(bubble, isExpanding = false)
@@ -168,7 +179,12 @@
whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpandedNoOp,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleInForStashed(bubble, isExpanding = false)
@@ -208,7 +224,12 @@
whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpandedNoOp,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleInForStashed(bubble, isExpanding = false)
@@ -249,7 +270,12 @@
whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpandedNoOp,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleInForStashed(bubble, isExpanding = false)
@@ -278,8 +304,15 @@
val handleAnimator = PhysicsAnimator.getInstance(handle)
whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+ var notifiedExpanded = false
+ val onExpanded = Runnable { notifiedExpanded = true }
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpanded,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleInForStashed(bubble, isExpanding = true)
@@ -303,6 +336,7 @@
assertThat(animatorScheduler.delayedBlock).isNull()
verify(bubbleStashController).showBubbleBarImmediate()
+ assertThat(notifiedExpanded).isTrue()
}
@Test
@@ -314,8 +348,15 @@
val handleAnimator = PhysicsAnimator.getInstance(handle)
whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+ var notifiedExpanded = false
+ val onExpanded = Runnable { notifiedExpanded = true }
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpanded,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleInForStashed(bubble, isExpanding = false)
@@ -345,6 +386,7 @@
.isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR)
verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_TASKBAR)
assertThat(animator.isAnimating).isFalse()
+ assertThat(notifiedExpanded).isTrue()
}
@Test
@@ -356,8 +398,15 @@
val handleAnimator = PhysicsAnimator.getInstance(handle)
whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+ var notifiedExpanded = false
+ val onExpanded = Runnable { notifiedExpanded = true }
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpanded,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleInForStashed(bubble, isExpanding = false)
@@ -384,6 +433,7 @@
.isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR)
verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_TASKBAR)
assertThat(animator.isAnimating).isFalse()
+ assertThat(notifiedExpanded).isTrue()
}
@Test
@@ -400,7 +450,12 @@
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpandedNoOp,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateToInitialState(bubble, isInApp = true, isExpanding = false)
@@ -442,8 +497,15 @@
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+ var notifiedExpanded = false
+ val onExpanded = Runnable { notifiedExpanded = true }
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpanded,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateToInitialState(bubble, isInApp = true, isExpanding = true)
@@ -459,6 +521,7 @@
assertThat(animatorScheduler.delayedBlock).isNull()
verify(bubbleStashController).showBubbleBarImmediate()
+ assertThat(notifiedExpanded).isTrue()
}
@Test
@@ -471,7 +534,12 @@
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpandedNoOp,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateToInitialState(bubble, isInApp = false, isExpanding = false)
@@ -502,8 +570,15 @@
whenever(bubbleStashController.bubbleBarTranslationY)
.thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+ var notifiedExpanded = false
+ val onExpanded = Runnable { notifiedExpanded = true }
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpanded,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateToInitialState(bubble, isInApp = false, isExpanding = false)
@@ -533,6 +608,7 @@
verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_HOTSEAT)
assertThat(animator.isAnimating).isFalse()
verify(bubbleStashController).showBubbleBarImmediate()
+ assertThat(notifiedExpanded).isTrue()
}
@Test
@@ -542,8 +618,15 @@
whenever(bubbleStashController.bubbleBarTranslationY)
.thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+ var notifiedExpanded = false
+ val onExpanded = Runnable { notifiedExpanded = true }
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpanded,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateToInitialState(bubble, isInApp = false, isExpanding = false)
@@ -566,6 +649,7 @@
verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_HOTSEAT)
assertThat(animator.isAnimating).isFalse()
+ assertThat(notifiedExpanded).isTrue()
}
@Test
@@ -578,7 +662,12 @@
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpandedNoOp,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleBarForCollapsed(bubble, isExpanding = false)
@@ -617,8 +706,15 @@
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+ var notifiedExpanded = false
+ val onExpanded = Runnable { notifiedExpanded = true }
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpanded,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleBarForCollapsed(bubble, isExpanding = true)
@@ -645,6 +741,7 @@
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
assertThat(bubbleBarView.isExpanded).isTrue()
verify(bubbleStashController).showBubbleBarImmediate()
+ assertThat(notifiedExpanded).isTrue()
}
@Test
@@ -656,8 +753,15 @@
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+ var notifiedExpanded = false
+ val onExpanded = Runnable { notifiedExpanded = true }
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpanded,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleBarForCollapsed(bubble, isExpanding = false)
@@ -695,6 +799,7 @@
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
assertThat(bubbleBarView.isExpanded).isTrue()
verify(bubbleStashController).showBubbleBarImmediate()
+ assertThat(notifiedExpanded).isTrue()
}
@Test
@@ -706,8 +811,15 @@
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+ var notifiedExpanded = false
+ val onExpanded = Runnable { notifiedExpanded = true }
val animator =
- BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ onExpanded,
+ animatorScheduler
+ )
InstrumentationRegistry.getInstrumentation().runOnMainSync {
animator.animateBubbleBarForCollapsed(bubble, isExpanding = false)
@@ -742,6 +854,7 @@
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
assertThat(bubbleBarView.isExpanded).isTrue()
verify(bubbleStashController).showBubbleBarImmediate()
+ assertThat(notifiedExpanded).isTrue()
}
private fun setUpBubbleBar() {
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index d9d5585..885a7f6 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -39,8 +39,8 @@
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.Task.TaskKey
import com.android.window.flags.Flags
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index cbc8441..244b897 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -39,7 +39,7 @@
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import org.junit.Before;
import org.junit.Test;
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/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml
index be4765a..39af989 100644
--- a/res/drawable/add_item_dialog_background.xml
+++ b/res/drawable/add_item_dialog_background.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
- <solid android:color="?attr/materialColorSurfaceContainerHighest" />
+ <solid android:color="?attr/widgetPickerPrimarySurfaceColor" />
<corners
android:topLeftRadius="?android:attr/dialogCornerRadius"
android:topRightRadius="?android:attr/dialogCornerRadius" />
diff --git a/res/drawable/bg_letter_list_text.xml b/res/drawable/bg_letter_list_text.xml
new file mode 100644
index 0000000..427702b
--- /dev/null
+++ b/res/drawable/bg_letter_list_text.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid android:color="?attr/materialColorSurfaceContainer" />
+ <corners android:radius="100dp"/>
+ <size
+ android:width="@dimen/bg_letter_list_text_size"
+ android:height="@dimen/bg_letter_list_text_size"/>
+</shape>
\ No newline at end of file
diff --git a/res/layout/all_apps_fast_scroller.xml b/res/layout/all_apps_fast_scroller.xml
index 0f1d933..7e16ca5 100644
--- a/res/layout/all_apps_fast_scroller.xml
+++ b/res/layout/all_apps_fast_scroller.xml
@@ -36,4 +36,17 @@
android:layout_marginEnd="@dimen/fastscroll_end_margin"
launcher:canThumbDetach="true" />
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/scroll_letter_layout"
+ android:layout_width="@dimen/fastscroll_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_alignTop="@+id/all_apps_header"
+ android:layout_marginTop="@dimen/all_apps_header_bottom_padding"
+ android:layout_marginEnd="@dimen/fastscroll_list_letter_end_margin"
+ android:clipToPadding="false"
+ android:outlineProvider="none"
+ />
</merge>
\ No newline at end of file
diff --git a/res/layout/fast_scroller_letter_list_text_view.xml b/res/layout/fast_scroller_letter_list_text_view.xml
new file mode 100644
index 0000000..493b6fc
--- /dev/null
+++ b/res/layout/fast_scroller_letter_list_text_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<com.android.launcher3.allapps.LetterListTextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/fastscroll_list_letter_size"
+ android:layout_height="@dimen/fastscroll_list_letter_size"
+ android:textSize="@dimen/fastscroll_list_letter_text_size"
+ android:importantForAccessibility="no"
+ android:gravity="center"
+ android:clickable="false">
+</com.android.launcher3.allapps.LetterListTextView>
\ No newline at end of file
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 3b239ac..f8c075f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -81,6 +81,11 @@
<dimen name="fastscroll_popup_text_size">32dp</dimen>
<dimen name="fastscroll_popup_margin">19dp</dimen>
+ <dimen name="fastscroll_list_letter_size">5dp</dimen>
+ <dimen name="fastscroll_list_letter_text_size">14sp</dimen>
+ <dimen name="fastscroll_list_letter_end_margin">-10dp</dimen>
+ <dimen name="bg_letter_list_text_size">20sp</dimen>
+
<!--
Fast scroller draws the content horizontally centered. The end of the track should be
aligned at the end of the container.
@@ -306,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/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index de1748b..6622e11 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -25,6 +25,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.Nullable;
+import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -54,9 +55,11 @@
super(context, attrs, defStyleAttr);
}
- public void bindFastScrollbar(RecyclerViewFastScroller scrollbar) {
+ public void bindFastScrollbar(RecyclerViewFastScroller scrollbar,
+ RecyclerViewFastScroller.FastScrollerLocation location) {
mScrollbar = scrollbar;
mScrollbar.setRecyclerView(this);
+ mScrollbar.setFastScrollerLocation(location);
onUpdateScrollbar(0);
}
@@ -163,6 +166,13 @@
public abstract void onUpdateScrollbar(int dy);
/**
+ * Return the fast scroll letter list view in the A-Z list.
+ */
+ public ConstraintLayout getLetterList() {
+ return null;
+ }
+
+ /**
* <p>Override in each subclass of this base class.
*/
public void onFastScrollCompleted() {}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 9f122c1..3ca6099 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;
@@ -1410,15 +1407,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.
*
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 227ac2b..cc4724c 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -29,6 +29,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE;
+import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.ALL_APPS_SCROLLER;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -65,6 +66,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.VisibleForTesting;
+import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.RecyclerView;
@@ -168,6 +170,7 @@
protected FloatingHeaderView mHeader;
protected View mBottomSheetBackground;
protected RecyclerViewFastScroller mFastScroller;
+ private ConstraintLayout mFastScrollLetterLayout;
/**
* View that defines the search box. Result is rendered inside {@link #mSearchRecyclerView}.
@@ -282,6 +285,13 @@
mSearchRecyclerView = findViewById(R.id.search_results_list_view);
mFastScroller = findViewById(R.id.fast_scroller);
mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
+ mFastScrollLetterLayout = findViewById(R.id.scroll_letter_layout);
+ if (Flags.letterFastScroller()) {
+ // Set clip children to false otherwise the scroller letters will be clipped.
+ setClipChildren(false);
+ } else {
+ setClipChildren(true);
+ }
mSearchContainer = inflateSearchBar();
if (!isSearchBarFloating()) {
@@ -563,7 +573,8 @@
mActivityContext.hideKeyboard();
}
if (mAH.get(currentActivePage).mRecyclerView != null) {
- mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar(mFastScroller);
+ mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar(mFastScroller,
+ ALL_APPS_SCROLLER);
}
// Header keeps track of active recycler view to properly render header protection.
mHeader.setActiveRV(currentActivePage);
@@ -1500,6 +1511,10 @@
}
}
+ ConstraintLayout getFastScrollerLetterList() {
+ return mFastScrollLetterLayout;
+ }
+
/**
* redraws header protection
*/
@@ -1567,7 +1582,7 @@
void setup(@NonNull View rv, @Nullable Predicate<ItemInfo> matcher) {
mAppsList.updateItemFilter(matcher);
mRecyclerView = (AllAppsRecyclerView) rv;
- mRecyclerView.bindFastScrollbar(mFastScroller);
+ mRecyclerView.bindFastScrollbar(mFastScroller, ALL_APPS_SCROLLER);
mRecyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
mRecyclerView.setApps(mAppsList);
mRecyclerView.setLayoutManager(mLayoutManager);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 2a47222..ae45a35 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.allapps;
+import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
+import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
+
import static com.android.launcher3.config.FeatureFlags.ALL_APPS_GONE_VISIBILITY;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo;
@@ -36,22 +39,29 @@
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.util.Consumer;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.FastScrollRecyclerView;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.views.ActivityContext;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -66,6 +76,7 @@
protected final int mNumAppsPerRow;
private final AllAppsFastScrollHelper mFastScrollHelper;
private int mCumulativeVerticalScroll;
+ private ConstraintLayout mLetterList;
protected AlphabeticalAppsList<?> mApps;
@@ -238,6 +249,9 @@
return;
}
+ if (Flags.letterFastScroller() && !mScrollbar.isDraggingThumb()) {
+ setLettersToScrollLayout(mApps.getFastScrollerSections());
+ }
// Only show the scrollbar if there is height to be scrolled
int availableScrollBarHeight = getAvailableScrollBarHeight();
int availableScrollHeight = getAvailableScrollHeight();
@@ -319,6 +333,80 @@
return false;
}
+ public void setLettersToScrollLayout(
+ List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections) {
+ if (mLetterList != null) {
+ mLetterList.removeAllViews();
+ }
+ Context context = getContext();
+ ActivityAllAppsContainerView<?> allAppsContainerView =
+ ActivityContext.lookupContext(context).getAppsView();
+ mLetterList = allAppsContainerView.getFastScrollerLetterList();
+ mLetterList.setPadding(0, getScrollBarTop(), 0, getScrollBarMarginBottom());
+ List<LetterListTextView> textViews = new ArrayList<>();
+ for (int i = 0; i < fastScrollSections.size(); i++) {
+ AlphabeticalAppsList.FastScrollSectionInfo sectionInfo = fastScrollSections.get(i);
+ LetterListTextView textView =
+ (LetterListTextView) LayoutInflater.from(context).inflate(
+ R.layout.fast_scroller_letter_list_text_view, mLetterList, false);
+ int viewId = View.generateViewId();
+ textView.setId(viewId);
+ sectionInfo.setId(viewId);
+ textView.setText(sectionInfo.sectionName);
+ if (i == fastScrollSections.size() - 1) {
+ // The last section info is just a duplicate so that user can scroll to the bottom.
+ textView.setVisibility(INVISIBLE);
+ }
+ ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
+ MATCH_CONSTRAINT, WRAP_CONTENT);
+ lp.dimensionRatio = "v,1:1";
+ textView.setLayoutParams(lp);
+ textViews.add(textView);
+ mLetterList.addView(textView);
+ }
+ // Need to add an extra textview to be aligned.
+ LetterListTextView lastLetterListTextView = new LetterListTextView(context);
+ int currentId = View.generateViewId();
+ lastLetterListTextView.setId(currentId);
+ lastLetterListTextView.setVisibility(INVISIBLE);
+ textViews.add(lastLetterListTextView);
+ mLetterList.addView(lastLetterListTextView);
+ constraintTextViewsVertically(mLetterList, textViews);
+ mLetterList.setVisibility(VISIBLE);
+ }
+
+ private void constraintTextViewsVertically(ConstraintLayout constraintLayout,
+ List<LetterListTextView> textViews) {
+ ConstraintSet chain = new ConstraintSet();
+ chain.clone(constraintLayout);
+ for (int i = 0; i < textViews.size(); i++) {
+ LetterListTextView currentView = textViews.get(i);
+ if (i == 0) {
+ chain.connect(currentView.getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID,
+ ConstraintSet.TOP);
+ } else {
+ chain.connect(currentView.getId(), ConstraintSet.TOP, textViews.get(i-1).getId(),
+ ConstraintSet.BOTTOM);
+ }
+ chain.connect(currentView.getId(), ConstraintSet.START, constraintLayout.getId(),
+ ConstraintSet.START);
+ chain.connect(currentView.getId(), ConstraintSet.END, constraintLayout.getId(),
+ ConstraintSet.END);
+ }
+ int[] viewIds = textViews.stream().mapToInt(TextView::getId).toArray();
+ float[] weights = new float[textViews.size()];
+ Arrays.fill(weights,1); // fill with 1 for equal weights
+ chain.createVerticalChain(constraintLayout.getId(), ConstraintSet.TOP,
+ constraintLayout.getId(), ConstraintSet.BOTTOM, viewIds, weights,
+ ConstraintSet.CHAIN_SPREAD);
+ chain.applyTo(constraintLayout);
+ }
+
+ @Override
+ public ConstraintLayout getLetterList() {
+ return mLetterList;
+ }
+
private void logCumulativeVerticalScroll() {
ActivityContext context = ActivityContext.lookupContext(getContext());
StatsLogManager mgr = context.getStatsLogManager();
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 6dd811a..8e44d65 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -74,11 +74,17 @@
public final CharSequence sectionName;
// The item position
public final int position;
+ // The view id associated with this section
+ public int id = -1;
public FastScrollSectionInfo(CharSequence sectionName, int position) {
this.sectionName = sectionName;
this.position = position;
}
+
+ public void setId(int id) {
+ this.id = id;
+ }
}
diff --git a/src/com/android/launcher3/allapps/LetterListTextView.java b/src/com/android/launcher3/allapps/LetterListTextView.java
new file mode 100644
index 0000000..9326d79
--- /dev/null
+++ b/src/com/android/launcher3/allapps/LetterListTextView.java
@@ -0,0 +1,133 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import androidx.core.graphics.ColorUtils;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Themes;
+
+/**
+ * A TextView that is used to display the letter list in the fast scroller.
+ */
+public class LetterListTextView extends TextView {
+ private static final float ABSOLUTE_TRANSLATION_X = 30f;
+ private static final float ABSOLUTE_SCALE = 1.4f;
+ private final Drawable mLetterBackground;
+ private final int mLetterListTextWidthAndHeight;
+ private final int mTextColor;
+ private final int mBackgroundColor;
+ private final int mSelectedColor;
+
+ public LetterListTextView(Context context) {
+ this(context, null, 0);
+ }
+
+ public LetterListTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LetterListTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mLetterBackground = context.getDrawable(R.drawable.bg_letter_list_text);
+ mLetterListTextWidthAndHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.fastscroll_list_letter_size);
+ mTextColor = Themes.getAttrColor(context, R.attr.materialColorOnSurface);
+ mBackgroundColor = Themes.getAttrColor(context, R.attr.materialColorSurfaceContainer);
+ mSelectedColor = Themes.getAttrColor(context, R.attr.materialColorOnSecondary);
+ }
+
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+ setBackground(mLetterBackground);
+ setTextColor(mTextColor);
+ setClickable(false);
+ setWidth(mLetterListTextWidthAndHeight);
+ setTextSize(mLetterListTextWidthAndHeight);
+ setVisibility(VISIBLE);
+ }
+
+ /**
+ * Animates the letter list text view based on the current finger position.
+ *
+ * @param currentFingerY The Y position of where the finger is placed on the fastScroller in
+ * pixels.
+ */
+ public void animateBasedOnYPosition(int currentFingerY) {
+ if (getBackground() == null) {
+ return;
+ }
+ float cutOffMin = currentFingerY - (getHeight() * 2);
+ float cutOffMax = currentFingerY + (getHeight() * 2);
+ float cutOffDistance = cutOffMax - cutOffMin;
+ // Update the background blend color
+ boolean isWithinAnimationBounds = getY() < cutOffMax && getY() > cutOffMin;
+ if (isWithinAnimationBounds) {
+ getBackground().setColorFilter(new PorterDuffColorFilter(
+ getBlendColorBasedOnYPosition(currentFingerY, cutOffDistance),
+ PorterDuff.Mode.MULTIPLY));
+ } else {
+ getBackground().setColorFilter(new PorterDuffColorFilter(
+ mBackgroundColor, PorterDuff.Mode.MULTIPLY));
+ }
+ translateBasedOnYPosition(currentFingerY, cutOffDistance, isWithinAnimationBounds);
+ scaleBasedOnYPosition(currentFingerY, cutOffDistance, isWithinAnimationBounds);
+ }
+
+ private int getBlendColorBasedOnYPosition(int y, float cutOffDistance) {
+ float raisedCosineBlend = (float) Math.cos(((y - getY()) / (cutOffDistance)) * Math.PI);
+ float blendRatio = Utilities.boundToRange(raisedCosineBlend, 0f, 1f);
+ return ColorUtils.blendARGB(mBackgroundColor, mSelectedColor, blendRatio);
+ }
+
+ private void scaleBasedOnYPosition(int y, float cutOffDistance,
+ boolean isWithinAnimationBounds) {
+ float raisedCosineScale = (float) Math.cos(((y - getY()) / (cutOffDistance)) * Math.PI)
+ * ABSOLUTE_SCALE;
+ if (isWithinAnimationBounds) {
+ raisedCosineScale = Utilities.boundToRange(raisedCosineScale, 1f, ABSOLUTE_SCALE);
+ setScaleX(raisedCosineScale);
+ setScaleY(raisedCosineScale);
+ } else {
+ setScaleX(1);
+ setScaleY(1);
+ }
+ }
+
+ private void translateBasedOnYPosition(int y, float cutOffDistance,
+ boolean isWithinAnimationBounds) {
+ float raisedCosineTranslation =
+ (float) Math.cos(((y - getY()) / (cutOffDistance)) * Math.PI)
+ * ABSOLUTE_TRANSLATION_X;
+ if (isWithinAnimationBounds) {
+ raisedCosineTranslation = -1 * Utilities.boundToRange(raisedCosineTranslation,
+ 0, ABSOLUTE_TRANSLATION_X);
+ setTranslationX(raisedCosineTranslation);
+ } else {
+ setTranslationX(0);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 3edf1f2..7bec768 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -773,6 +773,7 @@
addAnimationStartListeners(anim);
// Because t=0 has the folder match the folder icon, we can skip the
// first frame and have the same movement one frame earlier.
+ Log.d("b/311077782", "Folder.animateOpen");
anim.setCurrentPlayTime(Math.min(getSingleFrameMs(getContext()), anim.getTotalDuration()));
anim.start();
diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
index 406f697..de2269c 100644
--- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
+++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
@@ -18,10 +18,12 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.LauncherActivityInfo;
+import android.os.Build;
import android.os.UserHandle;
import androidx.annotation.NonNull;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.icons.BaseIconFactory.IconOptions;
@@ -64,9 +66,16 @@
@Override
public BitmapInfo loadIcon(@NonNull Context context, @NonNull LauncherActivityInfo object) {
try (LauncherIcons li = LauncherIcons.obtain(context)) {
- return li.createBadgedIconBitmap(LauncherAppState.getInstance(context)
- .getIconProvider().getIcon(object, li.mFillResIconDpi),
- new IconOptions().setUser(object.getUser()));
+ IconOptions iconOptions = new IconOptions().setUser(object.getUser());
+ iconOptions.mIsArchived = Flags.useNewIconForArchivedApps()
+ && Build.VERSION.SDK_INT >= 35
+ && object.getActivityInfo().isArchived;
+ return li.createBadgedIconBitmap(
+ LauncherAppState.getInstance(context)
+ .getIconProvider()
+ .getIcon(object, li.mFillResIconDpi),
+ iconOptions
+ );
}
}
}
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index 551c2d8..59d1d00 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -192,22 +192,18 @@
}
private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
- final Exception stackTrace = new Exception();
// Queue the item up for adding if launcher has not loaded properly yet
MODEL_EXECUTOR.post(() -> {
Pair<ItemInfo, Object> itemInfo = info.getItemInfo(mContext);
if (itemInfo == null) {
FileLog.d(LOG,
- "Adding PendingInstallShortcutInfo with no attached info to queue.",
- stackTrace);
+ "Adding PendingInstallShortcutInfo with no attached info to queue.");
} else {
FileLog.d(LOG,
- "Adding PendingInstallShortcutInfo to queue. Attached info: "
- + itemInfo.first,
- stackTrace);
+ "Adding PendingInstallShortcutInfo to queue."
+ + " Attached info: " + itemInfo.first);
}
-
addToQueue(info);
});
flushInstallQueue();
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 269cb9f..605accf 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -209,7 +209,10 @@
mApp.getContext().getContentResolver(),
"launcher_broadcast_installed_apps",
/* def= */ 0);
- if (launcherBroadcastInstalledApps == 1 && mIsRestoreFromBackup) {
+ boolean shouldAttachArchivingExtras = mIsRestoreFromBackup
+ && (launcherBroadcastInstalledApps == 1
+ || Flags.enableFirstScreenBroadcastArchivingExtras());
+ if (shouldAttachArchivingExtras) {
List<FirstScreenBroadcastModel> broadcastModels =
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
mPmHelper,
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/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index c117be4..856c294 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -25,6 +25,7 @@
import android.content.pm.PackageInstaller.SessionInfo;
import android.os.Build;
import android.os.UserHandle;
+import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -32,6 +33,7 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.Flags;
+import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.PackageUserKey;
import java.lang.ref.WeakReference;
@@ -41,6 +43,8 @@
@WorkerThread
public class InstallSessionTracker extends PackageInstaller.SessionCallback {
+ public static final String TAG = "InstallSessionTracker";
+
// Lazily initialized
private SparseArray<PackageUserKey> mActiveSessions = null;
@@ -75,6 +79,11 @@
}
SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback);
if (sessionInfo != null) {
+ FileLog.d(TAG, "onCreated: Install session created for"
+ + " appPackageName=" + sessionInfo.getAppPackageName()
+ + ", sessionId=" + sessionInfo.getSessionId()
+ + ", appIcon=" + sessionInfo.getAppIcon()
+ + ", appLabel=" + sessionInfo.getAppLabel());
callback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo));
}
@@ -102,6 +111,10 @@
activeSessions.remove(sessionId);
if (key != null && key.mPackageName != null) {
+ FileLog.d(TAG, "onFinished: active install session finished for"
+ + " appPackageName=" + key.mPackageName
+ + ", sessionId=" + sessionId
+ + ", success=" + success);
String packageName = key.mPackageName;
PackageInstallInfo info = PackageInstallInfo.fromState(
success ? STATUS_INSTALLED : STATUS_FAILED,
@@ -141,6 +154,11 @@
}
SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId, helper, callback);
if (sessionInfo != null) {
+ Log.d(TAG, "onBadgingChanged: badging info changed for"
+ + " appPackageName=" + sessionInfo.getAppPackageName()
+ + ", sessionId=" + sessionInfo.getSessionId()
+ + ", appIcon=" + sessionInfo.getAppIcon()
+ + ", appLabel=" + sessionInfo.getAppLabel());
helper.tryQueuePromiseAppIcon(sessionInfo);
}
}
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index fa17b7b..63648dd 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -20,6 +20,9 @@
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
+import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.ALL_APPS_SCROLLER;
+import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.WIDGET_SCROLLER;
+
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
@@ -40,11 +43,15 @@
import android.view.WindowInsets;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.FastScrollRecyclerView;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.LetterListTextView;
import com.android.launcher3.graphics.FastScrollThumbDrawable;
import com.android.launcher3.util.Themes;
@@ -55,6 +62,19 @@
* The track and scrollbar that shows when you scroll the list.
*/
public class RecyclerViewFastScroller extends View {
+
+ /** FastScrollerLocation describes what RecyclerView the fast scroller is dedicated to. */
+ public enum FastScrollerLocation {
+ UNKNOWN_SCROLLER(0),
+ ALL_APPS_SCROLLER(1),
+ WIDGET_SCROLLER(2);
+
+ public final int location;
+
+ FastScrollerLocation(int location) {
+ this.location = location;
+ }
+ }
private static final String TAG = "RecyclerViewFastScroller";
private static final boolean DEBUG = false;
private static final int FASTSCROLL_THRESHOLD_MILLIS = 40;
@@ -106,6 +126,8 @@
private final Point mThumbDrawOffset = new Point();
private final Paint mTrackPaint;
+ private final int mThumbColor;
+ private final int mThumbLetterScrollerColor;
private float mLastTouchY;
private boolean mIsDragging;
@@ -139,6 +161,7 @@
private int mDownX;
private int mDownY;
private int mLastY;
+ private FastScrollerLocation mFastScrollerLocation;
public RecyclerViewFastScroller(Context context) {
this(context, null);
@@ -151,13 +174,16 @@
public RecyclerViewFastScroller(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ mFastScrollerLocation = FastScrollerLocation.UNKNOWN_SCROLLER;
mTrackPaint = new Paint();
mTrackPaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
+ mThumbColor = Themes.getColorAccent(context);
+ mThumbLetterScrollerColor = Themes.getAttrColor(context, R.attr.materialColorSurfaceBright);
mThumbPaint = new Paint();
mThumbPaint.setAntiAlias(true);
- mThumbPaint.setColor(Themes.getColorAccent(context));
+ mThumbPaint.setColor(mThumbColor);
mThumbPaint.setStyle(Paint.Style.FILL);
Resources res = getResources();
@@ -334,6 +360,18 @@
animatePopupVisibility(!TextUtils.isEmpty(sectionName));
mLastTouchY = boundedY;
setThumbOffsetY((int) mLastTouchY);
+ updateFastScrollerLetterList(y);
+ }
+
+ private void updateFastScrollerLetterList(int y) {
+ if (!shouldUseLetterFastScroller()) {
+ return;
+ }
+ ConstraintLayout mLetterList = mRv.getLetterList();
+ for (int i = 0; i < mLetterList.getChildCount(); i++) {
+ LetterListTextView currentLetter = (LetterListTextView) mLetterList.getChildAt(i);
+ currentLetter.animateBasedOnYPosition(y + mTouchOffsetY);
+ }
}
/** End any active fast scrolling touch handling, if applicable. */
@@ -359,15 +397,35 @@
mThumbDrawOffset.set(getWidth() / 2, mRv.getScrollBarTop());
// Draw the track
float halfW = mWidth / 2;
- canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(),
- mWidth, mWidth, mTrackPaint);
-
- canvas.translate(0, mThumbOffsetY);
+ boolean useLetterFastScroller = shouldUseLetterFastScroller();
+ if (useLetterFastScroller) {
+ float translateX;
+ if (mIsDragging) {
+ // halfW * 3 is half circle.
+ translateX = halfW * 3;
+ } else {
+ translateX = halfW * 5;
+ }
+ canvas.translate(translateX, mThumbOffsetY);
+ } else {
+ canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(),
+ mWidth, mWidth, mTrackPaint);
+ canvas.translate(0, mThumbOffsetY);
+ }
mThumbDrawOffset.y += mThumbOffsetY;
+
+ /* Draw half circle */
halfW += mThumbPadding;
float r = getScrollThumbRadius();
- mThumbBounds.set(-halfW, 0, halfW, mThumbHeight);
- canvas.drawRoundRect(mThumbBounds, r, r, mThumbPaint);
+ if (useLetterFastScroller) {
+ mThumbPaint.setColor(mThumbLetterScrollerColor);
+ mThumbBounds.set(0, 0, 0, mThumbHeight);
+ canvas.drawCircle(-halfW, halfW, r * 2, mThumbPaint);
+ } else {
+ mThumbPaint.setColor(mThumbColor);
+ mThumbBounds.set(-halfW, 0, halfW, mThumbHeight);
+ canvas.drawRoundRect(mThumbBounds, r, r, mThumbPaint);
+ }
mThumbBounds.roundOut(SYSTEM_GESTURE_EXCLUSION_RECT.get(0));
// swiping very close to the thumb area (not just within it's bound)
// will also prevent back gesture
@@ -380,6 +438,11 @@
canvas.restoreToCount(saveCount);
}
+ boolean shouldUseLetterFastScroller() {
+ return Flags.letterFastScroller()
+ && getScrollerLocation() == FastScrollerLocation.ALL_APPS_SCROLLER;
+ }
+
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mSystemGestureInsets = insets.getSystemGestureInsets();
@@ -421,19 +484,25 @@
return isNearThumb(x, y);
}
- /**
- * Returns whether the specified x position is near the scroll bar.
- */
- public boolean isNearScrollBar(int x) {
- return x >= (getWidth() - mMaxWidth) / 2 - mScrollbarLeftOffsetTouchDelegate
- && x <= (getWidth() + mMaxWidth) / 2;
+ public FastScrollerLocation getScrollerLocation() {
+ return mFastScrollerLocation;
+ }
+
+ public void setFastScrollerLocation(@NonNull FastScrollerLocation location) {
+ mFastScrollerLocation = location;
}
private void animatePopupVisibility(boolean visible) {
if (mPopupVisible != visible) {
mPopupVisible = visible;
- mPopupView.animate().cancel();
- mPopupView.animate().alpha(visible ? 1f : 0f).setDuration(visible ? 200 : 150).start();
+ if (shouldUseLetterFastScroller()) {
+ mRv.getLetterList().animate().alpha(visible ? 1f : 0f)
+ .setDuration(visible ? 200 : 150).start();
+ } else {
+ mPopupView.animate().cancel();
+ mPopupView.animate().alpha(visible ? 1f : 0f)
+ .setDuration(visible ? 200 : 150).start();
+ }
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 2af8e6f..c8ad564 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.WIDGET_SCROLLER;
import android.animation.Animator;
import android.content.Context;
@@ -119,7 +120,7 @@
WidgetsRecyclerView searchRecyclerView =
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
if (mIsInSearchMode && searchRecyclerView != null) {
- searchRecyclerView.bindFastScrollbar(mFastScroller);
+ searchRecyclerView.bindFastScrollbar(mFastScroller, WIDGET_SCROLLER);
}
}
@@ -276,7 +277,7 @@
}
private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
- recyclerView.bindFastScrollbar(mFastScroller);
+ recyclerView.bindFastScrollbar(mFastScroller, WIDGET_SCROLLER);
if (mCurrentWidgetsRecyclerView != recyclerView) {
// Only reset the scroll position & expanded apps if the currently shown recycler view
// has been updated.
@@ -290,10 +291,10 @@
protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
// The first item is always an empty space entry. Look for any more items.
boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
- adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
if (adapterHolder.mAdapterType == AdapterHolder.SEARCH) {
mNoWidgetsView.setText(R.string.no_search_results);
+ adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
} else if (adapterHolder.mAdapterType == AdapterHolder.WORK
&& mUserCache.getUserProfiles().stream()
.filter(userHandle -> mUserCache.getUserInfo(userHandle).isWork())
@@ -556,6 +557,8 @@
mNoWidgetsView.setVisibility(GONE);
} else {
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE);
+ mAdapters.get(getCurrentAdapterHolderType()).mWidgetsRecyclerView.setVisibility(
+ VISIBLE);
// Visibility of recommended widgets, recycler views and headers are handled in methods
// below.
post(this::onRecommendedWidgetsBound);
@@ -1057,7 +1060,7 @@
mWidgetsRecyclerView.setClipToOutline(true);
mWidgetsRecyclerView.setClipChildren(false);
mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
- mWidgetsRecyclerView.bindFastScrollbar(mFastScroller);
+ mWidgetsRecyclerView.bindFastScrollbar(mFastScroller, WIDGET_SCROLLER);
mWidgetsRecyclerView.setItemAnimator(isTwoPane() ? null : mWidgetsListItemAnimator);
mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
if (!isTwoPane()) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 2653514..d329674 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -461,6 +461,13 @@
if (!isWidgetAvailable) {
mRightPane.removeAllViews();
mRightPane.addView(mNoWidgetsView);
+ // with no widgets message, no header is selected on left
+ if (mSuggestedWidgetsPackageUserKey != null
+ && mSuggestedWidgetsPackageUserKey.equals(mSelectedHeader)
+ && mSuggestedWidgetsHeader != null) {
+ mSuggestedWidgetsHeader.setExpanded(false);
+ }
+ mSelectedHeader = null;
}
super.updateRecyclerViewVisibility(adapterHolder);
}
diff --git a/tests/Android.bp b/tests/Android.bp
index 9945570..1fa6e05 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -160,7 +160,7 @@
}
filegroup {
- name: "launcher-testing-helpers-multivalent",
+ name: "launcher-testing-helpers-robo",
srcs: [
"src/**/*.java",
"src/**/*.kt",
@@ -183,7 +183,7 @@
filegroup {
name: "launcher-testing-helpers",
srcs: [
- ":launcher-testing-helpers-multivalent",
+ ":launcher-testing-helpers-robo",
"src/**/RoboApiWrapper.kt",
],
}
@@ -195,7 +195,7 @@
":launcher3-robo-src",
// Test util classes
- ":launcher-testing-helpers-multivalent",
+ ":launcher-testing-helpers-robo",
":launcher-testing-shared",
],
exclude_srcs: [
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt
index 4cecb5a..bcb9191 100644
--- a/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt
@@ -21,6 +21,7 @@
import android.view.View
import android.view.ViewGroup
import com.android.launcher3.CellLayout
+import com.android.launcher3.Utilities
import com.android.launcher3.Workspace
import com.android.launcher3.util.CellAndSpan
import com.android.launcher3.widget.LauncherAppWidgetHostView
@@ -54,7 +55,7 @@
return view as LauncherAppWidgetHostView
}
- fun getCellTopLeftRelativeToCellLayout(
+ fun getCellTopLeftRelativeToWorkspace(
workspace: Workspace<*>,
cellAndSpan: CellAndSpan
): Point {
@@ -67,6 +68,8 @@
cellAndSpan.spanY,
target
)
- return Point(target.left, target.top)
+ val point = floatArrayOf(target.left.toFloat(), target.top.toFloat())
+ Utilities.getDescendantCoordRelativeToAncestor(cellLayout, workspace, point, false)
+ return Point(point[0].toInt(), point[1].toInt())
}
}