Merge "Using resource override instead of code-swap for ApiWrapper" into main
diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml
index f3ff6ce..143761f 100644
--- a/quickstep/res/drawable/bg_overview_clear_all_button.xml
+++ b/quickstep/res/drawable/bg_overview_clear_all_button.xml
@@ -20,7 +20,7 @@
<item>
<shape android:shape="rectangle"
android:tint="?colorButtonNormal">
- <corners android:radius="24dp" />
+ <corners android:radius="@dimen/recents_clear_all_outline_radius" />
<solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
</shape>
</item>
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index da94c3a..3380ea4 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -17,10 +17,12 @@
<com.android.quickstep.views.ClearAllButton
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
style="@style/OverviewClearAllButton"
android:id="@+id/clear_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/recents_clear_all"
android:textColor="?androidprv:attr/materialColorOnSurface"
+ launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
android:textSize="14sp" />
\ No newline at end of file
diff --git a/quickstep/res/values/attrs.xml b/quickstep/res/values/attrs.xml
index 7288774..ccc7f18 100644
--- a/quickstep/res/values/attrs.xml
+++ b/quickstep/res/values/attrs.xml
@@ -30,6 +30,11 @@
<attr name="hoverBorderColor" format="color" />
</declare-styleable>
+ <declare-styleable name="ClearAllButton">
+ <!-- focus border color for overview clear all button views -->
+ <attr name="focusBorderColor" />
+ </declare-styleable>
+
<!--
Gesture nav edu specific attributes. These attributes are used to customize Gesture nav edu
view lottie animation colors in XML files.
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index af1ab99..00dc7cd 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -98,6 +98,8 @@
<dimen name="default_task_dismiss_drag_velocity_grid_focus_task">5dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
+ <dimen name="recents_clear_all_outline_radius">24dp</dimen>
+ <dimen name="recents_clear_all_outline_padding">2dp</dimen>
<!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
loading full resolution screenshots. -->
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index eb9c5f0..71855eb 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -26,6 +26,8 @@
<string name="recent_task_option_pin">Pin</string>
<!-- Title for an option to enter freeform mode for a given app -->
<string name="recent_task_option_freeform">Freeform</string>
+ <!-- Title for an option to enter desktop windowing mode for a given app -->
+ <string name="recent_task_option_desktop">Desktop</string>
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">No recent items</string>
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index 10733fb..64fe30c 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -56,6 +56,11 @@
systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
}
+ /** Launch desktop tasks from recents view */
+ fun moveToDesktop(taskId: Int) {
+ systemUiProxy.moveToDesktop(taskId)
+ }
+
private class RemoteDesktopLaunchTransitionRunner(
private val desktopTaskView: DesktopTaskView,
private val stateManager: StateManager<*>,
@@ -99,8 +104,7 @@
finishCallback: IRemoteTransitionFinishedCallback
) {}
- override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
- }
+ override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {}
}
companion object {
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 672bd1d..1c5a75d 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -499,7 +499,7 @@
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
pinPrediction(mItemInfo);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index e4f9ba5..5d418fa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -443,7 +443,8 @@
LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
// All Apps action is unrelated to navbar unification, so we only need to check DP.
- mAllAppsActionManager.setTaskbarPresent(dp != null && dp.isTaskbarPresent);
+ final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
+ mAllAppsActionManager.setTaskbarPresent(isLargeScreenTaskbar);
destroyExistingTaskbar();
@@ -467,6 +468,7 @@
}
mSharedState.startTaskbarVariantIsTransient =
DisplayController.isTransientTaskbar(mTaskbarActivityContext);
+ mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
mTaskbarActivityContext.init(mSharedState);
if (mActivity != null) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 99937f8..6ceec3e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -96,7 +96,7 @@
mAllAppsCallbacks.onAllAppsTransitionStart(true);
if (!animate) {
mAllAppsCallbacks.onAllAppsTransitionEnd(true);
- mTranslationShift = TRANSLATION_SHIFT_OPENED;
+ setTranslationShift(TRANSLATION_SHIFT_OPENED);
return;
}
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
new file mode 100644
index 0000000..8c71d92
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.view.View
+import com.android.launcher3.AbstractFloatingViewHelper
+import com.android.launcher3.BaseDraggingActivity
+import com.android.launcher3.R
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.popup.SystemShortcut
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.window.flags.Flags
+
+/** A menu item, "Desktop", that allows the user to bring the current app into Desktop Windowing. */
+class DesktopSystemShortcut(
+ activity: BaseDraggingActivity,
+ private val mTaskContainer: TaskIdAttributeContainer,
+ abstractFloatingViewHelper: AbstractFloatingViewHelper
+) :
+ SystemShortcut<BaseDraggingActivity>(
+ R.drawable.ic_caption_desktop_button_foreground,
+ R.string.recent_task_option_desktop,
+ activity,
+ mTaskContainer.itemInfo,
+ mTaskContainer.taskView,
+ abstractFloatingViewHelper
+ ) {
+ override fun onClick(view: View) {
+ dismissTaskMenuView()
+ val recentsView = mTarget!!.getOverviewPanel<RecentsView<*, *>>()
+ recentsView.moveTaskToDesktop(mTaskContainer) {
+ mTarget.statsLogManager
+ .logger()
+ .withItemInfo(mTaskContainer.itemInfo)
+ .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP)
+ }
+ }
+
+ companion object {
+ /** Creates a factory for creating Desktop system shorcuts. */
+ @JvmOverloads
+ fun createFactory(
+ abstractFloatingViewHelper: AbstractFloatingViewHelper = AbstractFloatingViewHelper()
+ ): TaskShortcutFactory {
+ return object : TaskShortcutFactory {
+ override fun getShortcuts(
+ activity: BaseDraggingActivity,
+ taskContainer: TaskIdAttributeContainer
+ ): List<DesktopSystemShortcut>? {
+ return if (!Flags.enableDesktopWindowingMode()) null
+ else if (!taskContainer.task.isDockable) null
+ else
+ listOf(
+ DesktopSystemShortcut(
+ activity,
+ taskContainer,
+ abstractFloatingViewHelper
+ )
+ )
+ }
+
+ override fun showForSplitscreen() = true
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 43e564c..4b4f914 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -67,6 +67,7 @@
import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.AssistStateManager;
import com.android.quickstep.util.GestureExclusionManager;
import com.android.quickstep.util.GestureExclusionManager.ExclusionListener;
import com.android.quickstep.util.NavBarPosition;
@@ -96,7 +97,7 @@
private final DisplayController mDisplayController;
private final GestureExclusionManager mExclusionManager;
-
+ private final AssistStateManager mAssistStateManager;
private final RotationTouchHelper mRotationTouchHelper;
private final TaskStackChangeListener mPipListener;
@@ -147,6 +148,7 @@
mContext = context;
mDisplayController = DisplayController.INSTANCE.get(context);
mExclusionManager = exclusionManager;
+ mAssistStateManager = AssistStateManager.INSTANCE.get(context);
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
if (isInstanceForTouches) {
@@ -587,8 +589,8 @@
: QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- if (DeviceConfigWrapper.get().getCustomLpnhThresholds()) {
- float customSlopMultiplier = DeviceConfigWrapper.get().getLpnhSlopPercentage() / 100f;
+ if (mAssistStateManager.getLPNHCustomSlopMultiplier().isPresent()) {
+ float customSlopMultiplier = mAssistStateManager.getLPNHCustomSlopMultiplier().get();
return customSlopMultiplier * slopMultiplier * touchSlop;
} else {
return slopMultiplier * touchSlop;
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index ab609fd..30bb863 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -836,7 +836,9 @@
*/
public void setBubbleBarBounds(Rect bubbleBarBounds) {
try {
- mBubbles.setBubbleBarBounds(bubbleBarBounds);
+ if (mBubbles != null) {
+ mBubbles.setBubbleBarBounds(bubbleBarBounds);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Failed call setBubbleBarBounds");
}
@@ -1473,6 +1475,17 @@
}
}
+ /** Call shell to move a task with given `taskId` to desktop */
+ public void moveToDesktop(int taskId) {
+ if (mDesktopMode != null) {
+ try {
+ mDesktopMode.moveToDesktop(taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call moveToDesktop", e);
+ }
+ }
+ }
+
//
// Unfold transition
//
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index cc582d1..8a4989b 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -136,6 +136,7 @@
TaskShortcutFactory.PIN,
TaskShortcutFactory.INSTALL,
TaskShortcutFactory.FREE_FORM,
+ DesktopSystemShortcut.Companion.createFactory(),
TaskShortcutFactory.WELLBEING,
TaskShortcutFactory.SAVE_APP_PAIR
};
@@ -315,7 +316,7 @@
@Override
public void onClick(View view) {
saveScreenshot(mThumbnailView.getTaskView().getTask());
- dismissTaskMenuView(mActivity);
+ dismissTaskMenuView();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 147a3e2..5e970f1 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -145,7 +145,7 @@
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
((RecentsView) mTarget.getOverviewPanel())
.getSplitSelectController().getAppPairsController().saveAppPair(mTaskView);
}
@@ -174,7 +174,7 @@
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
RecentsView rv = mTarget.getOverviewPanel();
rv.switchToScreenshot(() -> {
rv.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> {
@@ -420,7 +420,7 @@
SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(
mTaskView.getTask().key.id);
}
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
.log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
}
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index a1fdbbb..87d5138 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -58,7 +58,7 @@
}
/** Get the Launcher overridden long press touch slop multiplier to trigger Assistant. */
- public Optional<Long> getLPNHCustomSlopMultiplier() {
+ public Optional<Float> getLPNHCustomSlopMultiplier() {
return Optional.empty();
}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index 32ef904..acda2e1 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -17,15 +17,28 @@
package com.android.quickstep.views;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.widget.Button;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
+import com.android.launcher3.R;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.util.BorderAnimator;
+
+import kotlin.Unit;
public class ClearAllButton extends Button {
@@ -71,11 +84,71 @@
private float mScrollOffsetPrimary;
private int mSidePadding;
+ private int mOutlinePadding;
+ private boolean mBorderEnabled;
+ @Nullable
+ private final BorderAnimator mFocusBorderAnimator;
public ClearAllButton(Context context, AttributeSet attrs) {
super(context, attrs);
mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
mActivity = StatefulActivity.fromContext(context);
+
+ if (Flags.enableFocusOutline()) {
+ TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
+ R.styleable.ClearAllButton);
+ Resources resources = getResources();
+ mOutlinePadding = resources.getDimensionPixelSize(
+ R.dimen.recents_clear_all_outline_padding);
+ mFocusBorderAnimator =
+ BorderAnimator.createSimpleBorderAnimator(
+ /* borderRadiusPx= */ resources.getDimensionPixelSize(
+ R.dimen.recents_clear_all_outline_radius),
+ /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_border_width),
+ /* boundsBuilder= */ this::updateBorderBounds,
+ /* targetView= */ this,
+ /* borderColor= */ styledAttrs.getColor(
+ R.styleable.ClearAllButton_focusBorderColor,
+ DEFAULT_BORDER_COLOR));
+ styledAttrs.recycle();
+ } else {
+ mFocusBorderAnimator = null;
+ }
+ }
+
+ private Unit updateBorderBounds(@NonNull Rect bounds) {
+ bounds.set(0, 0, getWidth(), getHeight());
+ // Make the value negative to form a padding between button and outline
+ bounds.inset(-mOutlinePadding, -mOutlinePadding);
+ return Unit.INSTANCE;
+ }
+
+ @Override
+ public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ if (mFocusBorderAnimator != null && mBorderEnabled) {
+ mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
+ }
+ }
+
+ /**
+ * Enable or disable showing border on focus change
+ */
+ public void setBorderEnabled(boolean enabled) {
+ mBorderEnabled = enabled;
+ if (mFocusBorderAnimator != null) {
+ mFocusBorderAnimator.setBorderVisibility(/* visible= */
+ enabled && isFocused(), /* animated= */true);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mFocusBorderAnimator != null) {
+ mFocusBorderAnimator.drawBorder(canvas);
+ }
+ super.draw(canvas);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index eca70b7..adb9d2b 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -163,6 +163,7 @@
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.TranslateEdgeEffect;
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.ViewPool;
@@ -1454,6 +1455,7 @@
TaskView taskView = requireTaskViewAt(i);
taskView.setBorderEnabled(enabled);
}
+ mClearAllButton.setBorderEnabled(enabled);
}
/**
@@ -2569,6 +2571,7 @@
*/
public void onGestureAnimationStart(
Task[] runningTasks, RotationTouchHelper rotationTouchHelper) {
+ Log.d(TAG, "onGestureAnimationStart");
mActiveGestureRunningTasks = runningTasks;
// This needs to be called before the other states are set since it can create the task view
if (mOrientationState.setGestureActive(true)) {
@@ -4293,7 +4296,8 @@
* Updates {@link RecentsOrientedState}'s cached RecentsView rotation.
*/
public void updateRecentsRotation() {
- final int rotation = mActivity.getDisplay().getRotation();
+ final int rotation = TraceHelper.allowIpcs(
+ "RecentsView.updateRecentsRotation", () -> mActivity.getDisplay().getRotation());
mOrientationState.setRecentsRotation(rotation);
}
@@ -5413,6 +5417,8 @@
* Called when a running recents animation has finished or canceled.
*/
public void onRecentsAnimationComplete() {
+ Log.d(TAG, "onRecentsAnimationComplete - mRecentsAnimationController: "
+ + mRecentsAnimationController);
// At this point, the recents animation is not running and if the animation was canceled
// by a display rotation then reset this state to show the screenshot
setRunningTaskViewShowScreenshot(true);
@@ -6200,6 +6206,27 @@
UI_HELPER_EXECUTOR.post(() -> mActivity.setLocusContext(id, Bundle.EMPTY));
}
+ /**
+ * Moves the provided task into desktop mode, and invoke {@code successCallback} if succeeded.
+ */
+ public void moveTaskToDesktop(TaskIdAttributeContainer taskContainer,
+ Runnable successCallback) {
+ if (!enableDesktopWindowingMode()) {
+ return;
+ }
+ switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false,
+ () -> moveTaskToDesktopInternal(taskContainer, successCallback)));
+ }
+
+ private void moveTaskToDesktopInternal(TaskIdAttributeContainer taskContainer,
+ Runnable successCallback) {
+ if (mDesktopRecentsTransitionController == null) {
+ return;
+ }
+ mDesktopRecentsTransitionController.moveToDesktop(taskContainer.getTask().key.id);
+ successCallback.run();
+ }
+
public interface TaskLaunchListener {
void onTaskLaunched();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index cec0982..5338d81 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -423,7 +423,8 @@
mCurrentFullscreenParams = new FullscreenDrawParams(context);
mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
- boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get();
+ boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
+ || Flags.enableFocusOutline();
boolean cursorHoverStatesEnabled = enableCursorHoverStates();
setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled);
@@ -486,7 +487,11 @@
return getItemInfo(mTask);
}
- protected WorkspaceItemInfo getItemInfo(@Nullable Task task) {
+ /**
+ * Builds proto for logging
+ */
+ @VisibleForTesting
+ public WorkspaceItemInfo getItemInfo(@Nullable Task task) {
WorkspaceItemInfo stubInfo = new WorkspaceItemInfo();
stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
@@ -812,12 +817,18 @@
private void onClick(View view) {
if (getTask() == null) {
+ Log.d("b/310064698", "onClick - task is null");
return;
}
if (confirmSecondSplitSelectApp()) {
+ Log.d("b/310064698", mTask + " - onClick - split select is active");
return;
}
- launchTasks();
+ RunnableList callbackList = launchTasks();
+ Log.d("b/310064698", mTask + " - onClick - callbackList: " + callbackList);
+ if (callbackList != null) {
+ callbackList.add(() -> Log.d("b/310064698", mTask + " - onClick - launchCompleted"));
+ }
mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_TAP);
}
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
new file mode 100644
index 0000000..b90839d
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.content.ComponentName
+import android.content.Intent
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.AbstractFloatingViewHelper
+import com.android.launcher3.Launcher
+import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.quickstep.views.LauncherRecentsView
+import com.android.quickstep.views.TaskView
+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.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/** Test for DesktopSystemShortcut */
+class DesktopSystemShortcutTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+ private val launcher: Launcher = mock()
+ private val statsLogManager: StatsLogManager = mock()
+ private val statsLogger: StatsLogManager.StatsLogger = mock()
+ private val recentsView: LauncherRecentsView = mock()
+ private val taskView: TaskView = mock()
+ private val workspaceItemInfo: WorkspaceItemInfo = mock()
+ private val abstractFloatingViewHelper: AbstractFloatingViewHelper = mock()
+ private val factory: TaskShortcutFactory =
+ DesktopSystemShortcut.createFactory(abstractFloatingViewHelper)
+
+ @Test
+ fun createDesktopTaskShortcutFactory_featureOff() {
+ setFlagsRule.disableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+
+ val task =
+ Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ isDockable = true
+ }
+ val taskContainer =
+ taskView.TaskIdAttributeContainer(
+ task,
+ null,
+ null,
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+ )
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ fun createDesktopTaskShortcutFactory_undockable() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+
+ val task =
+ Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ isDockable = false
+ }
+ val taskContainer =
+ taskView.TaskIdAttributeContainer(
+ task,
+ null,
+ null,
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+ )
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).isNull()
+ }
+
+ @Test
+ fun desktopSystemShortcutClicked() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+
+ val task =
+ Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ isDockable = true
+ }
+ val taskContainer =
+ taskView.TaskIdAttributeContainer(
+ task,
+ null,
+ null,
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+ )
+
+ whenever(launcher.getOverviewPanel<LauncherRecentsView>()).thenReturn(recentsView)
+ whenever(launcher.statsLogManager).thenReturn(statsLogManager)
+ whenever(statsLogManager.logger()).thenReturn(statsLogger)
+ whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger)
+ whenever(taskView.getItemInfo(task)).thenReturn(workspaceItemInfo)
+ whenever(recentsView.moveTaskToDesktop(any(), any())).thenAnswer {
+ val successCallback = it.getArgument<Runnable>(1)
+ successCallback.run()
+ }
+
+ val shortcuts = factory.getShortcuts(launcher, taskContainer)
+ assertThat(shortcuts).hasSize(1)
+ assertThat(shortcuts!!.first()).isInstanceOf(DesktopSystemShortcut::class.java)
+
+ val desktopShortcut = shortcuts.first() as DesktopSystemShortcut
+
+ desktopShortcut.onClick(taskView)
+
+ val allTypesExceptRebindSafe =
+ AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv()
+ verify(abstractFloatingViewHelper).closeOpenViews(launcher, true, allTypesExceptRebindSafe)
+ verify(recentsView).moveTaskToDesktop(eq(taskContainer), any())
+ verify(statsLogger).withItemInfo(workspaceItemInfo)
+ verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP)
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
index b31f470..e4caa26 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
@@ -16,13 +16,9 @@
package com.android.quickstep;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
-
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import org.junit.Before;
@@ -47,8 +43,6 @@
@Test
@NavigationModeSwitch
- // Stress tests are long. We permanently demote them from presubmit to match the presubmit SLO.
- @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
public void testStressPressHome() {
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
// Destroy Launcher activity.
@@ -61,8 +55,6 @@
@Test
@NavigationModeSwitch
- // Stress tests are long. We permanently demote them from presubmit to match the presubmit SLO.
- @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
public void testStressSwipeToOverview() {
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
// Destroy Launcher activity.
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index e7b88dc..4ccf3db 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -278,18 +278,7 @@
public static void closeOpenViews(ActivityContext activity, boolean animate,
@FloatingViewType int type) {
- BaseDragLayer dragLayer = activity.getDragLayer();
- // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
- // and will be one of the last views.
- for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
- View child = dragLayer.getChildAt(i);
- if (child instanceof AbstractFloatingView) {
- AbstractFloatingView abs = (AbstractFloatingView) child;
- if (abs.isOfType(type)) {
- abs.close(animate);
- }
- }
- }
+ new AbstractFloatingViewHelper().closeOpenViews(activity, animate, type);
}
public static void closeAllOpenViews(ActivityContext activity, boolean animate) {
diff --git a/src/com/android/launcher3/AbstractFloatingViewHelper.kt b/src/com/android/launcher3/AbstractFloatingViewHelper.kt
new file mode 100644
index 0000000..0bfbc6e
--- /dev/null
+++ b/src/com/android/launcher3/AbstractFloatingViewHelper.kt
@@ -0,0 +1,38 @@
+/*
+ * 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
+
+import com.android.launcher3.AbstractFloatingView.FloatingViewType
+import com.android.launcher3.views.ActivityContext
+
+/**
+ * Helper class for manaing AbstractFloatingViews which shows a floating UI on top of the launcher
+ * UI.
+ */
+class AbstractFloatingViewHelper {
+ fun closeOpenViews(activity: ActivityContext, animate: Boolean, @FloatingViewType type: Int) {
+ val dragLayer = activity.getDragLayer()
+ // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
+ // and will be one of the last views.
+ for (i in dragLayer.getChildCount() - 1 downTo 0) {
+ val child = dragLayer.getChildAt(i)
+ if (child is AbstractFloatingView && child.isOfType(type)) {
+ child.close(animate)
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index e8f8ae2..441bbb5 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -218,6 +218,9 @@
@UiEvent(doc = "User tapped on free form icon on a task menu.")
LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP(519),
+ @UiEvent(doc = "User tapped on desktop icon on a task menu.")
+ LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP(1706),
+
@UiEvent(doc = "User tapped on pause app system shortcut.")
LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 0c5a0ec..f56d732 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -22,6 +22,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AbstractFloatingViewHelper;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.SecondaryDropTarget;
@@ -61,23 +62,23 @@
protected final ItemInfo mItemInfo;
protected final View mOriginalView;
+ private final AbstractFloatingViewHelper mAbstractFloatingViewHelper;
+
public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo,
View originalView) {
+ this(iconResId, labelResId, target, itemInfo, originalView,
+ new AbstractFloatingViewHelper());
+ }
+
+ public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo,
+ View originalView, AbstractFloatingViewHelper abstractFloatingViewHelper) {
mIconResId = iconResId;
mLabelResId = labelResId;
mAccessibilityActionId = labelResId;
mTarget = target;
mItemInfo = itemInfo;
mOriginalView = originalView;
- }
-
- public SystemShortcut(SystemShortcut<T> other) {
- mIconResId = other.mIconResId;
- mLabelResId = other.mLabelResId;
- mAccessibilityActionId = other.mAccessibilityActionId;
- mTarget = other.mTarget;
- mItemInfo = other.mItemInfo;
- mOriginalView = other.mOriginalView;
+ mAbstractFloatingViewHelper = abstractFloatingViewHelper;
}
public void setIconAndLabelFor(View iconView, TextView labelView) {
@@ -178,7 +179,7 @@
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
Rect sourceBounds = Utilities.getViewBounds(view);
new PackageManagerHelper(view.getContext()).startDetailsActivityForInfo(
mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
@@ -324,7 +325,7 @@
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
mTarget.getStatsLogManager().logger()
.withItemInfo(mItemInfo)
.log(LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP);
@@ -367,7 +368,7 @@
@Override
public void onClick(View view) {
- dismissTaskMenuView(mTarget);
+ dismissTaskMenuView();
SecondaryDropTarget.performUninstall(view.getContext(), mComponentName, mItemInfo);
mTarget.getStatsLogManager()
.logger()
@@ -376,8 +377,8 @@
}
}
- public static <T extends ActivityContext> void dismissTaskMenuView(T activity) {
- AbstractFloatingView.closeOpenViews(activity, true,
+ protected void dismissTaskMenuView() {
+ mAbstractFloatingViewHelper.closeOpenViews(mTarget, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
}
}
diff --git a/tests/src/com/android/launcher3/AbstractFloatingViewHelperTest.kt b/tests/src/com/android/launcher3/AbstractFloatingViewHelperTest.kt
new file mode 100644
index 0000000..7ff544d
--- /dev/null
+++ b/tests/src/com/android/launcher3/AbstractFloatingViewHelperTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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
+
+import android.view.View
+import com.android.launcher3.dragndrop.DragLayer
+import com.android.launcher3.views.ActivityContext
+import org.junit.Before
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
+
+/** Test for AbstractFloatingViewHelper */
+class AbstractFloatingViewHelperTest {
+ private val activityContext: ActivityContext = mock()
+ private val dragLayer: DragLayer = mock()
+ private val view: View = mock()
+ private val folderView: AbstractFloatingView = mock()
+ private val taskMenuView: AbstractFloatingView = mock()
+ private val abstractFloatingViewHelper = AbstractFloatingViewHelper()
+
+ @Before
+ fun setup() {
+ whenever(activityContext.dragLayer).thenReturn(dragLayer)
+ whenever(dragLayer.childCount).thenReturn(3)
+ whenever(dragLayer.getChildAt(0)).thenReturn(view)
+ whenever(dragLayer.getChildAt(1)).thenReturn(folderView)
+ whenever(dragLayer.getChildAt(2)).thenReturn(taskMenuView)
+ whenever(folderView.isOfType(any())).thenAnswer {
+ (it.getArgument<Int>(0) and AbstractFloatingView.TYPE_FOLDER) != 0
+ }
+ whenever(taskMenuView.isOfType(any())).thenAnswer {
+ (it.getArgument<Int>(0) and AbstractFloatingView.TYPE_TASK_MENU) != 0
+ }
+ }
+
+ @Test
+ fun closeOpenViews_all() {
+ abstractFloatingViewHelper.closeOpenViews(
+ activityContext,
+ true,
+ AbstractFloatingView.TYPE_ALL
+ )
+
+ verifyZeroInteractions(view)
+ verify(folderView).close(true)
+ verify(taskMenuView).close(true)
+ }
+
+ @Test
+ fun closeOpenViews_taskMenu() {
+ abstractFloatingViewHelper.closeOpenViews(
+ activityContext,
+ true,
+ AbstractFloatingView.TYPE_TASK_MENU
+ )
+
+ verifyZeroInteractions(view)
+ verify(folderView, never()).close(any())
+ verify(taskMenuView).close(true)
+ }
+
+ @Test
+ fun closeOpenViews_other() {
+ abstractFloatingViewHelper.closeOpenViews(
+ activityContext,
+ true,
+ AbstractFloatingView.TYPE_PIN_IME_POPUP
+ )
+
+ verifyZeroInteractions(view)
+ verify(folderView, never()).close(any())
+ verify(taskMenuView, never()).close(any())
+ }
+
+ @Test
+ fun closeOpenViews_both_animationOff() {
+ abstractFloatingViewHelper.closeOpenViews(
+ activityContext,
+ false,
+ AbstractFloatingView.TYPE_FOLDER or AbstractFloatingView.TYPE_TASK_MENU
+ )
+
+ verifyZeroInteractions(view)
+ verify(folderView).close(false)
+ verify(taskMenuView).close(false)
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index f01bdb3..fabe3f7 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -5,7 +5,6 @@
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.util.rule.FailureWatcher;
-import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -34,11 +33,7 @@
@Override
public Statement apply(Statement base, Description description) {
if (!TestHelpers.isInLauncherProcess()
- || description.getAnnotation(PortraitLandscape.class) == null
- // If running in presubmit, don't run in both orientations.
- // It's important to keep presubmits fast even if we will occasionally miss
- // regressions in presubmit.
- || TestStabilityRule.isPresubmit()) {
+ || description.getAnnotation(PortraitLandscape.class) == null) {
return base;
}