Merge "Cleanup LARGE_SCREEN_WIDGET_PICKER flag" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 9221f6b..874f862 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -34,3 +34,10 @@
description: "Enables new workspace grid calculations method."
bug: "241386436"
}
+
+flag {
+ name: "enable_overview_icon_menu"
+ namespace: "launcher"
+ description: "Enable updated overview icon and menu within task."
+ bug: "257950105"
+}
diff --git a/quickstep/res/drawable/ic_chevron_down.xml b/quickstep/res/drawable/ic_chevron_down.xml
new file mode 100644
index 0000000..77a8295
--- /dev/null
+++ b/quickstep/res/drawable/ic_chevron_down.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <target android:name="scaleGroup">
+ <aapt:attr name="android:animation">
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="scaleX"
+ android:valueFrom="1"
+ android:valueTo="-1" />
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="48dp"
+ android:height="48dp"
+ android:autoMirrored="true"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:viewportHeight="48"
+ android:viewportWidth="48">
+ <group
+ android:name="scaleGroup"
+ android:pivotX="24"
+ android:pivotY="24"
+ android:rotation="90">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18.75,36 L16.6,33.85 26.5,23.95 16.6,14.05 18.75,11.9 30.8,23.95Z" />
+ </group>
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/quickstep/res/layout/icon_app_chip_view.xml b/quickstep/res/layout/icon_app_chip_view.xml
new file mode 100644
index 0000000..87519a0
--- /dev/null
+++ b/quickstep/res/layout/icon_app_chip_view.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.quickstep.views.IconAppChipView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="false"
+ android:importantForAccessibility="no"
+ android:autoMirrored="true"
+ android:elevation="@dimen/task_thumbnail_icon_menu_elevation" >
+
+ <ImageView
+ android:id="@+id/icon_view_background"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/icon_menu_background"
+ android:importantForAccessibility="no" />
+
+ <com.android.quickstep.views.IconView
+ android:id="@+id/icon_view"
+ android:layout_width="@dimen/task_thumbnail_icon_size"
+ android:layout_height="@dimen/task_thumbnail_icon_size"
+ android:focusable="false"
+ android:importantForAccessibility="no" />
+
+ <TextView
+ android:id="@+id/icon_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:maxLines="1"
+ android:textAlignment="viewStart"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textSize="16sp"
+ android:importantForAccessibility="no" />
+
+ <ImageView
+ android:id="@+id/icon_arrow"
+ android:layout_width="@dimen/task_thumbnail_icon_menu_arrow_size"
+ android:layout_height="match_parent"
+ android:background="@drawable/icon_menu_arrow_background"
+ android:src="@drawable/ic_chevron_down"
+ android:importantForAccessibility="no" />
+</com.android.quickstep.views.IconAppChipView>
\ No newline at end of file
diff --git a/quickstep/res/layout/icon_view.xml b/quickstep/res/layout/icon_view.xml
new file mode 100644
index 0000000..a33066f
--- /dev/null
+++ b/quickstep/res/layout/icon_view.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.quickstep.views.IconView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 29c9992..823a86e 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -44,10 +44,9 @@
android:importantForAccessibility="no"
android:src="@drawable/ic_select_windows" />
- <com.android.quickstep.views.IconView
+ <ViewStub
android:id="@+id/icon"
- android:layout_width="@dimen/task_thumbnail_icon_size"
- android:layout_height="@dimen/task_thumbnail_icon_size"
- android:focusable="false"
- android:importantForAccessibility="no"/>
+ android:inflatedId="@id/icon"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content" />
</com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index 06f4d06..fe12bd3 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -45,11 +45,10 @@
android:layout_height="wrap_content"
android:visibility="gone" />
- <com.android.quickstep.views.IconView
+ <ViewStub
android:id="@+id/icon"
- android:layout_width="@dimen/task_thumbnail_icon_size"
- android:layout_height="@dimen/task_thumbnail_icon_size"
- android:focusable="false"
- android:importantForAccessibility="no" />
+ android:inflatedId="@id/icon"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content" />
</com.android.quickstep.views.DesktopTaskView>
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index 75ff626..d20afd3 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -66,17 +66,15 @@
android:importantForAccessibility="no"
android:src="@drawable/ic_select_windows" />
- <com.android.quickstep.views.IconView
+ <ViewStub
android:id="@+id/icon"
- android:layout_width="@dimen/task_thumbnail_icon_size"
- android:layout_height="@dimen/task_thumbnail_icon_size"
- android:focusable="false"
- android:importantForAccessibility="no"/>
+ android:inflatedId="@id/icon"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content" />
- <com.android.quickstep.views.IconView
+ <ViewStub
android:id="@+id/bottomRight_icon"
- android:layout_width="@dimen/task_thumbnail_icon_size"
- android:layout_height="@dimen/task_thumbnail_icon_size"
- android:focusable="false"
- android:importantForAccessibility="no"/>
+ android:inflatedId="@id/bottomRight_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content" />
</com.android.quickstep.views.GroupedTaskView>
\ No newline at end of file
diff --git a/quickstep/res/values-sw600dp/config.xml b/quickstep/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..b22cfc5
--- /dev/null
+++ b/quickstep/res/values-sw600dp/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<resources>
+ <!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
+ determines how many thumbnails will be fetched in the background. -->
+ <integer name="recentsThumbnailCacheSize">8</integer>
+</resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index aeb453c..aaa699b 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -44,6 +44,33 @@
<dimen name="overview_task_margin">16dp</dimen>
<!-- The horizontal space between tasks -->
<dimen name="overview_page_spacing">16dp</dimen>
+ <!-- The width of the thumbnail icon menu -->
+ <dimen name="task_thumbnail_icon_menu_min_width">132dp</dimen>
+ <!-- The width of the icon menu text -->
+ <dimen name="task_thumbnail_icon_menu_text_width">62dp</dimen>
+ <!-- The max width of the icon menu text -->
+ <dimen name="task_thumbnail_icon_menu_text_max_width">138dp</dimen>
+ <!-- The max width of the thumbnail icon menu -->
+ <dimen name="task_thumbnail_icon_menu_max_width">216dp</dimen>
+ <!-- The height of the thumbnail icon menu -->
+ <dimen name="task_thumbnail_icon_menu_min_height">36dp</dimen>
+ <!-- The max height of the thumbnail icon menu -->
+ <dimen name="task_thumbnail_icon_menu_max_height">52dp</dimen>
+ <!-- The size of the icon menu arrow -->
+ <dimen name="task_thumbnail_icon_menu_arrow_size">32dp</dimen>
+ <!-- The size of the icon menu arrow drawable -->
+ <dimen name="task_thumbnail_icon_menu_arrow_drawable_size">16dp</dimen>
+ <!-- The margin around the task icon menu -->
+ <dimen name="task_thumbnail_icon_menu_margin">12dp</dimen>
+ <!-- The space around the task icon arrow within the icon menu -->
+ <dimen name="task_thumbnail_icon_menu_arrow_margin">6dp</dimen>
+ <!-- The max space around the task icon within the icon menu -->
+ <dimen name="task_thumbnail_icon_menu_touch_max_margin">8dp</dimen>
+ <!-- The icon size for the icon menu -->
+ <dimen name="task_thumbnail_icon_menu_drawable_size">24dp</dimen>
+ <!-- The size of the icon menu's icon touch target -->
+ <dimen name="task_thumbnail_icon_menu_drawable_touch_size">36dp</dimen>
+ <dimen name="task_thumbnail_icon_menu_elevation">14dp</dimen>
<dimen name="task_icon_cache_default_icon_size">72dp</dimen>
<item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 841fc8f..ce644dc 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1850,7 +1850,7 @@
return null;
}
- current = (View) view.getParent();
+ current = (View) current.getParent();
}
return (T) current;
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index f82474a..1440498 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -36,6 +36,7 @@
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.model.data.ItemInfo;
@@ -126,6 +127,10 @@
int verticalPadding = getResources().getDimensionPixelSize(
R.dimen.all_apps_predicted_icon_vertical_padding);
int totalHeight = iconHeight + iconPadding + textHeight + verticalPadding * 2;
+ if (FeatureFlags.enableTwolineAllapps()) {
+ // Add extra textHeight to the existing total height.
+ totalHeight += textHeight;
+ }
return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
}
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
new file mode 100644
index 0000000..6fe007c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 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.desktop
+
+import android.app.IApplicationThread
+import android.os.IBinder
+import android.os.RemoteException
+import android.util.Log
+import android.view.SurfaceControl
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
+import android.window.RemoteTransition
+import android.window.TransitionInfo
+import com.android.launcher3.statehandlers.DepthController
+import com.android.launcher3.statemanager.StateManager
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.quickstep.SystemUiProxy
+import com.android.quickstep.TaskViewUtils
+import com.android.quickstep.views.DesktopTaskView
+import java.util.function.Consumer
+
+/** Manage recents related operations with desktop tasks */
+class DesktopRecentsTransitionController(
+ private val stateManager: StateManager<*>,
+ private val systemUiProxy: SystemUiProxy,
+ private val appThread: IApplicationThread,
+ private val depthController: DepthController?
+) {
+
+ /** Launch desktop tasks from recents view */
+ fun launchDesktopFromRecents(
+ desktopTaskView: DesktopTaskView,
+ callback: Consumer<Boolean>? = null
+ ) {
+ val animRunner =
+ RemoteDesktopLaunchTransitionRunner(
+ desktopTaskView,
+ stateManager,
+ depthController,
+ callback
+ )
+ val transition = RemoteTransition(animRunner, appThread, "RecentsToDesktop")
+ systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
+ }
+
+ private class RemoteDesktopLaunchTransitionRunner(
+ private val desktopTaskView: DesktopTaskView,
+ private val stateManager: StateManager<*>,
+ private val depthController: DepthController?,
+ private val successCallback: Consumer<Boolean>?
+ ) : IRemoteTransition.Stub() {
+
+ override fun startAnimation(
+ token: IBinder,
+ info: TransitionInfo,
+ t: SurfaceControl.Transaction,
+ finishCallback: IRemoteTransitionFinishedCallback
+ ) {
+ val errorHandlingFinishCallback = Runnable {
+ try {
+ finishCallback.onTransitionFinished(null /* wct */, null /* sct */)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed to call finish callback for desktop recents animation", e)
+ }
+ }
+
+ MAIN_EXECUTOR.execute {
+ TaskViewUtils.composeRecentsDesktopLaunchAnimator(
+ desktopTaskView,
+ stateManager,
+ depthController,
+ info,
+ t
+ ) {
+ errorHandlingFinishCallback.run()
+ successCallback?.accept(true)
+ }
+ }
+ }
+
+ override fun mergeAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ t: SurfaceControl.Transaction,
+ mergeTarget: IBinder,
+ finishCallback: IRemoteTransitionFinishedCallback
+ ) {}
+ }
+
+ companion object {
+ const val TAG = "DesktopRecentsTransitionController"
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index 3e1a6ae..a9d50b9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -40,6 +40,8 @@
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* A view that displays a recent task during a keyboard quick switch.
*/
@@ -96,17 +98,18 @@
Resources resources = mContext.getResources();
Preconditions.assertNotNull(mContent);
- mBorderAnimator = new BorderAnimator(
+ mBorderAnimator = BorderAnimator.createScalingBorderAnimator(
/* borderRadiusPx= */ resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_task_view_radius),
- /* borderColor= */ mBorderColor,
- /* borderAnimationParams= */ new BorderAnimator.ScalingParams(
- /* borderWidthPx= */ resources.getDimensionPixelSize(
+ /* borderWidthPx= */ resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ bounds -> bounds.set(
- 0, 0, getWidth(), getHeight()),
- /* targetView= */ this,
- /* contentView= */ mContent));
+ /* boundsBuilder= */ bounds -> {
+ bounds.set(0, 0, getWidth(), getHeight());
+ return Unit.INSTANCE;
+ },
+ /* targetView= */ this,
+ /* contentView= */ mContent,
+ /* borderColor= */ mBorderColor);
}
@Nullable
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index fa16b61..4b16019 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -88,10 +88,10 @@
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
import com.android.launcher3.util.DimensionUtils;
-import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.BaseDragLayer;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton;
@@ -144,6 +144,7 @@
private int mState;
private final TaskbarActivityContext mContext;
+ private final WindowManagerProxy mWindowManagerProxy;
private final FrameLayout mNavButtonsView;
private final LinearLayout mNavButtonContainer;
// Used for IME+A11Y buttons
@@ -190,6 +191,7 @@
private MultiValueAlpha mBackButtonAlpha;
private MultiValueAlpha mHomeButtonAlpha;
private FloatingRotationButton mFloatingRotationButton;
+ private ImageView mImeSwitcherButton;
// Variables for moving nav buttons to a separate window above IME
private boolean mAreNavButtonsInSeparateWindow = false;
@@ -198,10 +200,10 @@
this::onComputeInsetsForSeparateWindow;
private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
private ImageView mRecentsButton;
- private DisplayController mDisplayController;
public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
mContext = context;
+ mWindowManagerProxy = WindowManagerProxy.INSTANCE.get(mContext);
mNavButtonsView = navButtonsView;
mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
@@ -232,16 +234,14 @@
TaskbarManager.isPhoneMode(deviceProfile));
mNavButtonsView.getLayoutParams().height = p.y;
- mDisplayController = DisplayController.INSTANCE.get(mContext);
-
mIsImeRenderingNavButtons =
InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
if (!mIsImeRenderingNavButtons) {
// IME switcher
- View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
+ mImeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
mControllers.navButtonController, R.id.ime_switcher);
- mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
+ mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
flags -> ((flags & FLAG_SWITCHER_SHOWING) != 0)
&& ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
}
@@ -737,8 +737,11 @@
if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
NavButtonLayoutter navButtonLayoutter =
NavButtonLayoutFactory.Companion.getUiLayoutter(
- dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
- TaskbarManager.isPhoneMode(dp), mDisplayController.getInfo().rotation);
+ dp, mNavButtonsView, mImeSwitcherButton,
+ mControllers.rotationButtonController.getRotationButton(),
+ mA11yButton, res, isInKidsMode, isInSetup, isThreeButtonNav,
+ TaskbarManager.isPhoneMode(dp),
+ mWindowManagerProxy.getRotation(mContext));
navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
updateNavButtonColor();
return;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index e922c4c..ce901f2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -161,6 +161,9 @@
if (mActivity != null) {
mActivity.removeOnDeviceProfileChangeListener(
mDebugActivityDeviceProfileChanged);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "unregistering activity lifecycle callbacks from "
+ + "onActivityDestroyed.");
mActivity.unregisterActivityLifecycleCallbacks(this);
}
mActivity = null;
@@ -172,6 +175,35 @@
}
};
+ UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener =
+ new UnfoldTransitionProgressProvider.TransitionProgressListener() {
+ @Override
+ public void onTransitionStarted() {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition started getting called.");
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition progress : " + progress);
+ }
+
+ @Override
+ public void onTransitionFinishing() {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition finishing getting called.");
+
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "fold/unfold transition finished getting called.");
+
+ }
+ };
+
@SuppressLint("WrongConstant")
public TaskbarManager(TouchInteractionService service) {
Display display =
@@ -239,6 +271,7 @@
.register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
SettingsCache.INSTANCE.get(mContext)
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
mContext.registerComponentCallbacks(mComponentCallbacks);
mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
UI_HELPER_EXECUTOR.execute(() -> {
@@ -330,16 +363,18 @@
if (mActivity == activity) {
return;
}
- if (mActivity != null) {
- mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
- mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
- }
+ removeActivityCallbacksAndListeners();
mActivity = activity;
debugWhyTaskbarNotDestroyed("Set mActivity=" + mActivity);
mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "registering activity lifecycle callbacks from setActivity().");
mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
getUnfoldTransitionProgressProviderForActivity(activity);
+ if (unfoldTransitionProgressProvider != null) {
+ unfoldTransitionProgressProvider.addCallback(mUnfoldTransitionProgressListener);
+ }
mUnfoldProgressProvider.setSourceProvider(unfoldTransitionProgressProvider);
if (mTaskbarActivityContext != null) {
@@ -506,15 +541,27 @@
}
}
+ private void removeActivityCallbacksAndListeners() {
+ if (mActivity != null) {
+ mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ "unregistering activity lifecycle callbacks from "
+ + "removeActivityCallbackAndListeners().");
+ mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
+ UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
+ getUnfoldTransitionProgressProviderForActivity(mActivity);
+ if (unfoldTransitionProgressProvider != null) {
+ unfoldTransitionProgressProvider.removeCallback(mUnfoldTransitionProgressListener);
+ }
+ }
+ }
+
/**
* Called when the manager is no longer needed
*/
public void destroy() {
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
- if (mActivity != null) {
- mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
- }
-
+ removeActivityCallbacksAndListeners();
UI_HELPER_EXECUTOR.execute(
() -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
destroyExistingTaskbar();
@@ -525,6 +572,7 @@
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
SettingsCache.INSTANCE.get(mContext)
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
mContext.unregisterComponentCallbacks(mComponentCallbacks);
mContext.unregisterReceiver(mShutdownReceiver);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 139e8f0..445b312 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -155,7 +155,7 @@
mControllers.taskbarActivityContext.startTranslationSpring();
}
- /*
+ /**
* @param ev MotionEvent in screen coordinates.
* @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
*/
@@ -164,6 +164,14 @@
|| mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
}
+ /** Checks if the given {@link MotionEvent} is over the bubble bar stash handle. */
+ public boolean isEventOverBubbleBarStashHandle(MotionEvent ev) {
+ return mControllers.bubbleControllers.map(
+ bubbleControllers ->
+ bubbleControllers.bubbleStashController.isEventOverStashHandle(ev))
+ .orElse(false);
+ }
+
/**
* Returns true if icons should be aligned to hotseat in the current transition.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index c54bb7e..5182a32 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -89,7 +89,8 @@
mAppsModelFlags = flags;
mPackageUserKeytoUidMap = map;
if (mAppsView != null) {
- mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
+ mAppsView.getAppsStore().setApps(
+ mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false);
}
}
@@ -190,7 +191,7 @@
viewController.show(animate);
mAppsView = mOverlayContext.getAppsView();
- mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap);
+ mAppsView.getAppsStore().setApps(mApps, mAppsModelFlags, mPackageUserKeytoUidMap, false);
mAppsView.getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mPredictedApps);
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index c6c4f77..5ce2a7a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -222,7 +222,7 @@
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !mAppsView.shouldContainerScroll(ev)
|| getTopOpenViewWithType(
- mActivityContext, TYPE_ACCESSIBLE & ~TYPE_TASKBAR_ALL_APPS) != null;
+ mActivityContext, TYPE_ACCESSIBLE & ~TYPE_TASKBAR_OVERLAYS) != null;
}
return super.onControllerInterceptTouchEvent(ev);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index a5ea5a9..09021ed 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -22,6 +22,7 @@
import android.animation.AnimatorSet;
import android.annotation.Nullable;
import android.view.InsetsController;
+import android.view.MotionEvent;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.StashedHandleViewController;
@@ -350,4 +351,9 @@
return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
: getBubbleBarTranslationYForTaskbar();
}
+
+ /** Checks whether the motion event is over the stash handle. */
+ public boolean isEventOverStashHandle(MotionEvent ev) {
+ return mHandleViewController.isEventOverHandle(ev);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index fbab595..c998d97 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -24,6 +24,7 @@
import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewOutlineProvider;
@@ -268,4 +269,22 @@
});
return revealAnim;
}
+
+ /** Checks that the stash handle is visible and that the motion event is within bounds. */
+ public boolean isEventOverHandle(MotionEvent ev) {
+ if (mStashedHandleView.getVisibility() != VISIBLE) {
+ return false;
+ }
+
+ // the bounds of the handle only include the visible part, so we check that the Y coordinate
+ // is anywhere within the stashed taskbar height.
+ int top = mActivity.getDeviceProfile().heightPx - mStashedTaskbarHeight;
+
+ return (int) ev.getRawY() >= top && containsX((int) ev.getRawX());
+ }
+
+ /** Checks if the given x coordinate is within the stashed handle bounds. */
+ public boolean containsX(int x) {
+ return x >= mStashedHandleBounds.left && x <= mStashedHandleBounds.right;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index b682081..9758d44 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -18,12 +18,15 @@
import android.content.res.Resources
import android.graphics.drawable.RotateDrawable
+import android.view.Gravity
import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
+import com.android.systemui.shared.rotation.RotationButton
/**
* Meant to be a simple container for data subclasses will need
@@ -37,10 +40,13 @@
* @property startContextualContainer ViewGroup that holds the start contextual button (ex, A11y).
*/
abstract class AbstractNavButtonLayoutter(
- val resources: Resources,
- val navButtonContainer: LinearLayout,
- protected val endContextualContainer: ViewGroup,
- protected val startContextualContainer: ViewGroup
+ val resources: Resources,
+ val navButtonContainer: LinearLayout,
+ protected val endContextualContainer: ViewGroup,
+ protected val startContextualContainer: ViewGroup,
+ protected val imeSwitcher: ImageView?,
+ protected val rotationButton: RotationButton?,
+ protected val a11yButton: ImageView
) : NavButtonLayoutter {
protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
@@ -56,4 +62,11 @@
backButton.setImageDrawable(rotateDrawable)
}
}
+
+ fun getParamsToCenterView(): FrameLayout.LayoutParams {
+ val params = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ params.gravity = Gravity.CENTER
+ return params;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index 4a53c0c..f254ee8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -26,18 +26,25 @@
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
+import com.android.systemui.shared.rotation.RotationButton
class KidsNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView
) :
AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
@@ -89,5 +96,28 @@
navButtonContainer.requestLayout()
homeButton.onLongClickListener = null
+
+ endContextualContainer.removeAllViews()
+ startContextualContainer.removeAllViews()
+
+ val endContextualContainerParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ endContextualContainerParams.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+ endContextualContainer.layoutParams = endContextualContainerParams
+
+ val startContextualContainerParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ startContextualContainerParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+ startContextualContainer.layoutParams = startContextualContainerParams
+
+ if (imeSwitcher != null) {
+ startContextualContainer.addView(imeSwitcher)
+ imeSwitcher.layoutParams = getParamsToCenterView()
+ }
+ endContextualContainer.addView(a11yButton)
+ if (rotationButton != null) {
+ endContextualContainer.addView(rotationButton.currentView)
+ rotationButton.currentView.layoutParams = getParamsToCenterView()
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 931f692..7db1a37 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -21,11 +21,13 @@
import android.view.Surface.Rotation
import android.view.ViewGroup
import android.widget.FrameLayout
+import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.Companion
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
+import com.android.systemui.shared.rotation.RotationButton
/**
* Select the correct layout for nav buttons
@@ -52,14 +54,17 @@
* @param isThreeButtonNav are no-ops when taskbar is present/showing
*/
fun getUiLayoutter(
- deviceProfile: DeviceProfile,
- navButtonsView: FrameLayout,
- resources: Resources,
- isKidsMode: Boolean,
- isInSetup: Boolean,
- isThreeButtonNav: Boolean,
- phoneMode: Boolean,
- @Rotation surfaceRotation: Int
+ deviceProfile: DeviceProfile,
+ navButtonsView: FrameLayout,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView,
+ resources: Resources,
+ isKidsMode: Boolean,
+ isInSetup: Boolean,
+ isThreeButtonNav: Boolean,
+ phoneMode: Boolean,
+ @Rotation surfaceRotation: Int
): NavButtonLayoutter {
val navButtonContainer =
navButtonsView.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS)
@@ -73,24 +78,33 @@
isPhoneNavMode -> {
if (!deviceProfile.isLandscape) {
PhonePortraitNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
)
} else if (surfaceRotation == ROTATION_90) {
PhoneLandscapeNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
)
} else {
PhoneSeascapeNavLayoutter(
resources,
navButtonContainer,
endContextualContainer,
- startContextualContainer
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
)
}
}
@@ -99,33 +113,45 @@
resources,
navButtonContainer,
endContextualContainer,
- startContextualContainer
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
)
}
deviceProfile.isTaskbarPresent -> {
return when {
isInSetup -> {
SetupNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
)
}
isKidsMode -> {
KidsNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
)
}
else ->
TaskbarNavLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
index 8525c6c..c1dae40 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -18,24 +18,33 @@
import android.content.res.Resources
import android.view.ViewGroup
+import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
+import com.android.systemui.shared.rotation.RotationButton
/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
class PhoneGestureLayoutter(
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView
) :
AbstractNavButtonLayoutter(
resources,
navBarContainer,
endContextualContainer,
- startContextualContainer
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
- // no-op
+ endContextualContainer.removeAllViews()
+ startContextualContainer.removeAllViews()
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 2acd5d4..21bbca5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -20,24 +20,32 @@
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
+import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.view.children
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.util.DimensionUtils
+import com.android.systemui.shared.rotation.RotationButton
open class PhoneLandscapeNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView,
) :
AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
@@ -57,14 +65,11 @@
marginStart = 0
}
- // Swap recents and back button
- navButtonContainer.addView(recentsButton)
- navButtonContainer.addView(homeButton)
- navButtonContainer.addView(backButton)
-
navButtonContainer.layoutParams = navContainerParams
navButtonContainer.gravity = Gravity.CENTER
+ addThreeButtons()
+
// Add the spaces in between the nav buttons
val spaceInBetween: Int =
resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
@@ -84,5 +89,30 @@
}
}
}
+
+ endContextualContainer.removeAllViews()
+ startContextualContainer.removeAllViews()
+
+ val startContextualContainerParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ startContextualContainerParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ startContextualContainer.layoutParams = startContextualContainerParams
+
+ if (imeSwitcher != null) {
+ startContextualContainer.addView(imeSwitcher)
+ imeSwitcher.layoutParams = getParamsToCenterView()
+ }
+ startContextualContainer.addView(a11yButton)
+ if (rotationButton != null) {
+ startContextualContainer.addView(rotationButton.currentView)
+ rotationButton.currentView.layoutParams = getParamsToCenterView()
+ }
+ }
+
+ open fun addThreeButtons() {
+ // Swap recents and back button
+ navButtonContainer.addView(recentsButton)
+ navButtonContainer.addView(homeButton)
+ navButtonContainer.addView(backButton)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index c763115..ad03e5b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -20,23 +20,31 @@
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
+import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.util.DimensionUtils
+import com.android.systemui.shared.rotation.RotationButton
class PhonePortraitNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView,
) :
AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
@@ -90,5 +98,23 @@
}
}
}
+
+ endContextualContainer.removeAllViews()
+ startContextualContainer.removeAllViews()
+
+ val endContextualContainerParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ endContextualContainerParams.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+ endContextualContainer.layoutParams = endContextualContainerParams
+
+ if (imeSwitcher != null) {
+ endContextualContainer.addView(imeSwitcher)
+ imeSwitcher.layoutParams = getParamsToCenterView()
+ }
+ endContextualContainer.addView(a11yButton)
+ if (rotationButton != null) {
+ endContextualContainer.addView(rotationButton.currentView)
+ rotationButton.currentView.layoutParams = getParamsToCenterView()
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
index f0fe581..cde39f3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -17,30 +17,54 @@
package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
+import android.view.Gravity
import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
+import com.android.systemui.shared.rotation.RotationButton
class PhoneSeascapeNavLayoutter(
resources: Resources,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView
) :
PhoneLandscapeNavLayoutter(
resources,
navBarContainer,
endContextualContainer,
- startContextualContainer
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
) {
- override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
- // TODO(b/230395757): Polish pending, this is just to make it usable
- super.layoutButtons(dp, isContextualButtonShowing)
- navButtonContainer.removeAllViews()
+ override fun addThreeButtons() {
// Flip ordering of back and recents buttons
navButtonContainer.addView(backButton)
navButtonContainer.addView(homeButton)
navButtonContainer.addView(recentsButton)
+
+ endContextualContainer.removeAllViews()
+ startContextualContainer.removeAllViews()
+
+ val endContextualContainerParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ endContextualContainerParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ endContextualContainer.layoutParams = endContextualContainerParams
+
+ if (imeSwitcher != null) {
+ endContextualContainer.addView(imeSwitcher)
+ imeSwitcher.layoutParams = getParamsToCenterView()
+ }
+ endContextualContainer.addView(a11yButton)
+ if (rotationButton != null) {
+ endContextualContainer.addView(rotationButton.currentView)
+ rotationButton.currentView.layoutParams = getParamsToCenterView()
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index a24002c..db245b8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -20,20 +20,28 @@
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
+import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
+import com.android.systemui.shared.rotation.RotationButton
class SetupNavLayoutter(
- resources: Resources,
- navButtonContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup
+ resources: Resources,
+ navButtonContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView
) :
AbstractNavButtonLayoutter(
- resources,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer
+ resources,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
@@ -46,5 +54,28 @@
gravity = Gravity.START
}
navButtonContainer.requestLayout()
+
+ endContextualContainer.removeAllViews()
+ startContextualContainer.removeAllViews()
+
+ val endContextualContainerParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ endContextualContainerParams.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+ endContextualContainer.layoutParams = endContextualContainerParams
+
+ val startContextualContainerParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ startContextualContainerParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+ startContextualContainer.layoutParams = startContextualContainerParams
+
+ if (imeSwitcher != null) {
+ startContextualContainer.addView(imeSwitcher)
+ imeSwitcher.layoutParams = getParamsToCenterView()
+ }
+ endContextualContainer.addView(a11yButton)
+ if (rotationButton != null) {
+ endContextualContainer.addView(rotationButton.currentView)
+ rotationButton.currentView.layoutParams = getParamsToCenterView()
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 8332b7d..56e55bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -20,22 +20,30 @@
import android.view.Gravity
import android.view.ViewGroup
import android.widget.FrameLayout
+import android.widget.ImageView
import android.widget.LinearLayout
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
+import com.android.systemui.shared.rotation.RotationButton
/** Layoutter for showing 3 button navigation on large screen */
class TaskbarNavLayoutter(
- resources: Resources,
- navBarContainer: LinearLayout,
- endContextualContainer: ViewGroup,
- startContextualContainer: ViewGroup
+ resources: Resources,
+ navBarContainer: LinearLayout,
+ endContextualContainer: ViewGroup,
+ startContextualContainer: ViewGroup,
+ imeSwitcher: ImageView?,
+ rotationButton: RotationButton?,
+ a11yButton: ImageView
) :
AbstractNavButtonLayoutter(
- resources,
- navBarContainer,
- endContextualContainer,
- startContextualContainer
+ resources,
+ navBarContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ rotationButton,
+ a11yButton
) {
override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
@@ -77,5 +85,28 @@
}
}
}
+
+ endContextualContainer.removeAllViews()
+ startContextualContainer.removeAllViews()
+
+ val endContextualContainerParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ endContextualContainerParams.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+ endContextualContainer.layoutParams = endContextualContainerParams
+
+ val startContextualContainerParams = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ startContextualContainerParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+ startContextualContainer.layoutParams = startContextualContainerParams
+
+ if (imeSwitcher != null) {
+ startContextualContainer.addView(imeSwitcher)
+ imeSwitcher.layoutParams = getParamsToCenterView()
+ }
+ endContextualContainer.addView(a11yButton)
+ if (rotationButton != null) {
+ endContextualContainer.addView(rotationButton.currentView)
+ rotationButton.currentView.layoutParams = getParamsToCenterView()
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 40909d5..1b22c84 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -106,6 +106,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
@@ -225,6 +226,9 @@
*/
private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
+ @Nullable
+ private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
+
private SafeCloseable mViewCapture;
private boolean mEnableWidgetDepth;
@@ -235,12 +239,19 @@
mActionsView = findViewById(R.id.overview_actions_view);
RecentsView overviewPanel = getOverviewPanel();
+ SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
mSplitSelectStateController =
new SplitSelectStateController(this, mHandler, getStateManager(),
getDepthController(), getStatsLogManager(),
- SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this),
+ systemUiProxy, RecentsModel.INSTANCE.get(this),
() -> onStateBack());
- overviewPanel.init(mActionsView, mSplitSelectStateController);
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
+ getStateManager(), systemUiProxy, getIApplicationThread(),
+ getDepthController());
+ }
+ overviewPanel.init(mActionsView, mSplitSelectStateController,
+ mDesktopRecentsTransitionController);
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
mSplitSelectStateController);
mSplitToWorkspaceController = new SplitToWorkspaceController(this,
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index ca4f0ea..e788cc4 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -51,6 +51,7 @@
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.INVALID_VELOCITY_ON_SWIPE_UP;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
@@ -1214,6 +1215,11 @@
private GestureEndTarget calculateEndTarget(
PointF velocityPxPerMs, float endVelocityPxPerMs, boolean isFlingY, boolean isCancel) {
+
+ ActiveGestureErrorDetector.GestureEvent gestureEvent =
+ velocityPxPerMs.x == 0 && velocityPxPerMs.y == 0
+ ? INVALID_VELOCITY_ON_SWIPE_UP
+ : null;
ActiveGestureLog.INSTANCE.addLog(
new ActiveGestureLog.CompoundString("calculateEndTarget: velocities=(x=")
.append(Float.toString(dpiFromPx(velocityPxPerMs.x)))
@@ -1221,7 +1227,7 @@
.append(Float.toString(dpiFromPx(velocityPxPerMs.y)))
.append("dp/ms), angle=")
.append(Double.toString(Math.toDegrees(Math.atan2(
- -velocityPxPerMs.y, velocityPxPerMs.x)))));
+ -velocityPxPerMs.y, velocityPxPerMs.x)))), gestureEvent);
if (mGestureState.isHandlingAtomicEvent()) {
// Button mode, this is only used to go to recents.
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 3f88139..8925bd6 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -191,8 +191,11 @@
public abstract boolean allowAllAppsFromOverview();
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+ TaskbarUIController controller = getTaskbarController();
+ boolean isEventOverBubbleBarStashHandle =
+ controller != null && controller.isEventOverBubbleBarStashHandle(ev);
return deviceState.isInDeferredGestureRegion(ev) || deviceState.isImeRenderingNavButtons()
- || isTrackpadMultiFingerSwipe(ev);
+ || isTrackpadMultiFingerSwipe(ev) || isEventOverBubbleBarStashHandle;
}
/**
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 9be9294..221ce48 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -161,6 +161,10 @@
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
SystemUiProxy.INSTANCE.get(mContext).isDragAndDropReady());
return response;
+
+ case TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET:
+ runOnTISBinder(TouchInteractionService.TISBinder::refreshOverviewTarget);
+ return response;
}
return super.call(method, arg, extras);
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 0a7344a..6ee2cfd 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -353,7 +353,8 @@
writer.println(prefix + " ]");
}
- private static class TaskLoadResult extends ArrayList<GroupTask> {
+ @VisibleForTesting
+ static class TaskLoadResult extends ArrayList<GroupTask> {
final int mRequestId;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index d66421f..3c7fbf0 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -60,6 +60,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
@@ -82,6 +83,7 @@
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TISBindHelper;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -121,6 +123,8 @@
private final Handler mHandler = new Handler();
private final Runnable mAnimationStartTimeoutRunnable = this::onAnimationStartTimeout;
private SplitSelectStateController mSplitSelectStateController;
+ @Nullable
+ private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
/**
* Init drag layer and overview panel views.
@@ -133,13 +137,21 @@
mFallbackRecentsView = findViewById(R.id.overview_panel);
mActionsView = findViewById(R.id.overview_actions_view);
getRootView().getSysUiScrim().getSysUIProgress().updateValue(0);
+ SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
mSplitSelectStateController =
new SplitSelectStateController(this, mHandler, getStateManager(),
null /* depthController */, getStatsLogManager(),
- SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this),
+ systemUiProxy, RecentsModel.INSTANCE.get(this),
null /*activityBackCallback*/);
mDragLayer.recreateControllers();
- mFallbackRecentsView.init(mActionsView, mSplitSelectStateController);
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
+ getStateManager(), systemUiProxy, getIApplicationThread(),
+ null /* depthController */
+ );
+ }
+ mFallbackRecentsView.init(mActionsView, mSplitSelectStateController,
+ mDesktopRecentsTransitionController);
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index d798e62..36a6eb6 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -17,15 +17,18 @@
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.KeyguardManager;
+import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
@@ -36,6 +39,7 @@
import com.android.launcher3.icons.IconProvider.IconChangeListener;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.systemui.shared.recents.model.Task;
@@ -57,7 +61,7 @@
*/
@TargetApi(Build.VERSION_CODES.O)
public class RecentsModel implements IconChangeListener, TaskStackChangeListener,
- TaskVisualsChangeListener {
+ TaskVisualsChangeListener, SafeCloseable {
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
@@ -72,17 +76,46 @@
private final RecentTasksList mTaskList;
private final TaskIconCache mIconCache;
private final TaskThumbnailCache mThumbnailCache;
+ private final ComponentCallbacks mCallbacks;
private RecentsModel(Context context) {
- mContext = context;
- mTaskList = new RecentTasksList(MAIN_EXECUTOR,
- context.getSystemService(KeyguardManager.class),
- SystemUiProxy.INSTANCE.get(context));
+ this(context, new IconProvider(context));
+ }
- IconProvider iconProvider = new IconProvider(context);
- mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider);
+ private RecentsModel(Context context, IconProvider iconProvider) {
+ this(context,
+ new RecentTasksList(MAIN_EXECUTOR,
+ context.getSystemService(KeyguardManager.class),
+ SystemUiProxy.INSTANCE.get(context)),
+ new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider),
+ new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
+ iconProvider);
+ }
+
+ @VisibleForTesting
+ RecentsModel(Context context, RecentTasksList taskList, TaskIconCache iconCache,
+ TaskThumbnailCache thumbnailCache,
+ IconProvider iconProvider) {
+ mContext = context;
+ mTaskList = taskList;
+ mIconCache = iconCache;
mIconCache.registerTaskVisualsChangeListener(this);
- mThumbnailCache = new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR);
+ mThumbnailCache = thumbnailCache;
+ if (enableGridOnlyOverview()) {
+ mCallbacks = new ComponentCallbacks() {
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ updateCacheSizeAndPreloadIfNeeded();
+ }
+
+ @Override
+ public void onLowMemory() {
+ }
+ };
+ context.registerComponentCallbacks(mCallbacks);
+ } else {
+ mCallbacks = null;
+ }
TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
iconProvider.registerIconChangeListener(this, MAIN_EXECUTOR.getHandler());
@@ -109,7 +142,6 @@
RecentsFilterState.DEFAULT_FILTER);
}
-
/**
* Fetches the list of recent tasks, based on a filter
*
@@ -183,8 +215,8 @@
// time the user next enters overview
continue;
}
- mThumbnailCache.updateThumbnailInCache(group.task1);
- mThumbnailCache.updateThumbnailInCache(group.task2);
+ mThumbnailCache.updateThumbnailInCache(group.task1, /* lowResolution= */ true);
+ mThumbnailCache.updateThumbnailInCache(group.task2, /* lowResolution= */ true);
}
});
}
@@ -281,6 +313,54 @@
}
/**
+ * Preloads cache if enableGridOnlyOverview is true, preloading is enabled and
+ * highResLoadingState is enabled
+ */
+ public void preloadCacheIfNeeded() {
+ if (!enableGridOnlyOverview()) {
+ return;
+ }
+
+ if (!mThumbnailCache.isPreloadingEnabled()) {
+ // Skip if we aren't preloading.
+ return;
+ }
+
+ if (!mThumbnailCache.getHighResLoadingState().isEnabled()) {
+ // Skip if high-res loading state is disabled.
+ return;
+ }
+
+ mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), taskGroups -> {
+ for (GroupTask group : taskGroups) {
+ mThumbnailCache.updateThumbnailInCache(group.task1, /* lowResolution= */ false);
+ mThumbnailCache.updateThumbnailInCache(group.task2, /* lowResolution= */ false);
+ }
+ });
+ }
+
+ /**
+ * Updates cache size and preloads more tasks if cache size increases
+ */
+ public void updateCacheSizeAndPreloadIfNeeded() {
+ if (!enableGridOnlyOverview()) {
+ return;
+ }
+
+ // If new size is larger than original size, preload more cache to fill the gap
+ if (mThumbnailCache.updateCacheSizeAndRemoveExcess()) {
+ preloadCacheIfNeeded();
+ }
+ }
+
+ @Override
+ public void close() {
+ if (mCallbacks != null) {
+ mContext.unregisterComponentCallbacks(mCallbacks);
+ }
+ }
+
+ /**
* Listener for receiving running tasks changes
*/
public interface RunningTasksListener {
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 170847d..419824a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -137,6 +137,7 @@
private ISplitSelectListener mSplitSelectListener;
private IStartingWindowListener mStartingWindowListener;
private ILauncherUnlockAnimationController mLauncherUnlockAnimationController;
+ private String mLauncherActivityClass;
private IRecentTasksListener mRecentTasksListener;
private IUnfoldTransitionListener mUnfoldAnimationListener;
private IDesktopTaskListener mDesktopTaskListener;
@@ -248,7 +249,8 @@
registerSplitScreenListener(mSplitScreenListener);
registerSplitSelectListener(mSplitSelectListener);
setStartingWindowListener(mStartingWindowListener);
- setLauncherUnlockAnimationController(mLauncherUnlockAnimationController);
+ setLauncherUnlockAnimationController(
+ mLauncherActivityClass, mLauncherUnlockAnimationController);
new LinkedHashMap<>(mRemoteTransitions).forEach(this::registerRemoteTransition);
setupTransactionQueue();
registerRecentTasksListener(mRecentTasksListener);
@@ -1109,11 +1111,11 @@
* changes).
*/
public void setLauncherUnlockAnimationController(
- ILauncherUnlockAnimationController controller) {
+ String activityClass, ILauncherUnlockAnimationController controller) {
if (mSysuiUnlockAnimationController != null) {
try {
- mSysuiUnlockAnimationController.setLauncherUnlockController(controller);
-
+ mSysuiUnlockAnimationController.setLauncherUnlockController(
+ activityClass, controller);
if (controller != null) {
controller.dispatchSmartspaceStateToSysui();
}
@@ -1121,7 +1123,7 @@
Log.w(TAG, "Failed call setLauncherUnlockAnimationController", e);
}
}
-
+ mLauncherActivityClass = activityClass;
mLauncherUnlockAnimationController = controller;
}
@@ -1268,10 +1270,10 @@
//
/** Call shell to show all apps active on the desktop */
- public void showDesktopApps(int displayId) {
+ public void showDesktopApps(int displayId, @Nullable RemoteTransition transition) {
if (mDesktopMode != null) {
try {
- mDesktopMode.showDesktopApps(displayId);
+ mDesktopMode.showDesktopApps(displayId, transition);
} catch (RemoteException e) {
Log.w(TAG, "Failed call showDesktopApps", e);
}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 3175ba8..2ca9f99 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -15,12 +15,18 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+
import android.content.Context;
import android.content.res.Resources;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.R;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.util.CancellableTask;
+import com.android.quickstep.util.TaskKeyByLastActiveTimeCache;
+import com.android.quickstep.util.TaskKeyCache;
import com.android.quickstep.util.TaskKeyLruCache;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -34,11 +40,10 @@
public class TaskThumbnailCache {
private final Executor mBgExecutor;
-
- private final int mCacheSize;
- private final TaskKeyLruCache<ThumbnailData> mCache;
+ private final TaskKeyCache<ThumbnailData> mCache;
private final HighResLoadingState mHighResLoadingState;
private final boolean mEnableTaskSnapshotPreloading;
+ private final Context mContext;
public static class HighResLoadingState {
private boolean mForceHighResThumbnails;
@@ -91,26 +96,39 @@
}
public TaskThumbnailCache(Context context, Executor bgExecutor) {
+ this(context, bgExecutor,
+ context.getResources().getInteger(R.integer.recentsThumbnailCacheSize));
+ }
+
+ private TaskThumbnailCache(Context context, Executor bgExecutor, int cacheSize) {
+ this(context, bgExecutor,
+ enableGridOnlyOverview() ? new TaskKeyByLastActiveTimeCache<>(cacheSize)
+ : new TaskKeyLruCache<>(cacheSize));
+ }
+
+ @VisibleForTesting
+ TaskThumbnailCache(Context context, Executor bgExecutor, TaskKeyCache<ThumbnailData> cache) {
mBgExecutor = bgExecutor;
mHighResLoadingState = new HighResLoadingState(context);
+ mContext = context;
Resources res = context.getResources();
- mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize);
mEnableTaskSnapshotPreloading = res.getBoolean(R.bool.config_enableTaskSnapshotPreloading);
- mCache = new TaskKeyLruCache<>(mCacheSize);
+ mCache = cache;
}
/**
- * Synchronously fetches the thumbnail for the given {@param task} and puts it in the cache.
+ * Synchronously fetches the thumbnail for the given task at the specified resolution level, and
+ * puts it in the cache.
*/
- public void updateThumbnailInCache(Task task) {
+ public void updateThumbnailInCache(Task task, boolean lowResolution) {
if (task == null) {
return;
}
Preconditions.assertUIThread();
// Fetch the thumbnail for this task and put it in the cache
if (task.thumbnail == null) {
- updateThumbnailInBackground(task.key, true /* lowResolution */,
+ updateThumbnailInBackground(task.key, lowResolution,
t -> task.thumbnail = t);
}
}
@@ -148,6 +166,23 @@
});
}
+ /**
+ * Updates cache size and remove excess entries if current size is more than new cache size.
+ *
+ * @return whether cache size has increased
+ */
+ public boolean updateCacheSizeAndRemoveExcess() {
+ int newSize = mContext.getResources().getInteger(R.integer.recentsThumbnailCacheSize);
+ int oldSize = mCache.getMaxSize();
+ if (newSize == oldSize) {
+ // Return if no change in size
+ return false;
+ }
+
+ mCache.updateCacheSizeAndRemoveExcess(newSize);
+ return newSize > oldSize;
+ }
+
private CancellableTask updateThumbnailInBackground(TaskKey key, boolean lowResolution,
Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
@@ -169,6 +204,16 @@
@Override
public void handleResult(ThumbnailData result) {
+ // Avoid an async timing issue that a low res entry replaces an existing high res
+ // entry in high res enabled state, so we check before putting it to cache
+ if (enableGridOnlyOverview() && result.reducedResolution
+ && getHighResLoadingState().isEnabled()) {
+ ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
+ if (cachedThumbnail != null && cachedThumbnail.thumbnail != null
+ && !cachedThumbnail.reducedResolution) {
+ return;
+ }
+ }
mCache.put(key, result);
callback.accept(result);
}
@@ -195,7 +240,7 @@
* @return The cache size.
*/
public int getCacheSize() {
- return mCacheSize;
+ return mCache.getMaxSize();
}
/**
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 34965df..819f249 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -613,6 +613,42 @@
animator.start();
}
+ /**
+ * Start recents to desktop animation
+ */
+ public static void composeRecentsDesktopLaunchAnimator(
+ @NonNull DesktopTaskView launchingTaskView,
+ @NonNull StateManager stateManager, @Nullable DepthController depthController,
+ @NonNull TransitionInfo transitionInfo,
+ SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ t.apply();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishCallback.run();
+ }
+ });
+
+ final RemoteAnimationTarget[] apps = RemoteAnimationTargetCompat.wrapApps(
+ transitionInfo, t, null /* leashMap */);
+ final RemoteAnimationTarget[] wallpaper = RemoteAnimationTargetCompat.wrapNonApps(
+ transitionInfo, true /* wallpapers */, t, null /* leashMap */);
+ final RemoteAnimationTarget[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(
+ transitionInfo, false /* wallpapers */, t, null /* leashMap */);
+
+ composeRecentsLaunchAnimator(animatorSet, launchingTaskView, apps, wallpaper, nonApps,
+ true /* launcherClosing */, stateManager, launchingTaskView.getRecentsView(),
+ depthController);
+
+ animatorSet.start();
+ }
+
public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@NonNull RemoteAnimationTarget[] appTargets,
@NonNull RemoteAnimationTarget[] wallpaperTargets,
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 02d0f39..02fcc68 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -409,18 +409,16 @@
* Sets a proxy to bypass swipe up behavior
*/
public void setSwipeUpProxy(Function<GestureState, AnimatedFloat> proxy) {
- TouchInteractionService tis = mTis.get();
- if (tis == null) return;
- tis.mSwipeUpProxyProvider = proxy != null ? proxy : (i -> null);
+ executeForTouchInteractionService(
+ tis -> tis.mSwipeUpProxyProvider = proxy != null ? proxy : (i -> null));
}
/**
* Sets the task id where gestures should be blocked
*/
public void setGestureBlockedTaskId(int taskId) {
- TouchInteractionService tis = mTis.get();
- if (tis == null) return;
- tis.mDeviceState.setGestureBlockingTaskId(taskId);
+ executeForTouchInteractionService(
+ tis -> tis.mDeviceState.setGestureBlockingTaskId(taskId));
}
/** Sets a listener to be run on Overview Target updates. */
@@ -434,6 +432,12 @@
mOnOverviewTargetChangeListener = null;
}
}
+
+ /** Refreshes the current overview target. */
+ public void refreshOverviewTarget() {
+ executeForTouchInteractionService(tis -> tis.onOverviewTargetChange(
+ tis.mOverviewComponentObserver.isHomeAndOverviewSame()));
+ }
}
private static boolean sConnected = false;
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 95d88cd..059b0ce 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -36,6 +36,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.SplitConfigurationOptions;
@@ -73,8 +74,9 @@
}
@Override
- public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
- super.init(actionsView, splitController);
+ public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
+ @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
+ super.init(actionsView, splitController, desktopRecentsTransitionController);
setOverviewStateEnabled(true);
setOverlayEnabled(true);
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 61c2b43..f6a9440 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -15,6 +15,9 @@
*/
package com.android.quickstep.inputconsumers;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
@@ -30,6 +33,7 @@
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.InputDevice;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
import androidx.annotation.Nullable;
@@ -68,6 +72,9 @@
private final boolean mIsTaskbarAllAppsOpen;
private boolean mHasPassedTaskbarNavThreshold;
private boolean mIsInBubbleBarArea;
+ private boolean mIsVerticalGestureOverBubbleBar;
+ private boolean mIsPassedBubbleBarSlop;
+ private final int mTouchSlop;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
@@ -91,6 +98,7 @@
mOverviewCommandHelper = overviewCommandHelper;
// TODO(b/270395798): remove this when cleaning up old Persistent Taskbar code.
mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mScreenWidth = taskbarActivityContext.getDeviceProfile().widthPx;
Resources res = context.getResources();
@@ -99,8 +107,7 @@
taskbarActivityContext.getDeviceProfile());
mTaskbarNavThresholdY = taskbarActivityContext.getDeviceProfile().heightPx
- mTaskbarNavThreshold;
- mIsTaskbarAllAppsOpen =
- mTaskbarActivityContext != null && mTaskbarActivityContext.isTaskbarAllAppsOpen();
+ mIsTaskbarAllAppsOpen = mTaskbarActivityContext.isTaskbarAllAppsOpen();
mIsTransientTaskbar = DisplayController.isTransientTaskbar(context);
@@ -132,12 +139,8 @@
if (mState != STATE_ACTIVE) {
boolean isStashedTaskbarHovered = isMouseEvent(ev)
&& isStashedTaskbarHovered((int) ev.getX(), (int) ev.getY());
- if (!isStashedTaskbarHovered) {
- mDelegate.onMotionEvent(ev);
- }
-
// Only show the transient task bar if the touch events are on the screen.
- if (mTaskbarActivityContext != null && !isTrackpadMotionEvent(ev)) {
+ if (!isTrackpadMotionEvent(ev)) {
final float x = ev.getRawX();
final float y = ev.getRawY();
switch (ev.getAction()) {
@@ -193,14 +196,28 @@
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+ float dX = mLastPos.x - mDownPos.x;
+ float dY = mLastPos.y - mDownPos.y;
+
+ if (!mIsPassedBubbleBarSlop && mIsInBubbleBarArea) {
+ boolean passedSlop =
+ Math.abs(dY) > mTouchSlop || Math.abs(dX) > mTouchSlop;
+ if (passedSlop) {
+ mIsPassedBubbleBarSlop = true;
+ mIsVerticalGestureOverBubbleBar = Math.abs(dY) > Math.abs(dX);
+ if (mIsVerticalGestureOverBubbleBar) {
+ setActive(ev);
+ }
+ }
+ }
+
if (mIsTransientTaskbar) {
- float dY = mLastPos.y - mDownPos.y;
boolean passedTaskbarNavThreshold = dY < 0
&& Math.abs(dY) >= mTaskbarNavThreshold;
if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) {
mHasPassedTaskbarNavThreshold = true;
- if (mIsInBubbleBarArea) {
+ if (mIsInBubbleBarArea && mIsVerticalGestureOverBubbleBar) {
mTaskbarActivityContext.onSwipeToOpenBubblebar();
} else {
mTaskbarActivityContext.onSwipeToUnstashTaskbar();
@@ -217,17 +234,7 @@
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- if (!mIsTransientTaskbar && !mCanceledUnstashHint) {
- mTaskbarActivityContext.startTaskbarUnstashHint(
- /* animateForward = */ false);
- }
- mTaskbarActivityContext.setAutohideSuspendFlag(
- FLAG_AUTOHIDE_SUSPEND_TOUCHING, false);
- if (mTransitionCallback != null) {
- mTransitionCallback.onActionEnd();
- }
- mHasPassedTaskbarNavThreshold = false;
- mIsInBubbleBarArea = false;
+ cleanupAfterMotionEvent();
break;
case MotionEvent.ACTION_BUTTON_RELEASE:
if (isStashedTaskbarHovered) {
@@ -236,9 +243,61 @@
break;
}
}
+ boolean isMovingInBubbleBarArea = mIsInBubbleBarArea && ev.getAction() == ACTION_MOVE;
+ if (!isStashedTaskbarHovered) {
+ // if we're moving in the bubble bar area but we haven't passed the slop yet, don't
+ // propagate to the delegate, until we can determine the direction of the gesture.
+ if (!isMovingInBubbleBarArea || mIsPassedBubbleBarSlop) {
+ mDelegate.onMotionEvent(ev);
+ }
+ }
+ } else if (mIsVerticalGestureOverBubbleBar) {
+ // if we get here then this gesture is a vertical swipe over the bubble bar.
+ // we're also active and there's no need to delegate any additional motion events. the
+ // rest of the gesture will be handled here.
+ switch (ev.getAction()) {
+ case ACTION_MOVE:
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == INVALID_POINTER_ID) {
+ break;
+ }
+ mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+
+ float dY = mLastPos.y - mDownPos.y;
+
+ // bubble bar swipe gesture uses the same threshold as the taskbar.
+ boolean passedTaskbarNavThreshold = dY < 0
+ && Math.abs(dY) >= mTaskbarNavThreshold;
+
+ if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) {
+ mHasPassedTaskbarNavThreshold = true;
+ mTaskbarActivityContext.onSwipeToOpenBubblebar();
+ }
+ break;
+ case ACTION_UP:
+ case ACTION_CANCEL:
+ cleanupAfterMotionEvent();
+ break;
+ }
}
}
+ private void cleanupAfterMotionEvent() {
+ if (!mIsTransientTaskbar && !mCanceledUnstashHint) {
+ mTaskbarActivityContext.startTaskbarUnstashHint(
+ /* animateForward = */ false);
+ }
+ mTaskbarActivityContext.setAutohideSuspendFlag(
+ FLAG_AUTOHIDE_SUSPEND_TOUCHING, false);
+ if (mTransitionCallback != null) {
+ mTransitionCallback.onActionEnd();
+ }
+ mHasPassedTaskbarNavThreshold = false;
+ mIsInBubbleBarArea = false;
+ mIsVerticalGestureOverBubbleBar = false;
+ mIsPassedBubbleBarSlop = false;
+ }
+
private boolean isInTaskbarArea(float x) {
float areaFromMiddle = mUnstashArea / 2.0f;
float distFromMiddle = Math.abs(mScreenWidth / 2.0f - x);
@@ -246,13 +305,19 @@
}
private boolean isInBubbleBarArea(float x) {
- if (mTaskbarActivityContext != null && mIsTransientTaskbar) {
- BubbleControllers controllers = mTaskbarActivityContext.getBubbleControllers();
- if (controllers == null) return false;
+ if (mTaskbarActivityContext == null || !mIsTransientTaskbar) {
+ return false;
+ }
+ BubbleControllers controllers = mTaskbarActivityContext.getBubbleControllers();
+ if (controllers == null) {
+ return false;
+ }
+ if (controllers.bubbleStashController.isStashed()) {
+ return controllers.bubbleStashedHandleViewController.containsX((int) x);
+ } else {
Rect bubbleBarBounds = controllers.bubbleBarViewController.getBubbleBarBounds();
return x >= bubbleBarBounds.left && x <= bubbleBarBounds.right;
}
- return false;
}
private void onLongPressDetected(MotionEvent motionEvent) {
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 4d7a5bb..20fa921 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -39,6 +39,7 @@
SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT,
SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, RECENT_TASKS_MISSING,
+ INVALID_VELOCITY_ON_SWIPE_UP,
/**
* These GestureEvents are specifically associated to state flags that get set in
@@ -66,6 +67,8 @@
private ActiveGestureErrorDetector() {}
+ private static final long ON_START_RECENT_ANIMATION_TIME_LIMIT = 500;
+
protected static void analyseAndDump(
@NonNull String prefix,
@NonNull PrintWriter writer,
@@ -76,6 +79,7 @@
// Use a Set since the order is inherently checked in the loop.
final Set<GestureEvent> encounteredEvents = new ArraySet<>();
// Set flags and check order of operations.
+ long lastStartRecentAnimationEventEntryTime = 0;
for (ActiveGestureLog.EventEntry eventEntry : eventLog.eventEntries) {
GestureEvent gestureEvent = eventEntry.getGestureEvent();
if (gestureEvent == null) {
@@ -234,6 +238,16 @@
/* errorMessage= */ "ON_START_RECENTS_ANIMATION "
+ "onAnimationStart callback ran before startRecentsAnimation",
writer);
+ errorDetected |= printErrorIfTrue(
+ eventEntry.getTime() - lastStartRecentAnimationEventEntryTime
+ > ON_START_RECENT_ANIMATION_TIME_LIMIT,
+ prefix,
+ /* errorMessage= */"ON_START_RECENTS_ANIMATION "
+ + "startRecentsAnimation was never called or onAnimationStart "
+ + "callback was called more than 500 ms after "
+ + "startRecentsAnimation.",
+ writer);
+ lastStartRecentAnimationEventEntryTime = 0;
break;
case ON_CANCEL_RECENTS_ANIMATION:
errorDetected |= printErrorIfTrue(
@@ -253,12 +267,21 @@
+ "callback",
writer);
break;
+ case INVALID_VELOCITY_ON_SWIPE_UP:
+ errorDetected |= printErrorIfTrue(
+ true,
+ prefix,
+ /* errorMessage= */ "invalid velocity on swipe up gesture.",
+ writer);
+ break;
+ case START_RECENTS_ANIMATION:
+ lastStartRecentAnimationEventEntryTime = eventEntry.getTime();
+ break;
case MOTION_DOWN:
case SET_END_TARGET:
case SET_END_TARGET_HOME:
case SET_END_TARGET_ALL_APPS:
case SET_END_TARGET_NEW_TASK:
- case START_RECENTS_ANIMATION:
case SET_ON_PAGE_TRANSITION_END_CALLBACK:
case CANCEL_CURRENT_ANIMATION:
case FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER:
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index d6a2e93..7103e63 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -266,6 +266,10 @@
time = System.currentTimeMillis();
duplicateCount = 0;
}
+
+ public long getTime() {
+ return time;
+ }
}
/** An entire log of entries associated with a single log ID */
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index d4923b8..e8c1a78 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -36,6 +36,16 @@
return false;
}
+ /** Return {@code true} if the Settings toggle is enabled. */
+ public boolean isSettingsNavHandleEnabled() {
+ return false;
+ }
+
+ /** Return {@code true} if the Settings toggle is enabled. */
+ public boolean isSettingsHomeButtonEnabled() {
+ return false;
+ }
+
/** Dump states. */
public void dump(String prefix, PrintWriter writer) {}
}
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.java b/quickstep/src/com/android/quickstep/util/BorderAnimator.java
deleted file mode 100644
index 7563187..0000000
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Copyright (C) 2023 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.util;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.ColorInt;
-import android.annotation.Nullable;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.view.View;
-import android.view.animation.Interpolator;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Px;
-
-import com.android.app.animation.Interpolators;
-import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.anim.AnimatorListeners;
-
-/**
- * Utility class for drawing a rounded-rect border around a view.
- * <p>
- * To use this class:
- * 1. Create an instance in the target view. NOTE: The border will animate outwards from the
- * provided border bounds. See {@link SimpleParams} and {@link ScalingParams} to determine
- * which would be best for your target view.
- * 2. Override the target view's {@link android.view.View#draw(Canvas)} method and call
- * {@link BorderAnimator#drawBorder(Canvas)} after {@code super.draw(canvas)}.
- * 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation or call
- * {@link BorderAnimator#setBorderVisible(boolean)} where appropriate.
- */
-public final class BorderAnimator {
-
- public static final int DEFAULT_BORDER_COLOR = Color.WHITE;
-
- private static final long DEFAULT_APPEARANCE_ANIMATION_DURATION_MS = 300;
- private static final long DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS = 133;
- private static final Interpolator DEFAULT_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE;
-
- @NonNull private final AnimatedFloat mBorderAnimationProgress = new AnimatedFloat(
- this::updateOutline);
- @Px private final int mBorderRadiusPx;
- @NonNull private final BorderAnimationParams mBorderAnimationParams;
- private final long mAppearanceDurationMs;
- private final long mDisappearanceDurationMs;
- @NonNull private final Interpolator mInterpolator;
- @NonNull private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- @Nullable private Animator mRunningBorderAnimation;
-
- public BorderAnimator(
- @Px int borderRadiusPx,
- @ColorInt int borderColor,
- @NonNull BorderAnimationParams borderAnimationParams) {
- this(borderRadiusPx,
- borderColor,
- borderAnimationParams,
- DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
- DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
- DEFAULT_INTERPOLATOR);
- }
-
- /**
- * @param borderRadiusPx the radius of the border's corners, in pixels
- * @param borderColor the border's color
- * @param borderAnimationParams params for handling different target view layout situation.
- * @param appearanceDurationMs appearance animation duration, in milliseconds
- * @param disappearanceDurationMs disappearance animation duration, in milliseconds
- * @param interpolator animation interpolator
- */
- public BorderAnimator(
- @Px int borderRadiusPx,
- @ColorInt int borderColor,
- @NonNull BorderAnimationParams borderAnimationParams,
- long appearanceDurationMs,
- long disappearanceDurationMs,
- @NonNull Interpolator interpolator) {
- mBorderRadiusPx = borderRadiusPx;
- mBorderAnimationParams = borderAnimationParams;
- mAppearanceDurationMs = appearanceDurationMs;
- mDisappearanceDurationMs = disappearanceDurationMs;
- mInterpolator = interpolator;
-
- mBorderPaint.setColor(borderColor);
- mBorderPaint.setStyle(Paint.Style.STROKE);
- mBorderPaint.setAlpha(0);
- }
-
- private void updateOutline() {
- float interpolatedProgress = mInterpolator.getInterpolation(
- mBorderAnimationProgress.value);
-
- mBorderAnimationParams.setProgress(interpolatedProgress);
- mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress));
- mBorderPaint.setStrokeWidth(mBorderAnimationParams.getBorderWidth());
- mBorderAnimationParams.mTargetView.invalidate();
- }
-
- /**
- * Draws the border on the given canvas.
- * <p>
- * Call this method in the target view's {@link android.view.View#draw(Canvas)} method after
- * calling super.
- */
- public void drawBorder(Canvas canvas) {
- float alignmentAdjustment = mBorderAnimationParams.getAlignmentAdjustment();
- canvas.drawRoundRect(
- /* left= */ mBorderAnimationParams.mBorderBounds.left + alignmentAdjustment,
- /* top= */ mBorderAnimationParams.mBorderBounds.top + alignmentAdjustment,
- /* right= */ mBorderAnimationParams.mBorderBounds.right - alignmentAdjustment,
- /* bottom= */ mBorderAnimationParams.mBorderBounds.bottom - alignmentAdjustment,
- /* rx= */ mBorderRadiusPx + mBorderAnimationParams.getRadiusAdjustment(),
- /* ry= */ mBorderRadiusPx + mBorderAnimationParams.getRadiusAdjustment(),
- /* paint= */ mBorderPaint);
- }
-
- /**
- * Builds the border appearance/disappearance animation.
- */
- @NonNull
- public Animator buildAnimator(boolean isAppearing) {
- mRunningBorderAnimation = mBorderAnimationProgress.animateToValue(isAppearing ? 1f : 0f);
- mRunningBorderAnimation.setDuration(
- isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs);
-
- mRunningBorderAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mBorderAnimationParams.onShowBorder();
- }
- });
- mRunningBorderAnimation.addListener(
- AnimatorListeners.forEndCallback(() -> {
- mRunningBorderAnimation = null;
- if (isAppearing) {
- return;
- }
- mBorderAnimationParams.onHideBorder();
- }));
-
- return mRunningBorderAnimation;
- }
-
- /**
- * Immediately shows/hides the border without an animation.
- * <p>
- * To animate the appearance/disappearance, see {@link BorderAnimator#buildAnimator(boolean)}
- */
- public void setBorderVisible(boolean visible) {
- if (mRunningBorderAnimation != null) {
- mRunningBorderAnimation.end();
- }
- if (visible) {
- mBorderAnimationParams.onShowBorder();
- }
- mBorderAnimationProgress.updateValue(visible ? 1f : 0f);
- if (!visible) {
- mBorderAnimationParams.onHideBorder();
- }
- }
-
- /**
- * Callback to update the border bounds when building this animation.
- */
- public interface BorderBoundsBuilder {
-
- /**
- * Sets the given rect to the most up-to-date bounds.
- */
- void updateBorderBounds(Rect rect);
- }
-
- /**
- * Params for handling different target view layout situation.
- */
- private abstract static class BorderAnimationParams {
-
- @NonNull private final Rect mBorderBounds = new Rect();
- @NonNull private final BorderBoundsBuilder mBoundsBuilder;
-
- @NonNull final View mTargetView;
- @Px final int mBorderWidthPx;
-
- private float mAnimationProgress = 0f;
- @Nullable private View.OnLayoutChangeListener mLayoutChangeListener;
-
- /**
- * @param borderWidthPx the width of the border, in pixels
- * @param boundsBuilder callback to update the border bounds
- * @param targetView the view that will be drawing the border
- */
- private BorderAnimationParams(
- @Px int borderWidthPx,
- @NonNull BorderBoundsBuilder boundsBuilder,
- @NonNull View targetView) {
- mBorderWidthPx = borderWidthPx;
- mBoundsBuilder = boundsBuilder;
- mTargetView = targetView;
- }
-
- private void setProgress(float progress) {
- mAnimationProgress = progress;
- }
-
- private float getBorderWidth() {
- return mBorderWidthPx * mAnimationProgress;
- }
-
- float getAlignmentAdjustment() {
- // Outset the border by half the width to create an outwards-growth animation
- return (-getBorderWidth() / 2f) + getAlignmentAdjustmentInset();
- }
-
-
- void onShowBorder() {
- if (mLayoutChangeListener == null) {
- mLayoutChangeListener =
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- onShowBorder();
- mTargetView.invalidate();
- };
- mTargetView.addOnLayoutChangeListener(mLayoutChangeListener);
- }
- mBoundsBuilder.updateBorderBounds(mBorderBounds);
- }
-
- void onHideBorder() {
- if (mLayoutChangeListener != null) {
- mTargetView.removeOnLayoutChangeListener(mLayoutChangeListener);
- mLayoutChangeListener = null;
- }
- }
-
- abstract int getAlignmentAdjustmentInset();
-
- abstract float getRadiusAdjustment();
- }
-
- /**
- * Use an instance of this {@link BorderAnimationParams} if the border can be drawn outside the
- * target view's bounds without any additional logic.
- */
- public static final class SimpleParams extends BorderAnimationParams {
-
- public SimpleParams(
- @Px int borderWidthPx,
- @NonNull BorderBoundsBuilder boundsBuilder,
- @NonNull View targetView) {
- super(borderWidthPx, boundsBuilder, targetView);
- }
-
- @Override
- int getAlignmentAdjustmentInset() {
- return 0;
- }
-
- @Override
- float getRadiusAdjustment() {
- return -getAlignmentAdjustment();
- }
- }
-
- /**
- * Use an instance of this {@link BorderAnimationParams} if the border would other be clipped by
- * the target view's bound.
- * <p>
- * Note: using these params will set the scales and pivots of the
- * container and content views, however will only reset the scales back to 1.
- */
- public static final class ScalingParams extends BorderAnimationParams {
-
- @NonNull private final View mContentView;
-
- /**
- * @param targetView the view that will be drawing the border. this view will be scaled up
- * to make room for the border
- * @param contentView the view around which the border will be drawn. this view will be
- * scaled down reciprocally to keep its original size and location.
- */
- public ScalingParams(
- @Px int borderWidthPx,
- @NonNull BorderBoundsBuilder boundsBuilder,
- @NonNull View targetView,
- @NonNull View contentView) {
- super(borderWidthPx, boundsBuilder, targetView);
- mContentView = contentView;
- }
-
- @Override
- void onShowBorder() {
- super.onShowBorder();
- float width = mTargetView.getWidth();
- float height = mTargetView.getHeight();
- // Scale up just enough to make room for the border. Fail fast and fix the scaling
- // onLayout.
- float scaleX = width == 0 ? 1f : 1f + ((2 * mBorderWidthPx) / width);
- float scaleY = height == 0 ? 1f : 1f + ((2 * mBorderWidthPx) / height);
-
- mTargetView.setPivotX(width / 2);
- mTargetView.setPivotY(height / 2);
- mTargetView.setScaleX(scaleX);
- mTargetView.setScaleY(scaleY);
-
- mContentView.setPivotX(mContentView.getWidth() / 2f);
- mContentView.setPivotY(mContentView.getHeight() / 2f);
- mContentView.setScaleX(1f / scaleX);
- mContentView.setScaleY(1f / scaleY);
- }
-
- @Override
- void onHideBorder() {
- super.onHideBorder();
- mTargetView.setPivotX(mTargetView.getWidth());
- mTargetView.setPivotY(mTargetView.getHeight());
- mTargetView.setScaleX(1f);
- mTargetView.setScaleY(1f);
-
- mContentView.setPivotX(mContentView.getWidth() / 2f);
- mContentView.setPivotY(mContentView.getHeight() / 2f);
- mContentView.setScaleX(1f);
- mContentView.setScaleY(1f);
- }
-
- @Override
- int getAlignmentAdjustmentInset() {
- // Inset the border since we are scaling the container up
- return mBorderWidthPx;
- }
-
- @Override
- float getRadiusAdjustment() {
- // Increase the radius since we are scaling the container up
- return getAlignmentAdjustment();
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
new file mode 100644
index 0000000..44eb070
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2023 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.util
+
+import android.animation.Animator
+import android.annotation.ColorInt
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.view.View
+import android.view.View.OnLayoutChangeListener
+import android.view.animation.Interpolator
+import androidx.annotation.Px
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import com.android.app.animation.Interpolators
+import com.android.launcher3.anim.AnimatedFloat
+import kotlin.math.roundToInt
+
+/**
+ * Utility class for drawing a rounded-rect border around a view.
+ *
+ * To use this class:
+ * 1. Create an instance in the target view. NOTE: The border will animate outwards from the
+ * provided border bounds.
+ * 2. Override the target view's [View.draw] method and call [drawBorder] after
+ * `super.draw(canvas)`.
+ * 3. Call [buildAnimator] and start the animation or call [setBorderVisibility] where appropriate.
+ */
+class BorderAnimator
+private constructor(
+ @field:Px @param:Px private val borderRadiusPx: Int,
+ @ColorInt borderColor: Int,
+ private val borderAnimationParams: BorderAnimationParams,
+ private val appearanceDurationMs: Long,
+ private val disappearanceDurationMs: Long,
+ private val interpolator: Interpolator,
+) {
+ private val borderAnimationProgress = AnimatedFloat { updateOutline() }
+ private val borderPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = borderColor
+ style = Paint.Style.STROKE
+ alpha = 0
+ }
+ private var runningBorderAnimation: Animator? = null
+
+ companion object {
+ const val DEFAULT_BORDER_COLOR = Color.WHITE
+ private const val DEFAULT_APPEARANCE_ANIMATION_DURATION_MS = 300L
+ private const val DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS = 133L
+ private val DEFAULT_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE
+
+ /**
+ * Creates a BorderAnimator that simply draws the border outside the bound of the target
+ * view.
+ *
+ * Use this method if the border can be drawn outside the target view's bounds without any
+ * additional logic.
+ *
+ * @param borderRadiusPx the radius of the border's corners, in pixels
+ * @param borderWidthPx the width of the border, in pixels
+ * @param boundsBuilder callback to update the border bounds
+ * @param targetView the view that will be drawing the border
+ * @param borderColor the border's color
+ * @param appearanceDurationMs appearance animation duration, in milliseconds
+ * @param disappearanceDurationMs disappearance animation duration, in milliseconds
+ * @param interpolator animation interpolator
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun createSimpleBorderAnimator(
+ @Px borderRadiusPx: Int,
+ @Px borderWidthPx: Int,
+ boundsBuilder: (rect: Rect?) -> Unit,
+ targetView: View,
+ @ColorInt borderColor: Int = DEFAULT_BORDER_COLOR,
+ appearanceDurationMs: Long = DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
+ disappearanceDurationMs: Long = DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
+ interpolator: Interpolator = DEFAULT_INTERPOLATOR,
+ ): BorderAnimator {
+ return BorderAnimator(
+ borderRadiusPx,
+ borderColor,
+ SimpleParams(borderWidthPx, boundsBuilder, targetView),
+ appearanceDurationMs,
+ disappearanceDurationMs,
+ interpolator,
+ )
+ }
+
+ /**
+ * Creates a BorderAnimator that scales the target and content views to draw the border
+ * within the target's bounds without obscuring the content.
+ *
+ * Use this method if the border would otherwise be clipped by the target view's bound.
+ *
+ * Note: using this method will set the scales and pivots of the container and content
+ * views, however will only reset the scales back to 1.
+ *
+ * @param borderRadiusPx the radius of the border's corners, in pixels
+ * @param borderWidthPx the width of the border, in pixels
+ * @param boundsBuilder callback to update the border bounds
+ * @param targetView the view that will be drawing the border
+ * @param contentView the view around which the border will be drawn. this view will be
+ * scaled down reciprocally to keep its original size and location.
+ * @param borderColor the border's color
+ * @param appearanceDurationMs appearance animation duration, in milliseconds
+ * @param disappearanceDurationMs disappearance animation duration, in milliseconds
+ * @param interpolator animation interpolator
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun createScalingBorderAnimator(
+ @Px borderRadiusPx: Int,
+ @Px borderWidthPx: Int,
+ boundsBuilder: (rect: Rect?) -> Unit,
+ targetView: View,
+ contentView: View,
+ @ColorInt borderColor: Int = DEFAULT_BORDER_COLOR,
+ appearanceDurationMs: Long = DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
+ disappearanceDurationMs: Long = DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
+ interpolator: Interpolator = DEFAULT_INTERPOLATOR,
+ ): BorderAnimator {
+ return BorderAnimator(
+ borderRadiusPx,
+ borderColor,
+ ScalingParams(borderWidthPx, boundsBuilder, targetView, contentView),
+ appearanceDurationMs,
+ disappearanceDurationMs,
+ interpolator,
+ )
+ }
+ }
+
+ private fun updateOutline() {
+ val interpolatedProgress = interpolator.getInterpolation(borderAnimationProgress.value)
+ borderAnimationParams.animationProgress = interpolatedProgress
+ borderPaint.alpha = (255 * interpolatedProgress).roundToInt()
+ borderPaint.strokeWidth = borderAnimationParams.borderWidth
+ borderAnimationParams.targetView.invalidate()
+ }
+
+ /**
+ * Draws the border on the given canvas.
+ *
+ * Call this method in the target view's [View.draw] method after calling super.
+ */
+ fun drawBorder(canvas: Canvas) {
+ with(borderAnimationParams) {
+ val radius = borderRadiusPx + radiusAdjustment
+ canvas.drawRoundRect(
+ /* left= */ borderBounds.left + alignmentAdjustment,
+ /* top= */ borderBounds.top + alignmentAdjustment,
+ /* right= */ borderBounds.right - alignmentAdjustment,
+ /* bottom= */ borderBounds.bottom - alignmentAdjustment,
+ /* rx= */ radius,
+ /* ry= */ radius,
+ /* paint= */ borderPaint
+ )
+ }
+ }
+
+ /** Builds the border appearance/disappearance animation. */
+ fun buildAnimator(isAppearing: Boolean): Animator {
+ return borderAnimationProgress.animateToValue(if (isAppearing) 1f else 0f).apply {
+ duration = if (isAppearing) appearanceDurationMs else disappearanceDurationMs
+ doOnStart {
+ runningBorderAnimation?.cancel()
+ runningBorderAnimation = this
+ borderAnimationParams.onShowBorder()
+ }
+ doOnEnd {
+ runningBorderAnimation = null
+ if (!isAppearing) {
+ borderAnimationParams.onHideBorder()
+ }
+ }
+ }
+ }
+
+ /** Shows/hides the border, optionally with an animation. */
+ fun setBorderVisibility(visible: Boolean, animated: Boolean) {
+ if (animated) {
+ buildAnimator(visible).start()
+ return
+ }
+ runningBorderAnimation?.end()
+ if (visible) {
+ borderAnimationParams.onShowBorder()
+ }
+ borderAnimationProgress.updateValue(if (visible) 1f else 0f)
+ if (!visible) {
+ borderAnimationParams.onHideBorder()
+ }
+ }
+
+ /** Params for handling different target view layout situations. */
+ private abstract class BorderAnimationParams(
+ @field:Px @param:Px val borderWidthPx: Int,
+ private val boundsBuilder: (rect: Rect) -> Unit,
+ val targetView: View,
+ ) {
+ val borderBounds = Rect()
+ var animationProgress = 0f
+ private var layoutChangeListener: OnLayoutChangeListener? = null
+
+ abstract val alignmentAdjustmentInset: Int
+ abstract val radiusAdjustment: Float
+
+ val borderWidth: Float
+ get() = borderWidthPx * animationProgress
+ val alignmentAdjustment: Float
+ // Outset the border by half the width to create an outwards-growth animation
+ get() = -borderWidth / 2f + alignmentAdjustmentInset
+
+ open fun onShowBorder() {
+ if (layoutChangeListener == null) {
+ layoutChangeListener = OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ onShowBorder()
+ targetView.invalidate()
+ }
+ targetView.addOnLayoutChangeListener(layoutChangeListener)
+ }
+ boundsBuilder(borderBounds)
+ }
+
+ open fun onHideBorder() {
+ if (layoutChangeListener != null) {
+ targetView.removeOnLayoutChangeListener(layoutChangeListener)
+ layoutChangeListener = null
+ }
+ }
+ }
+
+ /** BorderAnimationParams that simply draws the border outside the bounds of the target view. */
+ private class SimpleParams(
+ @Px borderWidthPx: Int,
+ boundsBuilder: (rect: Rect?) -> Unit,
+ targetView: View,
+ ) : BorderAnimationParams(borderWidthPx, boundsBuilder, targetView) {
+ override val alignmentAdjustmentInset = 0
+ override val radiusAdjustment: Float
+ get() = -alignmentAdjustment
+ }
+
+ /**
+ * BorderAnimationParams that scales the target and content views to draw the border within the
+ * target's bounds without obscuring the content.
+ */
+ private class ScalingParams(
+ @Px borderWidthPx: Int,
+ boundsBuilder: (rect: Rect?) -> Unit,
+ targetView: View,
+ private val contentView: View,
+ ) : BorderAnimationParams(borderWidthPx, boundsBuilder, targetView) {
+ // Inset the border since we are scaling the container up
+ override val alignmentAdjustmentInset = borderWidthPx
+ override val radiusAdjustment: Float
+ // Increase the radius since we are scaling the container up
+ get() = alignmentAdjustment
+
+ override fun onShowBorder() {
+ super.onShowBorder()
+ val tvWidth = targetView.width.toFloat()
+ val tvHeight = targetView.height.toFloat()
+ // Scale up just enough to make room for the border. Fail fast and fix the scaling
+ // onLayout.
+ val newScaleX = if (tvWidth == 0f) 1f else 1f + 2 * borderWidthPx / tvWidth
+ val newScaleY = if (tvHeight == 0f) 1f else 1f + 2 * borderWidthPx / tvHeight
+ with(targetView) {
+ pivotX = width / 2f
+ pivotY = height / 2f
+ scaleX = newScaleX
+ scaleY = newScaleY
+ }
+ with(contentView) {
+ pivotX = width / 2f
+ pivotY = height / 2f
+ scaleX = 1f / newScaleX
+ scaleY = 1f / newScaleY
+ }
+ }
+
+ override fun onHideBorder() {
+ super.onHideBorder()
+ with(targetView) {
+ pivotX = width.toFloat()
+ pivotY = height.toFloat()
+ scaleX = 1f
+ scaleY = 1f
+ }
+ with(contentView) {
+ pivotX = width / 2f
+ pivotY = height / 2f
+ scaleX = 1f
+ scaleY = 1f
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index c3774eb..689402b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -34,12 +34,12 @@
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
import com.android.launcher3.views.BaseDragLayer
import com.android.quickstep.views.FloatingTaskView
-import com.android.quickstep.views.IconView
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.SplitInstructionsView
import com.android.quickstep.views.TaskThumbnailView
import com.android.quickstep.views.TaskView
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.quickstep.views.TaskViewIcon
import java.util.function.Supplier
/**
@@ -82,7 +82,7 @@
return SplitAnimInitProps(container.thumbnailView,
container.thumbnailView.thumbnail, drawable!!,
fadeWithThumbnail = true, isStagedTask = true,
- iconView = container.iconView
+ iconView = container.iconView.asView()
)
}
}
@@ -94,7 +94,7 @@
val drawable = getDrawable(taskView.iconView, splitSelectSource)
return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail,
drawable!!, fadeWithThumbnail = true, isStagedTask = true,
- taskView.iconView
+ taskView.iconView.asView()
)
}
}
@@ -105,7 +105,7 @@
* TaskView's icon drawable can be null if the TaskView is scrolled far enough off screen
* @return [Drawable]
*/
- fun getDrawable(iconView: IconView, splitSelectSource: SplitSelectSource?) : Drawable? {
+ fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?) : Drawable? {
if (iconView.drawable == null && splitSelectSource != null) {
return splitSelectSource.drawable
}
@@ -129,7 +129,7 @@
taskViewWidth: Int, taskViewHeight: Int,
isPrimaryTaskSplitting: Boolean) {
val thumbnail = taskIdAttributeContainer.thumbnailView
- val iconView: View = taskIdAttributeContainer.iconView
+ val iconView: View = taskIdAttributeContainer.iconView.asView()
builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
thumbnail.setShowSplashForSplitSelection(true)
if (deviceProfile.isLandscape) {
diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
new file mode 100644
index 0000000..79ca076
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 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.util;
+
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
+
+/**
+ * A class to cache task id and its corresponding object (e.g. thumbnail)
+ *
+ * <p>Maximum size of the cache should be provided when creating this class. When the number of
+ * entries is larger than its max size, it would remove the entry with the smallest last active time
+ * @param <V> Type of object stored in the cache
+ */
+public class TaskKeyByLastActiveTimeCache<V> implements TaskKeyCache<V> {
+ private static final String TAG = TaskKeyByLastActiveTimeCache.class.getSimpleName();
+ private final AtomicInteger mMaxSize;
+ private final Map<Integer, Entry<V>> mMap;
+ // To sort task id by last active time
+ private final PriorityQueue<Task.TaskKey> mQueue;
+
+ public TaskKeyByLastActiveTimeCache(int maxSize) {
+ mMap = new HashMap(maxSize);
+ mQueue = new PriorityQueue<>(Comparator.comparingLong(t -> t.lastActiveTime));
+ mMaxSize = new AtomicInteger(maxSize);
+ }
+
+ /**
+ * Removes all entries from the cache
+ */
+ @Override
+ public synchronized void evictAll() {
+ mMap.clear();
+ mQueue.clear();
+ }
+
+
+ /**
+ * Removes a particular entry from the cache
+ */
+ @Override
+ public synchronized void remove(Task.TaskKey key) {
+ if (key == null) {
+ return;
+ }
+
+ Entry<V> entry = mMap.remove(key.id);
+ if (entry != null) {
+ // Use real key in map entry to handle use case of using stub key for removal
+ mQueue.remove(entry.mKey);
+ }
+ }
+
+ /**
+ * Removes all entries matching keyCheck
+ */
+ @Override
+ public synchronized void removeAll(Predicate<Task.TaskKey> keyCheck) {
+ Iterator<Task.TaskKey> iterator = mQueue.iterator();
+ while (iterator.hasNext()) {
+ Task.TaskKey key = iterator.next();
+ if (keyCheck.test(key)) {
+ mMap.remove(key.id);
+ iterator.remove();
+ }
+ }
+ }
+
+ /**
+ * Gets the entry if it is still valid
+ */
+ @Override
+ public synchronized V getAndInvalidateIfModified(Task.TaskKey key) {
+ Entry<V> entry = mMap.get(key.id);
+ if (entry != null && entry.mKey.windowingMode == key.windowingMode
+ && entry.mKey.lastActiveTime == key.lastActiveTime) {
+ return entry.mValue;
+ } else {
+ remove(key);
+ return null;
+ }
+ }
+
+ /**
+ * Adds an entry to the cache, optionally evicting the last accessed entry
+ */
+ @Override
+ public final synchronized void put(Task.TaskKey key, V value) {
+ if (key != null && value != null) {
+ Entry<V> entry = mMap.get(key.id);
+ // If the same key already exist, remove item for existing key
+ if (entry != null) {
+ mQueue.remove(entry.mKey);
+ }
+
+ mMap.put(key.id, new Entry<>(key, value));
+ mQueue.add(key);
+ removeExcessIfNeeded();
+ } else {
+ Log.e(TAG, "Unexpected null key or value: " + key + ", " + value);
+ }
+ }
+
+ /**
+ * Updates the cache entry if it is already present in the cache
+ */
+ @Override
+ public synchronized void updateIfAlreadyInCache(int taskId, V data) {
+ Entry<V> entry = mMap.get(taskId);
+ if (entry != null) {
+ entry.mValue = data;
+ }
+ }
+
+ /**
+ * Updates cache size and remove excess if the number of existing entries is larger than new
+ * cache size
+ */
+ @Override
+ public synchronized void updateCacheSizeAndRemoveExcess(int cacheSize) {
+ mMaxSize.compareAndSet(mMaxSize.get(), cacheSize);
+ removeExcessIfNeeded();
+ }
+
+ private synchronized void removeExcessIfNeeded() {
+ while (mQueue.size() > mMaxSize.get() && !mQueue.isEmpty()) {
+ Task.TaskKey key = mQueue.poll();
+ mMap.remove(key.id);
+ }
+ }
+
+ /**
+ * Get maximum size of the cache
+ */
+ @Override
+ public int getMaxSize() {
+ return mMaxSize.get();
+ }
+
+ /**
+ * Get current size of the cache
+ */
+ @Override
+ public int getSize() {
+ return mMap.size();
+ }
+
+ @VisibleForTesting
+ PriorityQueue<Task.TaskKey> getQueue() {
+ return mQueue;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyCache.java
new file mode 100644
index 0000000..8ee78ab
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TaskKeyCache.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 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.util;
+
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.function.Predicate;
+
+/**
+ * An interface for caching task id and its corresponding object (e.g. thumbnail, task icon)
+ *
+ * @param <V> Type of object stored in the cache
+ */
+public interface TaskKeyCache<V> {
+
+ /**
+ * Removes all entries from the cache.
+ */
+ void evictAll();
+
+ /**
+ * Removes a particular entry from the cache.
+ */
+ void remove(Task.TaskKey key);
+
+ /**
+ * Removes all entries matching keyCheck.
+ */
+ void removeAll(Predicate<Task.TaskKey> keyCheck);
+
+ /**
+ * Gets the entry if it is still valid.
+ */
+ V getAndInvalidateIfModified(Task.TaskKey key);
+
+ /**
+ * Adds an entry to the cache, optionally evicting the last accessed entry.
+ */
+ void put(Task.TaskKey key, V value);
+
+ /**
+ * Updates the cache entry if it is already present in the cache.
+ */
+ void updateIfAlreadyInCache(int taskId, V data);
+
+ /**
+ * Updates cache size and remove excess if the number of existing entries is larger than new
+ * cache size.
+ */
+ default void updateCacheSizeAndRemoveExcess(int cacheSize) { }
+
+ /**
+ * Gets maximum size of the cache.
+ */
+ int getMaxSize();
+
+ /**
+ * Gets current size of the cache.
+ */
+ int getSize();
+
+ class Entry<V> {
+
+ final Task.TaskKey mKey;
+ V mValue;
+
+ Entry(Task.TaskKey key, V value) {
+ mKey = key;
+ mValue = value;
+ }
+
+ @Override
+ public int hashCode() {
+ return mKey.id;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java
index 08a65fa..89f5d41 100644
--- a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java
+++ b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java
@@ -27,7 +27,7 @@
* A simple LRU cache for task key entries
* @param <V> The type of the value
*/
-public class TaskKeyLruCache<V> {
+public class TaskKeyLruCache<V> implements TaskKeyCache<V> {
private final MyLinkedHashMap<V> mMap;
@@ -92,20 +92,14 @@
}
}
- private static class Entry<V> {
+ @Override
+ public int getMaxSize() {
+ return mMap.mMaxSize;
+ }
- final TaskKey mKey;
- V mValue;
-
- Entry(TaskKey key, V value) {
- mKey = key;
- mValue = value;
- }
-
- @Override
- public int hashCode() {
- return mKey.id;
- }
+ @Override
+ public int getSize() {
+ return mMap.size();
}
private static class MyLinkedHashMap<V> extends LinkedHashMap<Integer, Entry<V>> {
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 5135345..dc6b5a2 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -44,10 +44,10 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsModel;
-import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
@@ -62,6 +62,8 @@
import java.util.List;
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* TaskView that contains all tasks that are part of the desktop.
*/
@@ -142,9 +144,10 @@
}
@Override
- protected void updateBorderBounds(Rect bounds) {
+ protected Unit updateBorderBounds(@NonNull Rect bounds) {
bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(), mBackgroundView.getRight(),
mBackgroundView.getBottom());
+ return Unit.INSTANCE;
}
@Override
@@ -327,21 +330,25 @@
}
@Override
- protected boolean showTaskMenuWithContainer(IconView iconView) {
+ protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) {
return false;
}
- @Override
- public RunnableList launchTasks() {
- SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps(mActivity.getDisplayId());
- Launcher.getLauncher(mActivity).getStateManager().goToState(NORMAL, false /* animated */);
- return null;
- }
-
@Nullable
@Override
public RunnableList launchTaskAnimated() {
- return launchTasks();
+ RunnableList endCallback = new RunnableList();
+ endCallback.add(() -> Launcher.getLauncher(mActivity).getStateManager().goToState(NORMAL));
+
+ DesktopRecentsTransitionController recentsController =
+ getRecentsView().getDesktopRecentsController();
+ if (recentsController != null) {
+ recentsController.launchDesktopFromRecents(this, success -> {
+ endCallback.executeAllAndDestroy();
+ });
+ }
+
+ return endCallback;
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 7e58763..3d33c87 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -2,6 +2,7 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell;
@@ -11,6 +12,7 @@
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewStub;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -25,6 +27,7 @@
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskThumbnailCache;
+import com.android.quickstep.TaskUtils;
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitSelectStateController;
@@ -37,6 +40,8 @@
import java.util.HashMap;
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
*
@@ -52,7 +57,7 @@
@Nullable
private Task mSecondaryTask;
private TaskThumbnailView mSnapshotView2;
- private IconView mIconView2;
+ private TaskViewIcon mIconView2;
@Nullable
private CancellableTask<ThumbnailData> mThumbnailLoadRequest2;
@Nullable
@@ -76,10 +81,10 @@
}
@Override
- protected void updateBorderBounds(Rect bounds) {
+ protected Unit updateBorderBounds(@NonNull Rect bounds) {
if (mSplitBoundsConfig == null) {
super.updateBorderBounds(bounds);
- return;
+ return Unit.INSTANCE;
}
bounds.set(
Math.min(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()),
@@ -90,14 +95,21 @@
mSnapshotView2.getRight() + Math.round(mSnapshotView2.getTranslationX())),
Math.max(mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()),
mSnapshotView2.getBottom() + Math.round(mSnapshotView2.getTranslationY())));
+ return Unit.INSTANCE;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mSnapshotView2 = findViewById(R.id.bottomright_snapshot);
- mIconView2 = findViewById(R.id.bottomRight_icon);
- mIcon2TouchDelegate = new TransformingTouchDelegate(mIconView2);
+ ViewStub iconViewStub2 = findViewById(R.id.bottomRight_icon);
+ if (enableOverviewIconMenu()) {
+ iconViewStub2.setLayoutResource(R.layout.icon_app_chip_view);
+ } else {
+ iconViewStub2.setLayoutResource(R.layout.icon_view);
+ }
+ mIconView2 = (TaskViewIcon) iconViewStub2.inflate();
+ mIcon2TouchDelegate = new TransformingTouchDelegate(mIconView2.asView());
}
public void bind(Task primary, Task secondary, RecentsOrientedState orientedState,
@@ -157,6 +169,7 @@
mIconLoadRequest2 = iconCache.updateIconInBackground(mSecondaryTask,
(task) -> {
setIcon(mIconView2, task.icon);
+ setText(mIconView2, TaskUtils.getTitle(getContext(), task));
mDigitalWellBeingToast2.initialize(mSecondaryTask);
mDigitalWellBeingToast2.setSplitConfiguration(mSplitBoundsConfig);
mDigitalWellBeingToast.setSplitConfiguration(mSplitBoundsConfig);
@@ -171,6 +184,7 @@
}
if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
setIcon(mIconView2, null);
+ setText(mIconView2, null);
}
}
}
@@ -302,7 +316,7 @@
}
// Check which of the two apps was selected
- if (isCoordInView(mIconView2, mLastTouchDownPosition)
+ if (isCoordInView(mIconView2.asView(), mLastTouchDownPosition)
|| isCoordInView(mSnapshotView2, mLastTouchDownPosition)) {
return 1;
}
@@ -368,10 +382,7 @@
super.setOrientationState(orientationState);
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
boolean isGridTask = deviceProfile.isTablet && !isFocusedTask();
- int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx
- : deviceProfile.overviewTaskIconDrawableSizePx;
- mIconView2.setDrawableSize(iconDrawableSize, iconDrawableSize);
- mIconView2.setRotation(getPagedOrientationHandler().getDegreesRotated());
+ mIconView2.setIconOrientation(orientationState, isGridTask);
updateIconPlacement();
updateSecondaryDwbPlacement();
}
@@ -385,7 +396,7 @@
int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- getPagedOrientationHandler().setSplitIconParams(mIconView, mIconView2,
+ getPagedOrientationHandler().setSplitIconParams(mIconView.asView(), mIconView2.asView(),
taskIconHeight, mSnapshotView.getMeasuredWidth(), mSnapshotView.getMeasuredHeight(),
getMeasuredHeight(), getMeasuredWidth(), isRtl, deviceProfile,
mSplitBoundsConfig);
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
new file mode 100644
index 0000000..960a09e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2023 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 com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.touch.LandscapePagedViewHandler;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.SeascapePagedViewHandler;
+import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.util.RecentsOrientedState;
+
+/**
+ * An icon app menu view which can be used in place of an IconView in overview TaskViews.
+ */
+public class IconAppChipView extends FrameLayout implements TaskViewIcon {
+
+ private static final int MENU_BACKGROUND_REVEAL_DURATION = 417;
+ private static final int MENU_BACKGROUND_HIDE_DURATION = 333;
+
+ private IconView mIconView;
+ private TextView mIconTextView;
+ private ImageView mIconArrowView;
+ private ImageView mIconViewBackground;
+
+ private int mMaxIconBackgroundWidth;
+ private int mMinIconBackgroundWidth;
+ private int mMaxIconBackgroundHeight;
+ private int mMinIconBackgroundHeight;
+ private int mIconTextMinWidth;
+ private int mIconTextMaxWidth;
+ private int mInnerMargin;
+ private int mIconArrowSize;
+ private int mIconMenuMarginStart;
+ private int mArrowMaxTranslationX;
+
+ public IconAppChipView(Context context) {
+ this(context, null);
+ }
+
+ public IconAppChipView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public IconAppChipView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public IconAppChipView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mMaxIconBackgroundWidth = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_max_width);
+ mMinIconBackgroundWidth = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_min_width);
+ mMaxIconBackgroundHeight = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_max_height);
+ mMinIconBackgroundHeight = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_min_height);
+ mIconTextMaxWidth = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_text_max_width);
+ mInnerMargin = (int) getResources().getDimension(
+ R.dimen.task_thumbnail_icon_menu_arrow_margin);
+ mIconTextMinWidth = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_text_width) + (2 * mInnerMargin);
+ int taskIconHeight = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_drawable_touch_size);
+ int arrowWidth = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_arrow_size);
+ mIconArrowSize = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_arrow_drawable_size);
+ mIconMenuMarginStart = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_margin);
+ mArrowMaxTranslationX =
+ mMaxIconBackgroundWidth - taskIconHeight - mIconTextMaxWidth + arrowWidth;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mIconView = findViewById(R.id.icon_view);
+ mIconTextView = findViewById(R.id.icon_text);
+ mIconArrowView = findViewById(R.id.icon_arrow);
+ mIconViewBackground = findViewById(R.id.icon_view_background);
+ }
+
+ protected IconView getIconView() {
+ return mIconView;
+ }
+
+ @Override
+ public void setText(CharSequence text) {
+ if (mIconTextView != null) {
+ mIconTextView.setText(text);
+ }
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return mIconView == null ? null : mIconView.getDrawable();
+ }
+
+ @Override
+ public void setDrawable(Drawable icon) {
+ if (mIconView != null) {
+ mIconView.setDrawable(icon);
+ }
+ }
+
+ @Override
+ public void setDrawableSize(int iconWidth, int iconHeight) {
+ if (mIconView != null) {
+ mIconView.setDrawableSize(iconWidth, iconHeight);
+ }
+ }
+
+ @Override
+ public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) {
+
+ PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ DeviceProfile deviceProfile =
+ ActivityContext.lookupContext(getContext()).getDeviceProfile();
+
+ int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+ int taskIconSize = deviceProfile.overviewTaskIconSizePx;
+ int taskMargin = deviceProfile.overviewTaskMarginPx;
+
+ LayoutParams iconMenuParams = (LayoutParams) getLayoutParams();
+ orientationHandler.setTaskIconMenuParams(iconMenuParams, mIconMenuMarginStart,
+ thumbnailTopMargin);
+ iconMenuParams.width = mMinIconBackgroundWidth;
+ iconMenuParams.height = taskIconSize;
+ if (orientationHandler instanceof SeascapePagedViewHandler) {
+ // Use half menu height to place the pivot within the X/Y center of icon in the menu.
+ setPivotX(getHeight() / 2f);
+ setPivotY(getHeight() / 2f - mIconMenuMarginStart);
+ } else if (orientationHandler instanceof LandscapePagedViewHandler) {
+ setPivotX(getWidth());
+ setPivotY(0);
+ }
+ // Pivot not updated for PortraitPagedViewHandler case, as it has 0 rotation.
+
+ setTranslationY(0);
+ setRotation(orientationHandler.getDegreesRotated());
+
+ LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
+ orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconSize,
+ thumbnailTopMargin, isRtl);
+ iconParams.width = iconParams.height = taskIconSize;
+ iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
+ mIconView.setLayoutParams(iconParams);
+
+ int iconDrawableSize = enableOverviewIconMenu()
+ ? deviceProfile.overviewTaskIconAppChipMenuDrawableSizePx
+ : isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx
+ : deviceProfile.overviewTaskIconDrawableSizePx;
+ mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize);
+
+ LayoutParams iconTextParams = (LayoutParams) mIconTextView.getLayoutParams();
+ orientationHandler.setTaskIconParams(iconTextParams, 0, taskIconSize,
+ thumbnailTopMargin, isRtl);
+ iconTextParams.width = mIconTextMaxWidth;
+ iconTextParams.height = taskIconSize;
+ iconTextParams.setMarginStart(taskIconSize);
+ iconTextParams.topMargin = (getHeight() - mIconTextView.getHeight()) / 2;
+ iconTextParams.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
+ mIconTextView.setLayoutParams(iconTextParams);
+ mIconTextView.setRevealClip(true, 0, taskIconSize / 2f, mIconTextMinWidth);
+
+ LayoutParams iconArrowParams = (LayoutParams) mIconArrowView.getLayoutParams();
+ iconArrowParams.gravity = Gravity.CENTER_VERTICAL | Gravity.END;
+ iconArrowParams.setMarginStart(taskIconSize + mIconTextMinWidth);
+ iconArrowParams.setMarginEnd(mInnerMargin);
+ mIconArrowView.setLayoutParams(iconArrowParams);
+ mIconArrowView.getDrawable().setBounds(0, 0, mIconArrowSize, mIconArrowSize);
+
+ LayoutParams backgroundParams = (LayoutParams) mIconViewBackground.getLayoutParams();
+ backgroundParams.width = mMinIconBackgroundWidth;
+ backgroundParams.height = taskIconSize;
+ mIconViewBackground.setPivotX(
+ isRtl ? mMinIconBackgroundWidth - (taskIconSize / 2f - mInnerMargin)
+ : taskIconSize / 2f - mInnerMargin);
+ mIconViewBackground.setPivotY(taskIconSize / 2f);
+
+ requestLayout();
+ }
+
+ @Override
+ public void setIconColorTint(int color, float amount) {
+ if (mIconView != null) {
+ mIconView.setIconColorTint(color, amount);
+ }
+ }
+
+ @Override
+ public int getDrawableWidth() {
+ return mIconView == null ? 0 : mIconView.getDrawableWidth();
+ }
+
+ @Override
+ public int getDrawableHeight() {
+ return mIconView == null ? 0 : mIconView.getDrawableHeight();
+ }
+
+ protected void revealAnim(boolean isRevealing) {
+ if (isRevealing) {
+ ((AnimatedVectorDrawable) mIconArrowView.getDrawable()).start();
+ AnimatorSet anim = new AnimatorSet();
+ anim.playTogether(
+ ViewAnimationUtils.createCircularReveal(mIconTextView, 0,
+ mIconTextView.getHeight() / 2, mIconTextMinWidth, mIconTextMaxWidth),
+ ObjectAnimator.ofFloat(mIconViewBackground, SCALE_X,
+ mMaxIconBackgroundWidth / (float) mMinIconBackgroundWidth),
+ ObjectAnimator.ofFloat(mIconViewBackground, SCALE_Y,
+ mMaxIconBackgroundHeight / (float) mMinIconBackgroundHeight),
+ ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X,
+ isLayoutRtl() ? -mArrowMaxTranslationX : mArrowMaxTranslationX));
+ anim.setDuration(MENU_BACKGROUND_REVEAL_DURATION);
+ anim.setInterpolator(EMPHASIZED);
+ anim.start();
+ } else {
+ ((AnimatedVectorDrawable) mIconArrowView.getDrawable()).reverse();
+ AnimatorSet anim = new AnimatorSet();
+ Animator textRevealAnim = ViewAnimationUtils.createCircularReveal(mIconTextView, 0,
+ mIconTextView.getHeight() / 2, mIconTextMaxWidth, mIconTextMinWidth);
+ textRevealAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // createCircularReveal removes clip on finish, restore it here to clip text.
+ mIconTextView.setRevealClip(true, 0, mIconTextView.getHeight() / 2f,
+ mIconTextMinWidth);
+ }
+ });
+ anim.playTogether(
+ textRevealAnim,
+ ObjectAnimator.ofFloat(mIconViewBackground, SCALE_X, 1),
+ ObjectAnimator.ofFloat(mIconViewBackground, SCALE_Y, 1),
+ ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, 0));
+ anim.setDuration(MENU_BACKGROUND_HIDE_DURATION);
+ anim.setInterpolator(EMPHASIZED);
+ anim.start();
+ }
+ }
+
+ @Override
+ public View asView() {
+ return this;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
index 5895c05..222f9ca 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.views;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
+
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -22,16 +24,21 @@
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.widget.FrameLayout;
import androidx.annotation.Nullable;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.util.RecentsOrientedState;
/**
* A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
* when the drawable changes.
*/
-public class IconView extends View {
+public class IconView extends View implements TaskViewIcon {
@Nullable
private Drawable mDrawable;
@@ -52,6 +59,7 @@
/**
* Sets a {@link Drawable} to be displayed.
*/
+ @Override
public void setDrawable(@Nullable Drawable d) {
if (mDrawable != null) {
mDrawable.setCallback(null);
@@ -67,6 +75,7 @@
/**
* Sets the size of the icon drawable.
*/
+ @Override
public void setDrawableSize(int iconWidth, int iconHeight) {
mDrawableWidth = iconWidth;
mDrawableHeight = iconHeight;
@@ -82,15 +91,18 @@
mDrawable.setBounds(drawableRect);
}
+ @Override
@Nullable
public Drawable getDrawable() {
return mDrawable;
}
+ @Override
public int getDrawableWidth() {
return mDrawableWidth;
}
+ @Override
public int getDrawableHeight() {
return mDrawableHeight;
}
@@ -147,9 +159,41 @@
* @param color to blend in.
* @param amount [0,1] 0 no tint, 1 full tint
*/
+ @Override
public void setIconColorTint(int color, float amount) {
if (mDrawable != null) {
mDrawable.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount));
}
}
+
+ @Override
+ public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) {
+ PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ DeviceProfile deviceProfile =
+ ActivityContext.lookupContext(getContext()).getDeviceProfile();
+
+ FrameLayout.LayoutParams iconParams = (FrameLayout.LayoutParams) getLayoutParams();
+
+ int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+ int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
+ int taskMargin = deviceProfile.overviewTaskMarginPx;
+
+ orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight,
+ thumbnailTopMargin, isRtl);
+ iconParams.width = iconParams.height = taskIconHeight;
+ setLayoutParams(iconParams);
+
+ setRotation(orientationHandler.getDegreesRotated());
+ int iconDrawableSize = enableOverviewIconMenu()
+ ? deviceProfile.overviewTaskIconAppChipMenuDrawableSizePx
+ : isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx
+ : deviceProfile.overviewTaskIconDrawableSizePx;
+ setDrawableSize(iconDrawableSize, iconDrawableSize);
+ }
+
+ @Override
+ public View asView() {
+ return this;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 892ebf9..1867fe9 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -37,6 +37,7 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.LauncherState;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
@@ -75,8 +76,9 @@
@Override
public void init(OverviewActionsView actionsView,
- SplitSelectStateController splitPlaceholderView) {
- super.init(actionsView, splitPlaceholderView);
+ SplitSelectStateController splitPlaceholderView,
+ @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
+ super.init(actionsView, splitPlaceholderView, desktopRecentsTransitionController);
setContentAlpha(0);
}
@@ -276,7 +278,8 @@
desktopVisibilityController.setRecentsGestureEnd(endTarget);
}
if (showDesktopApps) {
- SystemUiProxy.INSTANCE.get(mActivity).showDesktopApps(mActivity.getDisplayId());
+ SystemUiProxy.INSTANCE.get(mActivity).showDesktopApps(mActivity.getDisplayId(),
+ null /* transition */);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 9b41ccd..825c0ae 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -143,6 +143,7 @@
import com.android.launcher3.anim.SpringProperty;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statehandlers.DepthController;
@@ -722,6 +723,9 @@
private ObjectAnimator mActionsViewAlphaAnimator;
private float mActionsViewAlphaAnimatorFinalValue;
+ @Nullable
+ private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
+
/**
* Keeps track of the desktop task. Optional and only present when the feature flag is enabled.
*/
@@ -1042,10 +1046,12 @@
updateTaskStackListenerState();
}
- public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
+ public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
+ @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
mActionsView = actionsView;
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
mSplitSelectStateController = splitController;
+ mDesktopRecentsTransitionController = desktopRecentsTransitionController;
}
public SplitSelectStateController getSplitSelectController() {
@@ -2271,10 +2277,12 @@
if (showAsGrid()) {
int screenStart = mOrientationHandler.getPrimaryScroll(this);
int pageOrientedSize = mOrientationHandler.getMeasuredSize(this);
- int halfScreenSize = pageOrientedSize / 2;
- // Use +/- 50% screen width as visible area.
- visibleStart = screenStart - halfScreenSize;
- visibleEnd = screenStart + pageOrientedSize + halfScreenSize;
+ // For GRID_ONLY_OVERVIEW, use +/- 1 task column as visible area for preloading
+ // adjacent thumbnails, otherwise use +/-50% screen width
+ int extraWidth = enableGridOnlyOverview() ? getLastComputedTaskSize().width()
+ + getPageSpacing() : pageOrientedSize / 2;
+ visibleStart = screenStart - extraWidth;
+ visibleEnd = screenStart + pageOrientedSize + extraWidth;
} else {
int centerPageIndex = getPageNearestToCenterOfScreen();
int numChildren = getChildCount();
@@ -2355,6 +2363,12 @@
@Override
public void onHighResLoadingStateChanged(boolean enabled) {
+ // Preload cache when no overview task is visible (e.g. not in overview page), so when
+ // user goes to overview next time, the task thumbnails would show up without delay
+ if (mHasVisibleTaskData.size() == 0) {
+ mModel.preloadCacheIfNeeded();
+ }
+
// Whenever the high res loading state changes, poke each of the visible tasks to see if
// they want to updated their thumbnail state
for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
@@ -5787,6 +5801,11 @@
return null;
}
+ @Nullable
+ protected DesktopRecentsTransitionController getDesktopRecentsController() {
+ return mDesktopRecentsTransitionController;
+ }
+
/** Enables or disables modal state for RecentsView */
public abstract void setModalStateEnabled(int taskId, boolean animate);
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index b4d24e5..62c0bef 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,6 +16,8 @@
package com.android.quickstep.views;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
import android.animation.Animator;
@@ -24,6 +26,7 @@
import android.content.Context;
import android.graphics.Outline;
import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.util.AttributeSet;
@@ -58,16 +61,19 @@
private static final Rect sTempRect = new Rect();
- private static final int REVEAL_OPEN_DURATION = 150;
- private static final int REVEAL_CLOSE_DURATION = 100;
+ private static final int REVEAL_OPEN_DURATION = enableOverviewIconMenu() ? 417 : 150;
+ private static final int REVEAL_CLOSE_DURATION = enableOverviewIconMenu() ? 333 : 100;
private BaseDraggingActivity mActivity;
private TextView mTaskName;
@Nullable
private AnimatorSet mOpenCloseAnimator;
+ @Nullable private Runnable mOnClosingStartCallback;
private TaskView mTaskView;
private TaskIdAttributeContainer mTaskContainer;
private LinearLayout mOptionLayout;
+ private float mMenuTranslationYBeforeOpen;
+ private float mIconViewTranslationYBeforeOpen;
public TaskMenuView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -137,14 +143,20 @@
}
}
- public static boolean showForTask(TaskIdAttributeContainer taskContainer) {
+ public static boolean showForTask(TaskIdAttributeContainer taskContainer,
+ @Nullable Runnable onClosingStartCallback) {
BaseDraggingActivity activity = BaseDraggingActivity.fromContext(
taskContainer.getTaskView().getContext());
final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate(
R.layout.task_menu, activity.getDragLayer(), false);
+ taskMenuView.setOnClosingStartCallback(onClosingStartCallback);
return taskMenuView.populateAndShowForTask(taskContainer);
}
+ public static boolean showForTask(TaskIdAttributeContainer taskContainer) {
+ return showForTask(taskContainer, null);
+ }
+
private boolean populateAndShowForTask(TaskIdAttributeContainer taskContainer) {
if (isAttachedToWindow()) {
return false;
@@ -171,8 +183,12 @@
}
private void addMenuOptions(TaskIdAttributeContainer taskContainer) {
- mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask()));
- mTaskName.setOnClickListener(v -> close(true));
+ if (enableOverviewIconMenu()) {
+ removeView(mTaskName);
+ } else {
+ mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask()));
+ mTaskName.setOnClickListener(v -> close(true));
+ }
TaskOverlayFactory.getEnabledShortcuts(mTaskView, taskContainer)
.forEach(this::addMenuOption);
}
@@ -180,6 +196,9 @@
private void addMenuOption(SystemShortcut menuOption) {
LinearLayout menuOptionView = (LinearLayout) mActivity.getLayoutInflater().inflate(
R.layout.task_view_menu_option, this, false);
+ if (enableOverviewIconMenu()) {
+ ((GradientDrawable) menuOptionView.getBackground()).setCornerRadius(0);
+ }
menuOption.setIconAndLabelFor(
menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
@@ -198,8 +217,9 @@
// Get Position
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- mActivity.getDragLayer().getDescendantRectRelativeToSelf(taskContainer.getThumbnailView(),
- sTempRect);
+ mActivity.getDragLayer().getDescendantRectRelativeToSelf(
+ enableOverviewIconMenu() ? taskContainer.getIconView().asView()
+ : taskContainer.getThumbnailView(), sTempRect);
Rect insets = mActivity.getDragLayer().getInsets();
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
int padding = getResources()
@@ -217,12 +237,17 @@
ShapeDrawable divider = new ShapeDrawable(new RectShape());
divider.getPaint().setColor(getResources().getColor(android.R.color.transparent));
int dividerSpacing = (int) getResources().getDimension(R.dimen.task_menu_spacing);
- mOptionLayout.setShowDividers(SHOW_DIVIDER_MIDDLE);
+ mOptionLayout.setShowDividers(
+ enableOverviewIconMenu() ? SHOW_DIVIDER_NONE : SHOW_DIVIDER_MIDDLE);
orientationHandler.setTaskOptionsMenuLayoutOrientation(
deviceProfile, mOptionLayout, dividerSpacing, divider);
- float thumbnailAlignedX = sTempRect.left - insets.left;
- float thumbnailAlignedY = sTempRect.top - insets.top;
+ float thumbnailAlignedX = sTempRect.left - insets.left + (enableOverviewIconMenu()
+ ? -getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_touch_max_margin) : 0);
+ float thumbnailAlignedY = sTempRect.top - insets.top + (enableOverviewIconMenu()
+ ? getResources().getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_max_height)
+ - 2 * dividerSpacing : 0);
// Changing pivot to make computations easier
// NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
// which would render the X and Y position set here incorrect
@@ -231,15 +256,22 @@
setRotation(orientationHandler.getDegreesRotated());
// Margin that insets the menuView inside the taskView
- float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
+ float taskInsetMargin =
+ enableOverviewIconMenu() ? getResources().getDimension(
+ R.dimen.task_thumbnail_icon_menu_margin) : getResources().getDimension(
+ R.dimen.task_card_margin);
setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX,
- mTaskContainer.getThumbnailView(), deviceProfile, taskInsetMargin));
+ mTaskContainer.getThumbnailView(), deviceProfile, taskInsetMargin,
+ mTaskContainer.getIconView().asView()));
setTranslationY(orientationHandler.getTaskMenuY(
thumbnailAlignedY, mTaskContainer.getThumbnailView(),
- mTaskContainer.getStagePosition(), this, taskInsetMargin));
+ mTaskContainer.getStagePosition(), this, taskInsetMargin,
+ mTaskContainer.getIconView().asView()));
}
private void animateOpen() {
+ mMenuTranslationYBeforeOpen = getTranslationY();
+ mIconViewTranslationYBeforeOpen = mTaskContainer.getIconView().asView().getTranslationY();
animateOpenOrClosed(false);
mIsOpen = true;
}
@@ -256,7 +288,29 @@
final Animator revealAnimator = createOpenCloseOutlineProvider()
.createRevealAnimator(this, closing);
- revealAnimator.setInterpolator(Interpolators.DECELERATE);
+ revealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED
+ : Interpolators.DECELERATE);
+
+ if (enableOverviewIconMenu()
+ && ((RecentsView) mActivity.getOverviewPanel()).isOnGridBottomRow(mTaskView)) {
+ float taskBottom = mTaskView.getHeight() + mTaskView.getPersistentTranslationY();
+ float menuBottom = getHeight() + mMenuTranslationYBeforeOpen;
+ float additionalTranslationY = Math.max(menuBottom - taskBottom, 0);
+
+ ObjectAnimator translationYAnim = ObjectAnimator.ofFloat(this, TRANSLATION_Y,
+ closing ? mMenuTranslationYBeforeOpen
+ : mMenuTranslationYBeforeOpen - additionalTranslationY);
+ translationYAnim.setInterpolator(EMPHASIZED);
+
+ ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat(
+ mTaskContainer.getIconView().asView(), TRANSLATION_Y,
+ closing ? mIconViewTranslationYBeforeOpen
+ : mIconViewTranslationYBeforeOpen - additionalTranslationY);
+ menuTranslationYAnim.setInterpolator(EMPHASIZED);
+
+ mOpenCloseAnimator.playTogether(translationYAnim, menuTranslationYAnim);
+ }
+
mOpenCloseAnimator.playTogether(revealAnimator,
ObjectAnimator.ofFloat(
mTaskContainer.getThumbnailView(), DIM_ALPHA,
@@ -266,6 +320,9 @@
@Override
public void onAnimationStart(Animator animation) {
setVisibility(VISIBLE);
+ if (closing && mOnClosingStartCallback != null) {
+ mOnClosingStartCallback.run();
+ }
}
@Override
@@ -286,9 +343,16 @@
private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
float radius = TaskCornerRadius.get(mContext);
- Rect fromRect = new Rect(0, 0, getWidth(), 0);
+ Rect fromRect = new Rect(
+ enableOverviewIconMenu() && isLayoutRtl() ? getWidth() : 0,
+ 0,
+ enableOverviewIconMenu() && !isLayoutRtl() ? 0 : getWidth(),
+ 0);
Rect toRect = new Rect(0, 0, getWidth(), getHeight());
return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect);
}
+ private void setOnClosingStartCallback(Runnable onClosingStartCallback) {
+ mOnClosingStartCallback = onClosingStartCallback;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index b373911..12b8b6f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -106,7 +106,7 @@
override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0
override fun getTargetObjectLocation(outPos: Rect?) {
- popupContainer.getDescendantRectRelativeToSelf(taskContainer.iconView, outPos)
+ popupContainer.getDescendantRectRelativeToSelf(taskContainer.iconView.asView(), outPos)
}
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index f84b549..6ae1973 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -25,6 +25,7 @@
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -63,6 +64,7 @@
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewStub;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -107,7 +109,6 @@
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TaskCornerRadius;
import com.android.quickstep.util.TaskRemovedDuringLaunchListener;
-import com.android.quickstep.util.TransformParams;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -121,6 +122,8 @@
import java.util.function.Consumer;
import java.util.stream.Stream;
+import kotlin.Unit;
+
/**
* A task in the Recents view.
*/
@@ -351,7 +354,7 @@
@Nullable
protected Task mTask;
protected TaskThumbnailView mSnapshotView;
- protected IconView mIconView;
+ protected TaskViewIcon mIconView;
protected final DigitalWellBeingToast mDigitalWellBeingToast;
protected float mFullscreenProgress;
private float mGridProgress;
@@ -441,48 +444,44 @@
boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
|| DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+ boolean cursorHoverStatesEnabled = FeatureFlags.enableCursorHoverStates();
- boolean willDrawBorder =
- keyboardFocusHighlightEnabled || FeatureFlags.enableCursorHoverStates();
- setWillNotDraw(!willDrawBorder);
+ setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled);
- if (willDrawBorder) {
- TypedArray styledAttrs = context.obtainStyledAttributes(
- attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
+ TypedArray styledAttrs = context.obtainStyledAttributes(
+ attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
- mFocusBorderAnimator = keyboardFocusHighlightEnabled ? new BorderAnimator(
- /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR),
- /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this)) : null;
+ mFocusBorderAnimator = keyboardFocusHighlightEnabled
+ ? BorderAnimator.createSimpleBorderAnimator(
+ /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
+ /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_border_width),
+ /* boundsBuilder= */ this::updateBorderBounds,
+ /* targetView= */ this,
+ /* borderColor= */ styledAttrs.getColor(
+ R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR))
+ : null;
- mHoverBorderAnimator =
- FeatureFlags.enableCursorHoverStates() ? new BorderAnimator(
- /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR),
- /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
- /* borderWidthPx= */ context.getResources()
- .getDimensionPixelSize(R.dimen.task_hover_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this)) : null;
+ mHoverBorderAnimator = cursorHoverStatesEnabled
+ ? BorderAnimator.createSimpleBorderAnimator(
+ /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
+ /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+ R.dimen.task_hover_border_width),
+ /* boundsBuilder= */ this::updateBorderBounds,
+ /* targetView= */ this,
+ /* borderColor= */ styledAttrs.getColor(
+ R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR))
+ : null;
- styledAttrs.recycle();
- } else {
- mFocusBorderAnimator = null;
- mHoverBorderAnimator = null;
- }
+ styledAttrs.recycle();
}
- protected void updateBorderBounds(Rect bounds) {
+ protected Unit updateBorderBounds(@NonNull Rect bounds) {
bounds.set(mSnapshotView.getLeft() + Math.round(mSnapshotView.getTranslationX()),
mSnapshotView.getTop() + Math.round(mSnapshotView.getTranslationY()),
mSnapshotView.getRight() + Math.round(mSnapshotView.getTranslationX()),
mSnapshotView.getBottom() + Math.round(mSnapshotView.getTranslationY()));
+ return Unit.INSTANCE;
}
public void setTaskViewId(int id) {
@@ -522,27 +521,35 @@
protected void onFinishInflate() {
super.onFinishInflate();
mSnapshotView = findViewById(R.id.snapshot);
- mIconView = findViewById(R.id.icon);
- mIconTouchDelegate = new TransformingTouchDelegate(mIconView);
+ ViewStub iconViewStub = findViewById(R.id.icon);
+ if (enableOverviewIconMenu()) {
+ iconViewStub.setLayoutResource(R.layout.icon_app_chip_view);
+ } else {
+ iconViewStub.setLayoutResource(R.layout.icon_view);
+ }
+ mIconView = (TaskViewIcon) iconViewStub.inflate();
+ mIconTouchDelegate = new TransformingTouchDelegate(mIconView.asView());
}
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.buildAnimator(gainFocus).start();
+ mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
}
}
@Override
public boolean onHoverEvent(MotionEvent event) {
- if (FeatureFlags.enableCursorHoverStates()) {
+ if (mHoverBorderAnimator != null) {
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_ENTER:
- mHoverBorderAnimator.buildAnimator(/* isAppearing= */ true).start();
+ mHoverBorderAnimator.setBorderVisibility(
+ /* visible= */ true, /* animated= */ true);
break;
case MotionEvent.ACTION_HOVER_EXIT:
- mHoverBorderAnimator.buildAnimator(/* isAppearing= */ false).start();
+ mHoverBorderAnimator.setBorderVisibility(
+ /* visible= */ false, /* animated= */ true);
break;
default:
break;
@@ -564,14 +571,14 @@
@Override
public void draw(Canvas canvas) {
- super.draw(canvas);
+ // Draw border first so any child views outside of the thumbnail bounds are drawn above it.
if (mFocusBorderAnimator != null) {
mFocusBorderAnimator.drawBorder(canvas);
}
-
if (mHoverBorderAnimator != null) {
mHoverBorderAnimator.drawBorder(canvas);
}
+ super.draw(canvas);
}
/**
@@ -588,17 +595,22 @@
return false;
}
- protected void computeAndSetIconTouchDelegate(IconView iconView, float[] tempCenterCoords,
+ protected void computeAndSetIconTouchDelegate(TaskViewIcon view, float[] tempCenterCoords,
TransformingTouchDelegate transformingTouchDelegate) {
- float iconHalfSize = iconView.getWidth() / 2f;
- tempCenterCoords[0] = tempCenterCoords[1] = iconHalfSize;
- getDescendantCoordRelativeToAncestor(iconView, mActivity.getDragLayer(), tempCenterCoords,
- false);
+ if (view == null) {
+ return;
+ }
+ float viewHalfWidth = view.getWidth() / 2f;
+ float viewHalfHeight = view.getHeight() / 2f;
+ tempCenterCoords[0] = viewHalfWidth;
+ tempCenterCoords[1] = viewHalfHeight;
+ getDescendantCoordRelativeToAncestor(view.asView(), mActivity.getDragLayer(),
+ tempCenterCoords, false);
transformingTouchDelegate.setBounds(
- (int) (tempCenterCoords[0] - iconHalfSize),
- (int) (tempCenterCoords[1] - iconHalfSize),
- (int) (tempCenterCoords[0] + iconHalfSize),
- (int) (tempCenterCoords[1] + iconHalfSize));
+ (int) (tempCenterCoords[0] - viewHalfWidth),
+ (int) (tempCenterCoords[1] - viewHalfHeight),
+ (int) (tempCenterCoords[0] + viewHalfWidth),
+ (int) (tempCenterCoords[1] + viewHalfHeight));
}
/**
@@ -630,8 +642,8 @@
cancelPendingLoadTasks();
mTask = task;
mTaskIdContainer[0] = mTask.key.id;
- mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView,
- mIconView, STAGE_POSITION_UNDEFINED);
+ mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView, mIconView,
+ STAGE_POSITION_UNDEFINED);
mSnapshotView.bind(task);
setOrientationState(orientedState);
}
@@ -746,7 +758,7 @@
return new TaskThumbnailView[]{mSnapshotView};
}
- public IconView getIconView() {
+ public TaskViewIcon getIconView() {
return mIconView;
}
@@ -975,19 +987,17 @@
if (remoteTargetHandles.length == 1) {
targets = remoteTargetHandles[0].getTransformParams().getTargetSet();
} else {
- TransformParams topLeftParams = remoteTargetHandles[0].getTransformParams();
- TransformParams rightBottomParams = remoteTargetHandles[1].getTransformParams();
- RemoteAnimationTarget[] apps = Stream.concat(
- Arrays.stream(topLeftParams.getTargetSet().apps),
- Arrays.stream(rightBottomParams.getTargetSet().apps))
+ RemoteAnimationTarget[] apps = Arrays.stream(remoteTargetHandles)
+ .flatMap(handle -> Stream.of(
+ handle.getTransformParams().getTargetSet().apps))
.toArray(RemoteAnimationTarget[]::new);
- RemoteAnimationTarget[] wallpapers = Stream.concat(
- Arrays.stream(topLeftParams.getTargetSet().wallpapers),
- Arrays.stream(rightBottomParams.getTargetSet().wallpapers))
+ RemoteAnimationTarget[] wallpapers = Arrays.stream(remoteTargetHandles)
+ .flatMap(handle -> Stream.of(
+ handle.getTransformParams().getTargetSet().wallpapers))
.toArray(RemoteAnimationTarget[]::new);
targets = new RemoteAnimationTargets(apps, wallpapers,
- topLeftParams.getTargetSet().nonApps,
- topLeftParams.getTargetSet().targetMode);
+ remoteTargetHandles[0].getTransformParams().getTargetSet().nonApps,
+ remoteTargetHandles[0].getTransformParams().getTargetSet().targetMode);
}
if (targets == null) {
// If the recents animation is cancelled somehow between the parent if block and
@@ -1078,6 +1088,7 @@
mIconLoadRequest = iconCache.updateIconInBackground(mTask,
(task) -> {
setIcon(mIconView, task.icon);
+ setText(mIconView, TaskUtils.getTitle(getContext(), task));
mDigitalWellBeingToast.initialize(task);
});
}
@@ -1093,6 +1104,7 @@
}
if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
setIcon(mIconView, null);
+ setText(mIconView, null);
}
}
}
@@ -1112,7 +1124,7 @@
}
}
- private boolean showTaskMenu(IconView iconView) {
+ private boolean showTaskMenu(TaskViewIcon iconView) {
if (!getRecentsView().canLaunchFullscreenTask()) {
// Don't show menu when selecting second split screen app
return true;
@@ -1129,11 +1141,16 @@
}
}
- protected boolean showTaskMenuWithContainer(IconView iconView) {
+ protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) {
TaskIdAttributeContainer menuContainer =
mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1];
DeviceProfile dp = mActivity.getDeviceProfile();
- if (dp.isTablet) {
+ if (enableOverviewIconMenu() && iconView instanceof IconAppChipView) {
+ ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ true);
+ return TaskMenuView.showForTask(menuContainer, () -> {
+ ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ false);
+ });
+ } else if (dp.isTablet) {
int alignedOptionIndex = 0;
if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) {
if (FeatureFlags.enableGridOnlyOverview()) {
@@ -1145,13 +1162,14 @@
alignedOptionIndex = 1;
}
}
- return TaskMenuViewWithArrow.Companion.showForTask(menuContainer, alignedOptionIndex);
+ return TaskMenuViewWithArrow.Companion.showForTask(menuContainer,
+ alignedOptionIndex);
} else {
return TaskMenuView.showForTask(menuContainer);
}
}
- protected void setIcon(IconView iconView, @Nullable Drawable icon) {
+ protected void setIcon(TaskViewIcon iconView, @Nullable Drawable icon) {
if (icon != null) {
iconView.setDrawable(icon);
iconView.setOnClickListener(v -> {
@@ -1171,32 +1189,13 @@
}
}
- public void setOrientationState(RecentsOrientedState orientationState) {
- setIconOrientation(orientationState);
- setThumbnailOrientation(orientationState);
+ protected void setText(TaskViewIcon iconView, CharSequence text) {
+ iconView.setText(text);
}
- protected void setIconOrientation(RecentsOrientedState orientationState) {
- PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
- boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
-
- boolean isGridTask = isGridTask();
- LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
-
- int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
- int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
- int taskMargin = deviceProfile.overviewTaskMarginPx;
-
- orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight,
- thumbnailTopMargin, isRtl);
- iconParams.width = iconParams.height = taskIconHeight;
- mIconView.setLayoutParams(iconParams);
-
- mIconView.setRotation(orientationHandler.getDegreesRotated());
- int iconDrawableSize = isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx
- : deviceProfile.overviewTaskIconDrawableSizePx;
- mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize);
+ public void setOrientationState(RecentsOrientedState orientationState) {
+ mIconView.setIconOrientation(orientationState, isGridTask());
+ setThumbnailOrientation(orientationState);
}
protected void setThumbnailOrientation(RecentsOrientedState orientationState) {
@@ -1901,14 +1900,14 @@
public class TaskIdAttributeContainer {
private final TaskThumbnailView mThumbnailView;
private final Task mTask;
- private final IconView mIconView;
+ private final TaskViewIcon mIconView;
/** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
private @SplitConfigurationOptions.StagePosition int mStagePosition;
@IdRes
private final int mA11yNodeId;
public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView,
- IconView iconView, int stagePosition) {
+ TaskViewIcon iconView, int stagePosition) {
this.mTask = task;
this.mThumbnailView = thumbnailView;
this.mIconView = iconView;
@@ -1933,7 +1932,7 @@
return TaskView.this;
}
- public IconView getIconView() {
+ public TaskViewIcon getIconView() {
return mIconView;
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskViewIcon.java b/quickstep/src/com/android/quickstep/views/TaskViewIcon.java
new file mode 100644
index 0000000..b4f21be
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskViewIcon.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 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.annotation.Nullable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.quickstep.util.RecentsOrientedState;
+
+/**
+ * Interface defining an object which can be used as a TaskView's icon.
+ */
+public interface TaskViewIcon {
+
+ /**
+ * Returns the width of this icon view.
+ */
+ int getWidth();
+
+ /**
+ * Returns the height of this icon view.
+ */
+ int getHeight();
+
+ /**
+ * Sets the opacity of the view.
+ */
+ void setAlpha(float alpha);
+
+ /**
+ * Returns this icon view's drawable.
+ */
+ @Nullable Drawable getDrawable();
+
+ /**
+ * Sets a {@link Drawable} to be displayed.
+ */
+ void setDrawable(@Nullable Drawable icon);
+
+ /**
+ * Register a callback to be invoked when this view is clicked.
+ */
+ void setOnClickListener(@Nullable View.OnClickListener l);
+
+ /**
+ * Register a callback to be invoked when this view is clicked and held.
+ */
+ void setOnLongClickListener(@Nullable View.OnLongClickListener l);
+
+ /**
+ * Returns the LayoutParams associated with this view.
+ */
+ ViewGroup.LayoutParams getLayoutParams();
+
+ /**
+ * Sets the layout parameters associated with this view.
+ */
+ void setLayoutParams(ViewGroup.LayoutParams params);
+
+ /**
+ * Sets the degrees that the view is rotated around the pivot point.
+ */
+ void setRotation(float rotation);
+
+ /**
+ * Sets the size of the icon drawable.
+ */
+ void setDrawableSize(int iconWidth, int iconHeight);
+
+ /**
+ * Sets the orientation of this icon view based on the provided orientationState.
+ */
+ void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask);
+
+ /**
+ * Sets the visibility state of this view.
+ */
+ void setVisibility(int visibility);
+
+ /**
+ * Sets the tint color of the icon, useful for scrimming or dimming.
+ *
+ * @param color to blend in.
+ * @param amount [0,1] 0 no tint, 1 full tint
+ */
+ void setIconColorTint(int color, float amount);
+
+ /**
+ * Gets the opacity of the view.
+ */
+ float getAlpha();
+
+ /**
+ * Returns the width of this icon view's drawable.
+ */
+ int getDrawableWidth();
+
+ /**
+ * Returns the height of this icon view's drawable.
+ */
+ int getDrawableHeight();
+
+ /**
+ * Directly calls any attached OnClickListener.
+ */
+ boolean callOnClick();
+
+ /**
+ * Calls this view's OnLongClickListener.
+ */
+ boolean performLongClick();
+
+ /**
+ * Sets the text for this icon view if any text view is associated.
+ */
+ default void setText(CharSequence text) {}
+
+ /**
+ * Returns this icon view cast as a View.
+ */
+ View asView();
+}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
index 3920b08..16bfe70 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -13,6 +13,7 @@
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarManager
+import com.android.systemui.shared.rotation.RotationButton
import java.lang.IllegalStateException
import org.junit.Assume.assumeTrue
import org.junit.Before
@@ -34,6 +35,9 @@
@Mock lateinit var mockBackButton: ImageView
@Mock lateinit var mockRecentsButton: ImageView
@Mock lateinit var mockHomeButton: ImageView
+ @Mock lateinit var mockImeSwitcher: ImageView
+ @Mock lateinit var mockRotationButton: RotationButton
+ @Mock lateinit var mockA11yButton: ImageView
private var surfaceRotation = Surface.ROTATION_0
@@ -196,7 +200,10 @@
isInSetup = isInSetup,
isThreeButtonNav = isThreeButtonNav,
phoneMode = phoneMode,
- surfaceRotation = surfaceRotation
+ surfaceRotation = surfaceRotation,
+ imeSwitcher = mockImeSwitcher,
+ rotationButton = mockRotationButton,
+ a11yButton = mockA11yButton
)
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index a10b24d..99c79b3 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -32,8 +32,6 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -146,7 +144,7 @@
.around(new NavigationModeSwitchRule(mLauncher))
.around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
.around(viewCaptureRule)
- .around(new TestIsolationRule(mLauncher))
+ .around(new TestIsolationRule(mLauncher, false))
.around(setLauncherCommand);
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
new file mode 100644
index 0000000..08e0898
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 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.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.Flags;
+import com.android.launcher3.R;
+import com.android.launcher3.icons.IconProvider;
+import com.android.quickstep.util.GroupTask;
+import com.android.systemui.shared.recents.model.Task;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+@SmallTest
+public class RecentsModelTest {
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private TaskThumbnailCache mThumbnailCache;
+
+ @Mock
+ private RecentTasksList mTasksList;
+
+ @Mock
+ private TaskThumbnailCache.HighResLoadingState mHighResLoadingState;
+
+ private RecentsModel mRecentsModel;
+
+ private RecentTasksList.TaskLoadResult mTaskResult;
+
+ private Resources mResource;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setup() throws NoSuchFieldException {
+ MockitoAnnotations.initMocks(this);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW);
+ mTaskResult = getTaskResult();
+ doAnswer(invocation-> {
+ Consumer<ArrayList<GroupTask>> callback = invocation.getArgument(1);
+ callback.accept(mTaskResult);
+ return null;
+ }).when(mTasksList).getTaskKeys(anyInt(), any());
+
+ when(mHighResLoadingState.isEnabled()).thenReturn(true);
+ when(mThumbnailCache.getHighResLoadingState()).thenReturn(mHighResLoadingState);
+ when(mThumbnailCache.isPreloadingEnabled()).thenReturn(true);
+
+ mRecentsModel = new RecentsModel(mContext, mTasksList, mock(TaskIconCache.class),
+ mThumbnailCache, mock(IconProvider.class));
+
+ mResource = mock(Resources.class);
+ when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3);
+ when(mContext.getResources()).thenReturn(mResource);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_GRID_ONLY_OVERVIEW);
+ }
+
+ @Test
+ @UiThreadTest
+ public void preloadOnHighResolutionEnabled() {
+ mRecentsModel.preloadCacheIfNeeded();
+
+ ArgumentCaptor<Task> taskArgs = ArgumentCaptor.forClass(Task.class);
+ verify(mRecentsModel.getThumbnailCache(), times(2))
+ .updateThumbnailInCache(taskArgs.capture(), /* lowResolution= */ eq(false));
+
+ GroupTask expectedGroupTask = mTaskResult.get(0);
+ assertThat(taskArgs.getAllValues().get(0)).isEqualTo(
+ expectedGroupTask.task1);
+ assertThat(taskArgs.getAllValues().get(1)).isEqualTo(
+ expectedGroupTask.task2);
+ }
+
+ @Test
+ public void notPreloadOnHighResolutionDisabled() {
+ when(mHighResLoadingState.isEnabled()).thenReturn(false);
+ when(mThumbnailCache.isPreloadingEnabled()).thenReturn(true);
+ mRecentsModel.preloadCacheIfNeeded();
+ verify(mRecentsModel.getThumbnailCache(), never())
+ .updateThumbnailInCache(any(), anyBoolean());
+ }
+
+ @Test
+ public void notPreloadOnPreloadDisabled() {
+ when(mThumbnailCache.isPreloadingEnabled()).thenReturn(false);
+ mRecentsModel.preloadCacheIfNeeded();
+ verify(mRecentsModel.getThumbnailCache(), never())
+ .updateThumbnailInCache(any(), anyBoolean());
+
+ }
+
+ @Test
+ public void increaseCacheSizeAndPreload() {
+ // Mock to return preload is needed
+ when(mThumbnailCache.updateCacheSizeAndRemoveExcess()).thenReturn(true);
+ // Update cache size
+ mRecentsModel.updateCacheSizeAndPreloadIfNeeded();
+ // Assert update cache is called
+ verify(mRecentsModel.getThumbnailCache(), times(2))
+ .updateThumbnailInCache(any(), /* lowResolution= */ eq(false));
+ }
+
+ @Test
+ public void decreaseCacheSizeAndNotPreload() {
+ // Mock to return preload is not needed
+ when(mThumbnailCache.updateCacheSizeAndRemoveExcess()).thenReturn(false);
+ // Update cache size
+ mRecentsModel.updateCacheSizeAndPreloadIfNeeded();
+ // Assert update cache is never called
+ verify(mRecentsModel.getThumbnailCache(), never())
+ .updateThumbnailInCache(any(), anyBoolean());
+ }
+
+ private RecentTasksList.TaskLoadResult getTaskResult() {
+ RecentTasksList.TaskLoadResult allTasks = new RecentTasksList.TaskLoadResult(0, false, 1);
+ ActivityManager.RecentTaskInfo taskInfo1 = new ActivityManager.RecentTaskInfo();
+ Task.TaskKey taskKey1 = new Task.TaskKey(taskInfo1);
+ Task task1 = Task.from(taskKey1, taskInfo1, false);
+
+ ActivityManager.RecentTaskInfo taskInfo2 = new ActivityManager.RecentTaskInfo();
+ Task.TaskKey taskKey2 = new Task.TaskKey(taskInfo2);
+ Task task2 = Task.from(taskKey2, taskInfo2, false);
+
+ allTasks.add(new GroupTask(task1, task2, null));
+ return allTasks;
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
new file mode 100644
index 0000000..74f37a4
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsKeyboardQuickSwitch.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2023 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.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.tapl.KeyboardQuickSwitch;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsKeyboardQuickSwitch extends AbstractQuickStepTest {
+
+ private enum TestSurface {
+ HOME, LAUNCHED_APP, HOME_ALL_APPS, WIDGETS,
+ }
+
+ private enum TestCase {
+ DISMISS(0),
+ LAUNCH_LAST_APP(0),
+ LAUNCH_SELECTED_APP(1),
+ LAUNCH_OVERVIEW(5);
+
+ private final int mNumAdditionalRunningTasks;
+
+ TestCase(int numAdditionalRunningTasks) {
+ mNumAdditionalRunningTasks = numAdditionalRunningTasks;
+ }
+ }
+
+ private static final String CALCULATOR_APP_PACKAGE =
+ resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
+
+ @Override
+ public void setUp() throws Exception {
+ Assume.assumeTrue(mLauncher.isTablet());
+ super.setUp();
+ TaplTestsLauncher3.initialize(this);
+ startAppFast(CALCULATOR_APP_PACKAGE);
+ startTestActivity(2);
+ }
+
+ @Test
+ public void testDismiss_fromHome() {
+ runTest(TestSurface.HOME, TestCase.DISMISS);
+ }
+
+ @Test
+ public void testDismiss_fromApp() {
+ runTest(TestSurface.LAUNCHED_APP, TestCase.DISMISS);
+ }
+
+ @Test
+ public void testDismiss_fromHomeAllApps() {
+ runTest(TestSurface.HOME_ALL_APPS, TestCase.DISMISS);
+ }
+
+ @Test
+ public void testDismiss_fromWidgets() {
+ runTest(TestSurface.WIDGETS, TestCase.DISMISS);
+ }
+
+ @Test
+ public void testLaunchLastTask_fromHome() {
+ runTest(TestSurface.HOME, TestCase.LAUNCH_LAST_APP);
+ }
+
+ @Test
+ public void testLaunchLastTask_fromApp() {
+ runTest(TestSurface.LAUNCHED_APP, TestCase.LAUNCH_LAST_APP);
+ }
+
+ @Test
+ public void testLaunchLastTask_fromHomeAllApps() {
+ runTest(TestSurface.HOME_ALL_APPS, TestCase.LAUNCH_LAST_APP);
+ }
+
+ @Test
+ public void testLaunchLastTask_fromWidgets() {
+ runTest(TestSurface.WIDGETS, TestCase.LAUNCH_LAST_APP);
+ }
+
+ @Test
+ public void testLaunchSelectedTask_fromHome() {
+ runTest(TestSurface.HOME, TestCase.LAUNCH_SELECTED_APP);
+ }
+
+ @Test
+ public void testLaunchSelectedTask_fromApp() {
+ runTest(TestSurface.LAUNCHED_APP, TestCase.LAUNCH_SELECTED_APP);
+ }
+
+ @Test
+ public void testLaunchSelectedTask_fromHomeAllApps() {
+ runTest(TestSurface.HOME_ALL_APPS, TestCase.LAUNCH_SELECTED_APP);
+ }
+
+ @Test
+ public void testLaunchSelectedTask_fromWidgets() {
+ runTest(TestSurface.WIDGETS, TestCase.LAUNCH_SELECTED_APP);
+ }
+
+ @Test
+ public void testLaunchOverviewTask_fromHome() {
+ runTest(TestSurface.HOME, TestCase.LAUNCH_OVERVIEW);
+ }
+
+ @Test
+ public void testLaunchOverviewTask_fromApp() {
+ runTest(TestSurface.LAUNCHED_APP, TestCase.LAUNCH_OVERVIEW);
+ }
+
+ @Test
+ public void testLaunchOverviewTask_fromHomeAllApps() {
+ runTest(TestSurface.HOME_ALL_APPS, TestCase.LAUNCH_OVERVIEW);
+ }
+
+ @Test
+ public void testLaunchOverviewTask_fromWidgets() {
+ runTest(TestSurface.WIDGETS, TestCase.LAUNCH_OVERVIEW);
+ }
+
+ private void runTest(@NonNull TestSurface testSurface, @NonNull TestCase testCase) {
+ for (int i = 0; i < testCase.mNumAdditionalRunningTasks; i++) {
+ startTestActivity(3 + i);
+ }
+
+ KeyboardQuickSwitch kqs;
+ switch (testSurface) {
+ case HOME:
+ kqs = mLauncher.goHome().showQuickSwitchView();
+ break;
+ case LAUNCHED_APP:
+ mLauncher.setIgnoreTaskbarVisibility(true);
+ kqs = mLauncher.getLaunchedAppState().showQuickSwitchView();
+ break;
+ case HOME_ALL_APPS:
+ kqs = mLauncher.goHome().switchToAllApps().showQuickSwitchView();
+ break;
+ case WIDGETS:
+ kqs = mLauncher.goHome().openAllWidgets().showQuickSwitchView();
+ break;
+ default:
+ throw new IllegalStateException(
+ "KeyboardQuickSwitch could not be initialized for test surface: "
+ + testSurface);
+ }
+
+ switch (testCase) {
+ case DISMISS:
+ kqs.dismiss();
+ break;
+ case LAUNCH_LAST_APP:
+ kqs.launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
+ break;
+ case LAUNCH_SELECTED_APP:
+ kqs.moveFocusForward().launchFocusedAppTask(CALCULATOR_APP_PACKAGE);
+ break;
+ case LAUNCH_OVERVIEW:
+ kqs.moveFocusBackward().moveFocusBackward().launchFocusedOverviewTask();
+ break;
+ default:
+ throw new IllegalStateException("Cannot run test case: " + testCase);
+ }
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 5531c6e..f383949 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -17,10 +17,7 @@
package com.android.quickstep;
import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
-import static com.android.launcher3.testing.shared.TestProtocol.FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP;
-import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ICON_MENU;
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
@@ -32,7 +29,6 @@
import android.content.Intent;
import android.platform.test.annotations.PlatinumTest;
-import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -51,11 +47,9 @@
import com.android.launcher3.tapl.OverviewTaskMenu;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TaplTestsLauncher3;
-import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
-import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
import com.android.quickstep.views.RecentsView;
@@ -203,6 +197,7 @@
}
+ @PlatinumTest(focusArea = "launcher")
@Test
public void testOverviewActionsMenu() throws Exception {
startTestAppsWithCheck();
@@ -215,6 +210,23 @@
isInLaunchedApp(launcher)));
}
+
+ @Test
+ public void testOverviewActionsMenu_iconAppChipMenu() throws Exception {
+ try (AutoCloseable c = TestUtil.overrideFlag(ENABLE_OVERVIEW_ICON_MENU, true)) {
+ startTestAppsWithCheck();
+
+ OverviewTaskMenu menu =
+ mLauncher.goHome().switchToOverview().getCurrentTask().tapMenu();
+
+ assertNotNull("Tapping App info menu item returned null", menu.tapAppInfoMenuItem());
+ executeOnLauncher(launcher -> assertTrue(
+ "Launcher activity is the top activity; expecting another activity to be the "
+ + "top",
+ isInLaunchedApp(launcher)));
+ }
+ }
+
private int getCurrentOverviewPage(Launcher launcher) {
return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
}
@@ -320,7 +332,7 @@
@Test
@ScreenRecord // b/242163205
@PlatinumTest(focusArea = "launcher")
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/286084688
+ @TaskbarModeSwitch(mode = PERSISTENT)
public void testQuickSwitchToPreviousAppForTablet() throws Exception {
assumeTrue(mLauncher.isTablet());
startTestActivity(2);
@@ -340,16 +352,7 @@
"The first app we should have quick switched to is not running");
// Expect task bar visible when the launched app was the test activity.
launchedAppState = getAndAssertLaunchedApp();
-
- Log.e(FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP,
- "is Taskbar Transient : " + DisplayController.isTransientTaskbar(mTargetContext));
- // TODO(b/286084688): Remove this branching check after test corruption is resolved.
- // Branching this check because of test corruption.
- if (DisplayController.isTransientTaskbar(mTargetContext)) {
- launchedAppState.assertTaskbarHidden();
- } else {
- launchedAppState.assertTaskbarVisible();
- }
+ launchedAppState.assertTaskbarVisible();
}
@Test
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index cc56faf..ed152f2 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -16,6 +16,7 @@
package com.android.quickstep;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ICON_MENU;
import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
@@ -30,8 +31,10 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.tapl.OverviewTaskMenu;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
@@ -139,6 +142,42 @@
.hasMenuItem("Save app pair"));
}
+ @Test
+ public void testTapBothIconMenus() {
+ createAndLaunchASplitPair();
+
+ OverviewTaskMenu taskMenu =
+ mLauncher.goHome().switchToOverview().getCurrentTask().tapMenu();
+ assertTrue("App info item not appearing in expanded task menu.",
+ taskMenu.hasMenuItem("App info"));
+ taskMenu.touchOutsideTaskMenuToDismiss();
+
+ OverviewTaskMenu splitMenu =
+ mLauncher.getOverview().getCurrentTask().tapSplitTaskMenu();
+ assertTrue("App info item not appearing in expanded split task's menu.",
+ splitMenu.hasMenuItem("App info"));
+ splitMenu.touchOutsideTaskMenuToDismiss();
+ }
+
+ @Test
+ public void testTapBothIconMenus_iconAppChipMenu() throws Exception {
+ try (AutoCloseable c = TestUtil.overrideFlag(ENABLE_OVERVIEW_ICON_MENU, true)) {
+ createAndLaunchASplitPair();
+
+ OverviewTaskMenu taskMenu =
+ mLauncher.goHome().switchToOverview().getCurrentTask().tapMenu();
+ assertTrue("App info item not appearing in expanded task menu.",
+ taskMenu.hasMenuItem("App info"));
+ taskMenu.touchOutsideTaskMenuToDismiss();
+
+ OverviewTaskMenu splitMenu =
+ mLauncher.getOverview().getCurrentTask().tapSplitTaskMenu();
+ assertTrue("App info item not appearing in expanded split task's menu.",
+ splitMenu.hasMenuItem("App info"));
+ splitMenu.touchOutsideTaskMenuToDismiss();
+ }
+ }
+
private void createAndLaunchASplitPair() {
startTestActivity(2);
startTestActivity(3);
diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java b/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java
new file mode 100644
index 0000000..4e04261
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 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 junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.R;
+import com.android.quickstep.util.TaskKeyCache;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+@SmallTest
+public class TaskThumbnailCacheTest {
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private Resources mResource;
+
+ @Mock
+ private TaskKeyCache mTaskKeyCache;
+
+ @Before
+ public void setup() throws NoSuchFieldException {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResource);
+ }
+
+ @Test
+ public void increaseCacheSize() {
+ // Mock a cache size increase from 3 to 8
+ when(mTaskKeyCache.getMaxSize()).thenReturn(3);
+ when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(8);
+ TaskThumbnailCache thumbnailCache = new TaskThumbnailCache(mContext, mock(Executor.class),
+ mTaskKeyCache);
+
+ // Preload is needed when increasing size
+ assertTrue(thumbnailCache.updateCacheSizeAndRemoveExcess());
+ verify(mTaskKeyCache, times(1)).updateCacheSizeAndRemoveExcess(8);
+ }
+
+ @Test
+ public void decreaseCacheSize() {
+ // Mock a cache size decrease from 8 to 3
+ when(mTaskKeyCache.getMaxSize()).thenReturn(8);
+ when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3);
+ TaskThumbnailCache thumbnailCache = new TaskThumbnailCache(mContext, mock(Executor.class),
+ mTaskKeyCache);
+ // Preload is not needed when decreasing size
+ assertFalse(thumbnailCache.updateCacheSizeAndRemoveExcess());
+ verify(mTaskKeyCache, times(1)).updateCacheSizeAndRemoveExcess(3);
+ }
+
+ @Test
+ public void keepSameCacheSize() {
+ when(mTaskKeyCache.getMaxSize()).thenReturn(3);
+ when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3);
+ TaskThumbnailCache thumbnailCache = new TaskThumbnailCache(mContext, mock(Executor.class),
+ mTaskKeyCache);
+ // Preload is not needed when it has the same cache size
+ assertFalse(thumbnailCache.updateCacheSizeAndRemoveExcess());
+ verify(mTaskKeyCache, never()).updateCacheSizeAndRemoveExcess(anyInt());
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java
new file mode 100644
index 0000000..ea2688a
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCacheTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2023 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.util;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+
+import android.content.ComponentName;
+import android.content.Intent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import org.junit.Test;
+
+@SmallTest
+public class TaskKeyByLastActiveTimeCacheTest {
+ @Test
+ public void add() {
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(3);
+ Task.TaskKey key1 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 1);
+ ThumbnailData data1 = new ThumbnailData();
+ cache.put(key1, data1);
+
+ Task.TaskKey key2 = new Task.TaskKey(2, 0, new Intent(),
+ new ComponentName("", ""), 0, 2);
+ ThumbnailData data2 = new ThumbnailData();
+ cache.put(key2, data2);
+
+ assertEquals(2, cache.getSize());
+ assertEquals(data1, cache.getAndInvalidateIfModified(key1));
+ assertEquals(data2, cache.getAndInvalidateIfModified(key2));
+
+ assertEquals(2, cache.getQueue().size());
+ assertEquals(key1, cache.getQueue().poll());
+ assertEquals(key2, cache.getQueue().poll());
+ }
+
+ @Test
+ public void addSameTasksWithSameLastActiveTimeTwice() {
+ // Add 2 tasks with same id and last active time, it should only have 1 entry in cache
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(3);
+ Task.TaskKey key1 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 1000);
+ ThumbnailData data1 = new ThumbnailData();
+ cache.put(key1, data1);
+
+ Task.TaskKey key2 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 1000);
+ ThumbnailData data2 = new ThumbnailData();
+ cache.put(key2, data2);
+
+ assertEquals(1, cache.getSize());
+ assertEquals(data2, cache.getAndInvalidateIfModified(key2));
+
+ assertEquals(1, cache.getQueue().size());
+ assertEquals(key2, cache.getQueue().poll());
+ }
+
+ @Test
+ public void addSameTasksWithDifferentLastActiveTime() {
+ // Add 2 tasks with same id and different last active time, it should only have the
+ // higher last active time entry
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(3);
+ Task.TaskKey key1 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 1000);
+ ThumbnailData data1 = new ThumbnailData();
+ cache.put(key1, data1);
+
+ Task.TaskKey key2 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 2000);
+ ThumbnailData data2 = new ThumbnailData();
+ cache.put(key2, data2);
+
+ assertEquals(1, cache.getSize());
+ assertEquals(data2, cache.getAndInvalidateIfModified(key2));
+
+ assertEquals(1, cache.getQueue().size());
+ Task.TaskKey queueKey = cache.getQueue().poll();
+ assertEquals(key2, queueKey);
+ // TaskKey's equal method does not check last active time, so we check here
+ assertEquals(2000, queueKey.lastActiveTime);
+ }
+
+ @Test
+ public void remove() {
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(3);
+ Task.TaskKey key1 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 0);
+ cache.put(key1, new ThumbnailData());
+
+ cache.remove(key1);
+
+ assertEquals(0, cache.getSize());
+ assertEquals(0, cache.getQueue().size());
+ }
+
+ @Test
+ public void removeByStubKey() {
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(3);
+ Task.TaskKey key1 = new Task.TaskKey(1, 1, new Intent(),
+ new ComponentName("", ""), 1, 100);
+ cache.put(key1, new ThumbnailData());
+
+ Task.TaskKey stubKey = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 0);
+ cache.remove(stubKey);
+
+ assertEquals(0, cache.getSize());
+ assertEquals(0, cache.getQueue().size());
+ }
+
+ @Test
+ public void evictAll() {
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(3);
+ Task.TaskKey key1 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 0);
+ cache.put(key1, new ThumbnailData());
+ Task.TaskKey key2 = new Task.TaskKey(2, 0, new Intent(),
+ new ComponentName("", ""), 0, 0);
+ cache.put(key2, new ThumbnailData());
+
+ cache.evictAll();
+
+ assertEquals(0, cache.getSize());
+ assertEquals(0, cache.getQueue().size());
+ }
+
+ @Test
+ public void removeAllByPredicate() {
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(3);
+ // Add user 1's tasks
+ Task.TaskKey user1Key1 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 1, 0);
+ cache.put(user1Key1, new ThumbnailData());
+ Task.TaskKey user1Key2 = new Task.TaskKey(2, 0, new Intent(),
+ new ComponentName("", ""), 1, 0);
+ cache.put(user1Key2, new ThumbnailData());
+ // Add user 2's task
+ Task.TaskKey user2Key = new Task.TaskKey(3, 0, new Intent(),
+ new ComponentName("", ""), 2, 0);
+ ThumbnailData user2Data = new ThumbnailData();
+ cache.put(user2Key, user2Data);
+
+ cache.removeAll(key -> key.userId == 1);
+
+ // Only user 2's task remains
+ assertEquals(1, cache.getSize());
+ assertEquals(user2Data, cache.getAndInvalidateIfModified(user2Key));
+
+ assertEquals(1, cache.getQueue().size());
+ assertEquals(user2Key, cache.getQueue().poll());
+ }
+
+ @Test
+ public void getAndInvalidateIfModified() {
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(3);
+ // Add user 1's tasks
+ Task.TaskKey key1 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 1, 0);
+ ThumbnailData data1 = new ThumbnailData();
+ cache.put(key1, data1);
+
+ // Get result with task key of same last active time
+ Task.TaskKey keyWithSameActiveTime = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 1, 0);
+ ThumbnailData result1 = cache.getAndInvalidateIfModified(keyWithSameActiveTime);
+ assertEquals(data1, result1);
+ assertEquals(1, cache.getQueue().size());
+
+ // Invalidate result with task key of new last active time
+ Task.TaskKey keyWithNewActiveTime = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 1, 1);
+ ThumbnailData result2 = cache.getAndInvalidateIfModified(keyWithNewActiveTime);
+ // No entry is retrieved because the key has higher last active time
+ assertNull(result2);
+ assertEquals(0, cache.getSize());
+ assertEquals(0, cache.getQueue().size());
+ }
+
+ @Test
+ public void removeByLastActiveTimeWhenOverMaxSize() {
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(2);
+ Task.TaskKey key1 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 200);
+ ThumbnailData task1 = new ThumbnailData();
+ cache.put(key1, task1);
+ Task.TaskKey key2 = new Task.TaskKey(2, 0, new Intent(),
+ new ComponentName("", ""), 0, 100);
+ ThumbnailData task2 = new ThumbnailData();
+ cache.put(key2, task2);
+
+ // Add the 3rd entry which will exceed the max cache size
+ Task.TaskKey key3 = new Task.TaskKey(3, 0, new Intent(),
+ new ComponentName("", ""), 0, 300);
+ ThumbnailData task3 = new ThumbnailData();
+ cache.put(key3, task3);
+
+ // Assert map size and check the remaining entries have higher active time
+ assertEquals(2, cache.getSize());
+ assertEquals(task1, cache.getAndInvalidateIfModified(key1));
+ assertEquals(task3, cache.getAndInvalidateIfModified(key3));
+ assertNull(cache.getAndInvalidateIfModified(key2));
+
+ // Assert queue size and check the remaining entries have higher active time
+ assertEquals(2, cache.getQueue().size());
+ Task.TaskKey queueKey1 = cache.getQueue().poll();
+ assertEquals(key1, queueKey1);
+ assertEquals(200, queueKey1.lastActiveTime);
+ Task.TaskKey queueKey2 = cache.getQueue().poll();
+ assertEquals(key3, queueKey2);
+ assertEquals(300, queueKey2.lastActiveTime);
+ }
+
+ @Test
+ public void updateIfAlreadyInCache() {
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(2);
+ Task.TaskKey key1 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 200);
+ cache.put(key1, new ThumbnailData());
+
+ // Update original data to new data
+ ThumbnailData newData = new ThumbnailData();
+ cache.updateIfAlreadyInCache(key1.id, newData);
+
+ // Data is updated to newData successfully
+ ThumbnailData result = cache.getAndInvalidateIfModified(key1);
+ assertEquals(newData, result);
+ }
+
+ @Test
+ public void updateCacheSizeAndInvalidateExcess() {
+ // Last active time are not in-sync with insertion order to simulate the real async case
+ TaskKeyByLastActiveTimeCache<ThumbnailData> cache = new TaskKeyByLastActiveTimeCache<>(4);
+ Task.TaskKey key1 = new Task.TaskKey(1, 0, new Intent(),
+ new ComponentName("", ""), 0, 200);
+ cache.put(key1, new ThumbnailData());
+
+ Task.TaskKey key2 = new Task.TaskKey(2, 0, new Intent(),
+ new ComponentName("", ""), 0, 100);
+ cache.put(key2, new ThumbnailData());
+
+ Task.TaskKey key3 = new Task.TaskKey(3, 0, new Intent(),
+ new ComponentName("", ""), 0, 400);
+ cache.put(key3, new ThumbnailData());
+
+ Task.TaskKey key4 = new Task.TaskKey(4, 0, new Intent(),
+ new ComponentName("", ""), 0, 300);
+ cache.put(key4, new ThumbnailData());
+
+ // Check that it has 4 entries before cache size changes
+ assertEquals(4, cache.getSize());
+ assertEquals(4, cache.getQueue().size());
+
+ // Update size to 2
+ cache.updateCacheSizeAndRemoveExcess(2);
+
+ // Number of entries becomes 2, only key3 and key4 remain
+ assertEquals(2, cache.getSize());
+ assertEquals(2, cache.getQueue().size());
+ assertNotNull(cache.getAndInvalidateIfModified(key3));
+ assertNotNull(cache.getAndInvalidateIfModified(key4));
+ }
+}
diff --git a/res/drawable/icon_menu_arrow_background.xml b/res/drawable/icon_menu_arrow_background.xml
new file mode 100644
index 0000000..f24022e
--- /dev/null
+++ b/res/drawable/icon_menu_arrow_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:autoMirrored="true">
+ <gradient
+ android:type="linear"
+ android:angle="0"
+ android:startColor="#00000000"
+ android:centerX="0.25"
+ android:centerColor="?androidprv:attr/materialColorSurfaceContainer"
+ android:endColor="?androidprv:attr/materialColorSurfaceContainer" />
+ <corners android:radius="@dimen/dialogCornerRadius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/icon_menu_background.xml b/res/drawable/icon_menu_background.xml
new file mode 100644
index 0000000..ec5f011
--- /dev/null
+++ b/res/drawable/icon_menu_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
+ <corners android:radius="@dimen/dialogCornerRadius" />
+</shape>
\ No newline at end of file
diff --git a/res/layout/work_mode_fab.xml b/res/layout/work_mode_fab.xml
index 32e3b77..276d73e 100644
--- a/res/layout/work_mode_fab.xml
+++ b/res/layout/work_mode_fab.xml
@@ -37,11 +37,14 @@
android:id="@+id/pause_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:maxWidth="@dimen/work_fab_width"
android:textColor="@color/work_fab_icon_color"
android:textSize="14sp"
android:includeFontPadding="false"
android:textDirection="locale"
android:text="@string/work_apps_pause_btn_text"
android:layout_marginStart="@dimen/work_fab_text_start_margin"
+ android:ellipsize="end"
+ android:maxLines="1"
style="@style/TextHeadline"/>
</com.android.launcher3.allapps.WorkModeSwitch>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index b5e4012..aa96397 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -60,7 +60,7 @@
<string name="all_apps_loading_message" msgid="5813968043155271636">"Laai tans programme …"</string>
<string name="all_apps_no_search_results" msgid="3200346862396363786">"Kon geen programme kry wat by \"<xliff:g id="QUERY">%1$s</xliff:g>\" pas nie"</string>
<string name="label_application" msgid="8531721983832654978">"Program"</string>
- <string name="all_apps_label" msgid="5015784846527570951">"Alle programme"</string>
+ <string name="all_apps_label" msgid="5015784846527570951">"Alle apps"</string>
<string name="notifications_header" msgid="1404149926117359025">"Kennisgewings"</string>
<string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Raak en hou om \'n kortpad te skuif."</string>
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dubbeltik en hou om \'n kortpad te skuif of gebruik gepasmaakte handelinge."</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c6fce28..7661bd7 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -141,6 +141,7 @@
<dimen name="work_fab_icon_size">24dp</dimen>
<dimen name="work_fab_text_start_margin">8dp</dimen>
<dimen name="work_card_padding_horizontal">10dp</dimen>
+ <dimen name="work_fab_width">214dp</dimen>
<dimen name="work_card_button_height">52dp</dimen>
<dimen name="work_fab_margin">16dp</dimen>
<dimen name="work_fab_margin_bottom">20dp</dimen>
@@ -398,6 +399,10 @@
<dimen name="task_thumbnail_icon_size">0dp</dimen>
<dimen name="task_thumbnail_icon_drawable_size">0dp</dimen>
<dimen name="task_thumbnail_icon_drawable_size_grid">0dp</dimen>
+ <dimen name="task_thumbnail_icon_menu_max_width">0dp</dimen>
+ <dimen name="task_thumbnail_icon_menu_drawable_size">0dp</dimen>
+ <dimen name="task_thumbnail_icon_menu_drawable_touch_size">0dp</dimen>
+ <dimen name="task_menu_vertical_padding">0dp</dimen>
<dimen name="overview_task_margin">0dp</dimen>
<dimen name="overview_actions_height">0dp</dimen>
<dimen name="overview_actions_button_spacing">0dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1b46b4d..a2f4a61 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -433,7 +433,7 @@
<!-- button string shown to dismiss work tab education -->
<string name="work_apps_paused_edu_accept">Got it</string>
- <!-- button string shown pause work profile -->
+ <!-- button string shown pause work profile [CHAR LIMIT=28] -->
<string name="work_apps_pause_btn_text">Pause work apps</string>
<!-- button string shown enable work profile -->
<string name="work_apps_enable_btn_text">Unpause</string>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index bd004e9..879000a 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,7 +16,7 @@
package com.android.launcher3;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ICON_LABEL_AUTO_SCALING;
import static com.android.launcher3.config.FeatureFlags.enableCursorHoverStates;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
@@ -39,6 +39,7 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.icu.text.MessageFormat;
+import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
@@ -148,6 +149,7 @@
private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
private final ActivityContext mActivity;
private FastBitmapDrawable mIcon;
+ private DeviceProfile mDeviceProfile;
private boolean mCenterVertically;
protected int mDisplay;
@@ -206,35 +208,35 @@
mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
mIsRtl = (getResources().getConfiguration().getLayoutDirection()
== View.LAYOUT_DIRECTION_RTL);
- DeviceProfile grid = mActivity.getDeviceProfile();
+ mDeviceProfile = mActivity.getDeviceProfile();
mDisplay = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
final int defaultIconSize;
if (mDisplay == DISPLAY_WORKSPACE) {
- setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
- setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
- defaultIconSize = grid.iconSizePx;
- setCenterVertically(grid.iconCenterVertically);
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, mDeviceProfile.iconTextSizePx);
+ setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
+ defaultIconSize = mDeviceProfile.iconSizePx;
+ setCenterVertically(mDeviceProfile.iconCenterVertically);
} else if (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW
|| mDisplay == DISPLAY_SEARCH_RESULT_APP_ROW) {
- setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
- setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
- defaultIconSize = grid.allAppsIconSizePx;
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, mDeviceProfile.allAppsIconTextSizePx);
+ setCompoundDrawablePadding(mDeviceProfile.allAppsIconDrawablePaddingPx);
+ defaultIconSize = mDeviceProfile.allAppsIconSizePx;
} else if (mDisplay == DISPLAY_FOLDER) {
- setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
- setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
- defaultIconSize = grid.folderChildIconSizePx;
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, mDeviceProfile.folderChildTextSizePx);
+ setCompoundDrawablePadding(mDeviceProfile.folderChildDrawablePaddingPx);
+ defaultIconSize = mDeviceProfile.folderChildIconSizePx;
} else if (mDisplay == DISPLAY_SEARCH_RESULT) {
- setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, mDeviceProfile.allAppsIconTextSizePx);
defaultIconSize = getResources().getDimensionPixelSize(R.dimen.search_row_icon_size);
} else if (mDisplay == DISPLAY_SEARCH_RESULT_SMALL) {
defaultIconSize = getResources().getDimensionPixelSize(
R.dimen.search_row_small_icon_size);
} else if (mDisplay == DISPLAY_TASKBAR) {
- defaultIconSize = grid.iconSizePx;
+ defaultIconSize = mDeviceProfile.iconSizePx;
} else {
// widget_selection or shortcut_popup
- defaultIconSize = grid.iconSizePx;
+ defaultIconSize = mDeviceProfile.iconSizePx;
}
mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
@@ -274,8 +276,7 @@
mDotParams.scale = 0f;
mForceHideDot = false;
setBackground(null);
- if (Flags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()
- || FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()) {
+ if (FeatureFlags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()) {
setMaxLines(1);
}
@@ -407,8 +408,8 @@
* Only if actual text can be displayed in two line, the {@code true} value will be effective.
*/
protected boolean shouldUseTwoLine() {
- return ((Flags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get())
- && mDisplay == DISPLAY_ALL_APPS)
+ return ((FeatureFlags.enableTwolineAllapps())
+ && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW))
|| (FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()
&& mDisplay == DISPLAY_SEARCH_RESULT);
}
@@ -691,21 +692,28 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int height = MeasureSpec.getSize(heightMeasureSpec);
if (mCenterVertically) {
Paint.FontMetrics fm = getPaint().getFontMetrics();
int cellHeightPx = mIconSize + getCompoundDrawablePadding() +
(int) Math.ceil(fm.bottom - fm.top);
- int height = MeasureSpec.getSize(heightMeasureSpec);
setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(),
getPaddingBottom());
}
// Only apply two line for all_apps and device search only if necessary.
if (shouldUseTwoLine() && (mLastOriginalText != null)) {
+ int allowedVerticalSpace = height - getPaddingTop() - getPaddingBottom()
+ - mDeviceProfile.allAppsIconSizePx
+ - mDeviceProfile.allAppsIconDrawablePaddingPx;
CharSequence modifiedString = modifyTitleToSupportMultiLine(
MeasureSpec.getSize(widthMeasureSpec) - getCompoundPaddingLeft()
- getCompoundPaddingRight(),
+ allowedVerticalSpace,
mLastOriginalText,
- getPaint(), mBreakPointsIntArray);
+ getPaint(),
+ mBreakPointsIntArray,
+ getLineSpacingMultiplier(),
+ getLineSpacingExtra());
if (!TextUtils.equals(modifiedString, mLastModifiedText)) {
mLastModifiedText = modifiedString;
setText(modifiedString);
@@ -785,7 +793,8 @@
* many words as it can until the limit is reached. Once the limit is reached, we decide to
* either return the original title or continue on a new line. How to get the new string is by
* iterating through the list of break points and determining if the strings between the break
- * points can fit within the line it is in.
+ * points can fit within the line it is in. We will show the modified string if there is enough
+ * horizontal and vertical space, otherwise this method will just return the original string.
* Example assuming each character takes up one spot:
* title = "Battery Stats", breakpoint = [6], stringPtr = 0, limitedWidth = 7
* We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) -> Battery,
@@ -794,8 +803,9 @@
* if the first char is a SPACE, we trim to append "Stats". So resulting string would be
* "Battery\nStats"
*/
- public static CharSequence modifyTitleToSupportMultiLine(int limitedWidth, CharSequence title,
- TextPaint paint, IntArray breakPoints) {
+ public static CharSequence modifyTitleToSupportMultiLine(int limitedWidth, int limitedHeight,
+ CharSequence title, TextPaint paint, IntArray breakPoints, float spacingMultiplier,
+ float spacingExtra) {
// current title is less than the width allowed so we can just skip
if (title == null || paint.measureText(title, 0, title.length()) <= limitedWidth) {
return title;
@@ -816,11 +826,7 @@
if (runningWidth <= limitedWidth) {
newString.append(currentWord);
} else {
- // there is no more space
- if (i == 0) {
- // if the first words exceeds width, just return as the first line will ellipse
- return title;
- } else {
+ if (i != 0) {
// If putting word onto a new line, make sure there is no space or new line
// character in the beginning of the current word and just put in the rest of
// the characters.
@@ -834,8 +840,14 @@
: EMPTY;
}
newString.append(NEW_LINE).append(lastCharacters);
- return newString.toString();
+ StaticLayout staticLayout = new StaticLayout(newString, paint, limitedWidth,
+ ALIGN_NORMAL, spacingMultiplier, spacingExtra, false);
+ if (staticLayout.getHeight() < limitedHeight) {
+ return newString.toString();
+ }
}
+ // if the first words exceeds width, just return as the first line will ellipse
+ return title;
}
if (i >= breakPoints.size()) {
// no need to look forward into the string if we've already finished processing
@@ -865,7 +877,7 @@
if ((info.runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0
|| info.hasPromiseIconUi()
|| (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
- || (ENABLE_DOWNLOAD_APP_UX_V2.get() && icon != null)) {
+ || (icon != null)) {
updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null);
}
}
@@ -902,9 +914,7 @@
if (mIcon instanceof PreloadIconDrawable) {
preloadIconDrawable = (PreloadIconDrawable) mIcon;
preloadIconDrawable.setLevel(progressLevel);
- preloadIconDrawable.setIsDisabled(ENABLE_DOWNLOAD_APP_UX_V2.get()
- ? info.getProgressLevel() == 0
- : !info.isAppStartable());
+ preloadIconDrawable.setIsDisabled(info.getProgressLevel() == 0);
} else {
preloadIconDrawable = makePreloadIcon();
setIcon(preloadIconDrawable);
@@ -929,9 +939,7 @@
final PreloadIconDrawable preloadDrawable = newPendingIcon(getContext(), info);
preloadDrawable.setLevel(progressLevel);
- preloadDrawable.setIsDisabled(ENABLE_DOWNLOAD_APP_UX_V2.get()
- ? info.getProgressLevel() == 0
- : !info.isAppStartable());
+ preloadDrawable.setIsDisabled(info.getProgressLevel() == 0);
return preloadDrawable;
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index c05afc9..94eb7a3 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.Utilities.pxFromSp;
import static com.android.launcher3.config.FeatureFlags.ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.icons.GraphicsUtils.getShapePath;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
@@ -250,6 +251,7 @@
public int overviewTaskIconSizePx;
public int overviewTaskIconDrawableSizePx;
public int overviewTaskIconDrawableSizeGridPx;
+ public int overviewTaskIconAppChipMenuDrawableSizePx;
public int overviewTaskThumbnailTopMarginPx;
public final int overviewActionsHeight;
public final int overviewActionsTopMarginPx;
@@ -300,7 +302,6 @@
// If true, used to layout taskbar in 3 button navigation mode.
public final boolean startAlignTaskbar;
public final boolean isTransientTaskbar;
-
// DragController
public int flingToDeleteThresholdVelocity;
@@ -309,7 +310,8 @@
SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode,
boolean transposeLayoutWithOrientation, boolean isMultiDisplay, boolean isGestureMode,
@NonNull final ViewScaleProvider viewScaleProvider,
- @NonNull final Consumer<DeviceProfile> dimensionOverrideProvider) {
+ @NonNull final Consumer<DeviceProfile> dimensionOverrideProvider,
+ boolean isTransientTaskbar) {
this.inv = inv;
this.isLandscape = windowBounds.isLandscape();
@@ -367,7 +369,7 @@
}
}
- isTransientTaskbar = DisplayController.isTransientTaskbar(context);
+ this.isTransientTaskbar = isTransientTaskbar;
if (!isTaskbarPresent) {
taskbarIconSize = taskbarHeight = stashedTaskbarHeight = taskbarBottomMargin = 0;
startAlignTaskbar = false;
@@ -614,12 +616,17 @@
desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin);
- overviewTaskIconSizePx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
+ overviewTaskIconSizePx = enableOverviewIconMenu() ? res.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_drawable_touch_size) : res.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_size);
overviewTaskIconDrawableSizePx =
res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size);
overviewTaskIconDrawableSizeGridPx =
res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid);
- overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx;
+ overviewTaskIconAppChipMenuDrawableSizePx = res.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_drawable_size);
+ overviewTaskThumbnailTopMarginPx =
+ enableOverviewIconMenu() ? 0 : overviewTaskIconSizePx + overviewTaskMarginPx;
// Don't add margin with floating search bar to minimize risk of overlapping.
overviewActionsTopMarginPx = FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() ? 0
: res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
@@ -1133,6 +1140,10 @@
if (isVerticalBarLayout()) {
hideWorkspaceLabelsIfNotEnoughSpace();
}
+ if (FeatureFlags.enableTwolineAllapps()) {
+ // Add extra textHeight to the existing allAppsCellHeight.
+ allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx);
+ }
updateHotseatSizes(iconSizePx);
@@ -2012,6 +2023,8 @@
overviewTaskIconDrawableSizePx));
writer.println(prefix + pxToDpStr("overviewTaskIconDrawableSizeGridPx",
overviewTaskIconDrawableSizeGridPx));
+ writer.println(prefix + pxToDpStr("overviewTaskIconAppChipMenuDrawableSizePx",
+ overviewTaskIconAppChipMenuDrawableSizePx));
writer.println(prefix + pxToDpStr("overviewTaskThumbnailTopMarginPx",
overviewTaskThumbnailTopMarginPx));
writer.println(prefix + pxToDpStr("overviewActionsTopMarginPx",
@@ -2119,10 +2132,13 @@
private Consumer<DeviceProfile> mOverrideProvider;
+ private boolean mIsTransientTaskbar;
+
public Builder(Context context, InvariantDeviceProfile inv, Info info) {
mContext = context;
mInv = inv;
mInfo = info;
+ mIsTransientTaskbar = info.isTransientTaskbar();
}
public Builder setMultiWindowMode(boolean isMultiWindowMode) {
@@ -2173,6 +2189,15 @@
return this;
}
+ /**
+ * Set the isTransientTaskbar for the builder
+ * @return This Builder
+ */
+ public Builder setIsTransientTaskbar(boolean isTransientTaskbar) {
+ mIsTransientTaskbar = isTransientTaskbar;
+ return this;
+ }
+
public DeviceProfile build() {
if (mWindowBounds == null) {
throw new IllegalArgumentException("Window bounds not set");
@@ -2194,7 +2219,7 @@
}
return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache,
mIsMultiWindowMode, mTransposeLayoutWithOrientation, mIsMultiDisplay,
- mIsGestureMode, mViewScaleProvider, mOverrideProvider);
+ mIsGestureMode, mViewScaleProvider, mOverrideProvider, mIsTransientTaskbar);
}
}
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 8707aba..04e8da1 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
+import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.annotation.TargetApi;
@@ -224,7 +225,7 @@
DisplayController.INSTANCE.get(context).setPriorityListener(
(displayContext, info, flags) -> {
if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
- | CHANGE_NAVIGATION_MODE)) != 0) {
+ | CHANGE_NAVIGATION_MODE | CHANGE_TASKBAR_PINNING)) != 0) {
onConfigChanged(displayContext);
}
});
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b8e7737..2b433f1 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -43,7 +43,6 @@
import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
@@ -114,7 +113,6 @@
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
-import android.view.KeyboardShortcutInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MotionEvent;
@@ -137,7 +135,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.accessibility.BaseAccessibilityDelegate.LauncherAction;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.AllAppsRecyclerView;
@@ -205,6 +202,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.KeyboardShortcutsDelegate;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.PackageUserKey;
@@ -335,6 +333,7 @@
private static final boolean DESKTOP_MODE_SUPPORTED =
"1".equals(Utilities.getSystemProperty("persist.wm.debug.desktop_mode_2", "0"));
+ KeyboardShortcutsDelegate mKeyboardShortcutsDelegate = new KeyboardShortcutsDelegate(this);
@Thunk
Workspace<?> mWorkspace;
@Thunk
@@ -427,6 +426,10 @@
private final List<BackPressHandler> mBackPressedHandlers = new ArrayList<>();
+ public static Launcher getLauncher(Context context) {
+ return fromContext(context);
+ }
+
@Override
@TargetApi(Build.VERSION_CODES.S)
protected void onCreate(Bundle savedInstanceState) {
@@ -672,10 +675,6 @@
return new OnboardingPrefs<>(this, sharedPrefs);
}
- public OnboardingPrefs<? extends Launcher> getOnboardingPrefs() {
- return mOnboardingPrefs;
- }
-
@Override
public void onPluginConnected(LauncherOverlayPlugin overlayManager, Context context) {
switchOverlay(() -> overlayManager.createOverlayManager(this, this));
@@ -789,56 +788,8 @@
return true;
}
- @Override
- public CellPosMapper getCellPosMapper() {
- return mCellPosMapper;
- }
-
- public RotationHelper getRotationHelper() {
- return mRotationHelper;
- }
-
- public ViewGroupFocusHelper getFocusHandler() {
- return mFocusHandler;
- }
-
- @Override
- public StateManager<LauncherState> getStateManager() {
- return mStateManager;
- }
-
private LauncherCallbacks mLauncherCallbacks;
- /**
- * Call this after onCreate to set or clear overlay.
- */
- @Override
- public void setLauncherOverlay(LauncherOverlay overlay) {
- mWorkspace.setLauncherOverlay(overlay);
- }
-
- public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
- mLauncherCallbacks = callbacks;
- return true;
- }
-
- public boolean isDraggingEnabled() {
- // We prevent dragging when we are loading the workspace as it is possible to pick up a view
- // that is subsequently removed from the workspace in startBinding().
- return !isWorkspaceLoading();
- }
-
- @NonNull
- @Override
- public PopupDataProvider getPopupDataProvider() {
- return mPopupDataProvider;
- }
-
- @Override
- public DotInfo getDotInfoForItem(ItemInfo info) {
- return mPopupDataProvider.getDotInfoForItem(info);
- }
-
@Override
public void invalidateParent(ItemInfo info) {
if (info.container >= 0) {
@@ -1178,10 +1129,6 @@
mDeferOverlayCallbacks = true;
}
- public LauncherOverlayManager getOverlayManager() {
- return mOverlayManager;
- }
-
@Override
public void onStateSetStart(LauncherState state) {
super.onStateSetStart(state);
@@ -1589,79 +1536,11 @@
return instance;
}
- public AllAppsTransitionController getAllAppsController() {
- return mAllAppsController;
- }
-
- @Override
- public DragLayer getDragLayer() {
- return mDragLayer;
- }
-
- @Override
- public ActivityAllAppsContainerView<Launcher> getAppsView() {
- return mAppsView;
- }
-
- public Workspace<?> getWorkspace() {
- return mWorkspace;
- }
-
- public Hotseat getHotseat() {
- return mHotseat;
- }
-
- public <T extends View> T getOverviewPanel() {
- return (T) mOverviewPanel;
- }
-
- public DropTargetBar getDropTargetBar() {
- return mDropTargetBar;
- }
-
- @Override
- public ScrimView getScrimView() {
- return mScrimView;
- }
-
- public LauncherWidgetHolder getAppWidgetHolder() {
- return mAppWidgetHolder;
- }
-
protected LauncherWidgetHolder createAppWidgetHolder() {
return LauncherWidgetHolder.HolderFactory.newFactory(this).newInstance(
this, appWidgetId -> getWorkspace().removeWidget(appWidgetId));
}
- public LauncherModel getModel() {
- return mModel;
- }
-
- /**
- * Returns the ModelWriter writer, make sure to call the function every time you want to use it.
- */
- public ModelWriter getModelWriter() {
- return mModelWriter;
- }
-
- @Override
- public SharedPreferences getSharedPrefs() {
- return mSharedPrefs;
- }
-
- @Override
- public SharedPreferences getDevicePrefs() {
- return LauncherPrefs.getDevicePrefs(this);
- }
-
- public int getOrientation() {
- return mOldConfig.orientation;
- }
-
- public BaseSearchConfig getSearchConfig() {
- return mBaseSearchConfig;
- }
-
@Override
protected void onNewIntent(Intent intent) {
if (Utilities.isRunningInTestHarness()) {
@@ -1895,27 +1774,6 @@
mStateManager.goToState(NORMAL);
}
- public boolean isWorkspaceLocked() {
- return mWorkspaceLoading || mPendingRequestArgs != null;
- }
-
- public boolean isWorkspaceLoading() {
- return mWorkspaceLoading;
- }
-
- @Override
- public boolean isBindingItems() {
- return mWorkspaceLoading;
- }
-
- private void setWorkspaceLoading(boolean value) {
- mWorkspaceLoading = value;
- }
-
- public void setWaitingForResult(PendingRequestArgs args) {
- mPendingRequestArgs = args;
- }
-
void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
WidgetAddFlowHandler addFlowHandler) {
if (LOGD) {
@@ -2145,13 +2003,6 @@
return super.dispatchTouchEvent(ev);
}
- /**
- * Returns true if a touch interaction is in progress
- */
- public boolean isTouchInProgress() {
- return mTouchInProgress;
- }
-
@Override
@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void onBackPressed() {
@@ -2213,16 +2064,6 @@
return mHotseat != null && (layout == mHotseat);
}
- /**
- * Returns the CellLayout of the specified container at the specified screen.
- *
- * @param screenId must be presenterPos and not modelPos.
- */
- public CellLayout getCellLayout(int container, int screenId) {
- return (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
- ? mHotseat : mWorkspace.getScreenWithId(screenId);
- }
-
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
@@ -2249,22 +2090,6 @@
return result;
}
- /**
- * Persistant callback which notifies when an activity launch is deferred because the activity
- * was not yet resumed.
- */
- public void setOnDeferredActivityLaunchCallback(Runnable callback) {
- mOnDeferredActivityLaunchCallback = callback;
- }
-
- /**
- * Sets the next pages to bind synchronously on next bind.
- * @param pages should not be null.
- */
- public void setPagesToBindSynchronously(@NonNull IntSet pages) {
- mPagesToBindSynchronously = pages;
- }
-
@Override
public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
IntSet visibleIds;
@@ -3110,11 +2935,6 @@
mAppsView.updateWorkUI();
}
- @Override
- public StringCache getStringCache() {
- return mStringCache;
- }
-
/**
* @param packageUser if null, refreshes all widgets and shortcuts, otherwise only
* refreshes the widgets and shortcuts associated with the given package/user
@@ -3168,6 +2988,7 @@
mStateManager.dump(prefix, writer);
mPopupDataProvider.dump(prefix, writer);
mDeviceProfile.dump(this, prefix, writer);
+ mAppsView.getAppsStore().dump(prefix, writer);
try {
FileLog.flushAll(writer);
@@ -3183,84 +3004,51 @@
mOverlayManager.dump(prefix, writer);
}
+ /**
+ * Populates the list of shortcuts. Logic delegated to {@Link KeyboardShortcutsDelegate}.
+ *
+ * @param data The data list to populate with shortcuts.
+ * @param menu The current menu, which may be null.
+ * @param deviceId The id for the connected device the shortcuts should be provided for.
+ */
@Override
public void onProvideKeyboardShortcuts(
List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
-
- ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
- if (isInState(NORMAL)) {
- shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
- KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
- shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.widget_button_text),
- KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON));
- }
- getSupportedActions(this, getCurrentFocus()).forEach(la ->
- shortcutInfos.add(new KeyboardShortcutInfo(
- la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON)));
- if (!shortcutInfos.isEmpty()) {
- data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
- }
-
+ mKeyboardShortcutsDelegate.onProvideKeyboardShortcuts(data, menu, deviceId);
super.onProvideKeyboardShortcuts(data, menu, deviceId);
}
+ /**
+ * Logic delegated to {@Link KeyboardShortcutsDelegate}.
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ */
@Override
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
- if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_A:
- if (isInState(NORMAL)) {
- getStateManager().goToState(ALL_APPS);
- return true;
- }
- break;
- case KeyEvent.KEYCODE_W:
- if (isInState(NORMAL)) {
- OptionsPopupView.openWidgets(this);
- return true;
- }
- break;
- default:
- for (LauncherAction la : getSupportedActions(this, getCurrentFocus())) {
- if (la.keyCode == keyCode) {
- return la.invokeFromKeyboard(getCurrentFocus());
- }
- }
- }
- }
- return super.onKeyShortcut(keyCode, event);
+ Boolean result = mKeyboardShortcutsDelegate.onKeyShortcut(keyCode, event);
+ return result != null ? result : super.onKeyShortcut(keyCode, event);
}
+ /**
+ * Logic delegated to {@Link KeyboardShortcutsDelegate}.
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ */
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
- // Close any open floating views.
- closeOpenViews();
- return true;
- }
- return super.onKeyDown(keyCode, event);
+ Boolean result = mKeyboardShortcutsDelegate.onKeyDown(keyCode, event);
+ return result != null ? result : super.onKeyDown(keyCode, event);
}
+ /**
+ * Logic delegated to {@Link KeyboardShortcutsDelegate}.
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ */
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_MENU) {
- // KEYCODE_MENU is sent by some tests, for example
- // LauncherJankTests#testWidgetsContainerFling. Don't just remove its handling.
- if (!mDragController.isDragging() && !mWorkspace.isSwitchingState() &&
- isInState(NORMAL)) {
- // Close any open floating views.
- closeOpenViews();
-
- // Setting the touch point to (-1, -1) will show the options popup in the center of
- // the screen.
- if (Utilities.isRunningInTestHarness()) {
- Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up");
- }
- showDefaultOptions(-1, -1);
- }
- return true;
- }
- return super.onKeyUp(keyCode, event);
+ Boolean result = mKeyboardShortcutsDelegate.onKeyUp(keyCode, event);
+ return result != null ? result : super.onKeyUp(keyCode, event);
}
/**
@@ -3271,18 +3059,6 @@
false);
}
- /**
- * Returns target rectangle for anchoring a popup menu.
- */
- protected RectF getPopupTarget(float x, float y) {
- float halfSize = getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
- if (x < 0 || y < 0) {
- x = mDragLayer.getWidth() / 2;
- y = mDragLayer.getHeight() / 2;
- }
- return new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
- }
-
@Override
public boolean canUseMultipleShadesForPopup() {
return getTopOpenViewWithType(this, TYPE_FOLDER) == null
@@ -3338,7 +3114,7 @@
getStateManager().goToState(LauncherState.NORMAL);
}
- private void closeOpenViews() {
+ public void closeOpenViews() {
closeOpenViews(true);
}
@@ -3346,10 +3122,6 @@
AbstractFloatingView.closeAllOpenViews(this, animate);
}
- public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
- return Stream.of(APP_INFO, WIDGETS, INSTALL);
- }
-
protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
return new LauncherAccessibilityDelegate(this);
}
@@ -3358,16 +3130,6 @@
@VisibleForTesting
public void enableHotseatEdu(boolean enable) {}
- /**
- * @see LauncherState#getOverviewScaleAndOffset(Launcher)
- */
- public float[] getNormalOverviewScaleAndOffset() {
- return new float[] {NO_SCALE, NO_OFFSET};
- }
-
- public static Launcher getLauncher(Context context) {
- return fromContext(context);
- }
/**
* Just a wrapper around the type cast to allow easier tracking of calls.
@@ -3405,20 +3167,6 @@
public Configuration config;
}
- @Override
- public StatsLogManager getStatsLogManager() {
- return super.getStatsLogManager().withDefaultInstanceId(mAllAppsSessionLogId);
- }
-
- /**
- * Returns the current popup for testing, if any.
- */
- @VisibleForTesting
- @Nullable
- public ArrowPopup<?> getOptionsPopup() {
- return findViewById(R.id.popup_container);
- }
-
/** Pauses view updates that should not be run during the app launch animation. */
public void pauseExpensiveViewUpdates() {
// Pause page indicator animations as they lead to layer trashing.
@@ -3452,9 +3200,212 @@
return false; // Base launcher does not track freeform tasks
}
+ // Getters and Setters
+
+ private void setWorkspaceLoading(boolean value) {
+ mWorkspaceLoading = value;
+ }
+
+ public boolean isWorkspaceLocked() {
+ return mWorkspaceLoading || mPendingRequestArgs != null;
+ }
+
+ public boolean isWorkspaceLoading() {
+ return mWorkspaceLoading;
+ }
+
@Override
- public View.OnLongClickListener getAllAppsItemLongClickListener() {
- return ItemLongClickListener.INSTANCE_ALL_APPS;
+ public boolean isBindingItems() {
+ return mWorkspaceLoading;
+ }
+
+ /**
+ * Returns true if a touch interaction is in progress
+ */
+ public boolean isTouchInProgress() {
+ return mTouchInProgress;
+ }
+
+ public boolean isDraggingEnabled() {
+ // We prevent dragging when we are loading the workspace as it is possible to pick up a view
+ // that is subsequently removed from the workspace in startBinding().
+ return !isWorkspaceLoading();
+ }
+
+ public void setWaitingForResult(PendingRequestArgs args) {
+ mPendingRequestArgs = args;
+ }
+
+ /**
+ * Call this after onCreate to set or clear overlay.
+ */
+ @Override
+ public void setLauncherOverlay(LauncherOverlay overlay) {
+ mWorkspace.setLauncherOverlay(overlay);
+ }
+
+ public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
+ mLauncherCallbacks = callbacks;
+ return true;
+ }
+
+ /**
+ * Persistent callback which notifies when an activity launch is deferred because the activity
+ * was not yet resumed.
+ */
+ public void setOnDeferredActivityLaunchCallback(Runnable callback) {
+ mOnDeferredActivityLaunchCallback = callback;
+ }
+
+ /**
+ * Sets the next pages to bind synchronously on next bind.
+ * @param pages should not be null.
+ */
+ public void setPagesToBindSynchronously(@NonNull IntSet pages) {
+ mPagesToBindSynchronously = pages;
+ }
+
+ public OnboardingPrefs<? extends Launcher> getOnboardingPrefs() {
+ return mOnboardingPrefs;
+ }
+
+ @Override
+ public CellPosMapper getCellPosMapper() {
+ return mCellPosMapper;
+ }
+
+ public RotationHelper getRotationHelper() {
+ return mRotationHelper;
+ }
+
+ public ViewGroupFocusHelper getFocusHandler() {
+ return mFocusHandler;
+ }
+
+ @Override
+ public StateManager<LauncherState> getStateManager() {
+ return mStateManager;
+ }
+
+ @NonNull
+ @Override
+ public PopupDataProvider getPopupDataProvider() {
+ return mPopupDataProvider;
+ }
+
+ @Override
+ public DotInfo getDotInfoForItem(ItemInfo info) {
+ return mPopupDataProvider.getDotInfoForItem(info);
+ }
+
+ public LauncherOverlayManager getOverlayManager() {
+ return mOverlayManager;
+ }
+
+ public AllAppsTransitionController getAllAppsController() {
+ return mAllAppsController;
+ }
+
+ @Override
+ public DragLayer getDragLayer() {
+ return mDragLayer;
+ }
+
+ @Override
+ public ActivityAllAppsContainerView<Launcher> getAppsView() {
+ return mAppsView;
+ }
+
+ public Workspace<?> getWorkspace() {
+ return mWorkspace;
+ }
+
+ public Hotseat getHotseat() {
+ return mHotseat;
+ }
+
+ public <T extends View> T getOverviewPanel() {
+ return (T) mOverviewPanel;
+ }
+
+ public DropTargetBar getDropTargetBar() {
+ return mDropTargetBar;
+ }
+
+ @Override
+ public ScrimView getScrimView() {
+ return mScrimView;
+ }
+
+ public LauncherWidgetHolder getAppWidgetHolder() {
+ return mAppWidgetHolder;
+ }
+
+ public LauncherModel getModel() {
+ return mModel;
+ }
+
+ /**
+ * Returns the ModelWriter writer, make sure to call the function every time you want to use it.
+ */
+ public ModelWriter getModelWriter() {
+ return mModelWriter;
+ }
+
+ @Override
+ public SharedPreferences getSharedPrefs() {
+ return mSharedPrefs;
+ }
+
+ @Override
+ public SharedPreferences getDevicePrefs() {
+ return LauncherPrefs.getDevicePrefs(this);
+ }
+
+ public int getOrientation() {
+ return mOldConfig.orientation;
+ }
+
+ public BaseSearchConfig getSearchConfig() {
+ return mBaseSearchConfig;
+ }
+
+ /**
+ * Returns the CellLayout of the specified container at the specified screen.
+ *
+ * @param screenId must be presenterPos and not modelPos.
+ */
+ public CellLayout getCellLayout(int container, int screenId) {
+ return (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
+ ? mHotseat : mWorkspace.getScreenWithId(screenId);
+ }
+
+ @Override
+ public StringCache getStringCache() {
+ return mStringCache;
+ }
+
+ /**
+ * Returns target rectangle for anchoring a popup menu.
+ */
+ protected RectF getPopupTarget(float x, float y) {
+ float halfSize = getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
+ if (x < 0 || y < 0) {
+ x = mDragLayer.getWidth() / 2;
+ y = mDragLayer.getHeight() / 2;
+ }
+ return new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
+ }
+
+ public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
+ return Stream.of(APP_INFO, WIDGETS, INSTALL);
+ }
+
+ /**
+ * @see LauncherState#getOverviewScaleAndOffset(Launcher)
+ */
+ public float[] getNormalOverviewScaleAndOffset() {
+ return new float[] {NO_SCALE, NO_OFFSET};
}
/**
@@ -3471,4 +3422,25 @@
public CannedAnimationCoordinator getAnimationCoordinator() {
return mAnimationCoordinator;
}
+
+ @Override
+ public View.OnLongClickListener getAllAppsItemLongClickListener() {
+ return ItemLongClickListener.INSTANCE_ALL_APPS;
+ }
+
+ @Override
+ public StatsLogManager getStatsLogManager() {
+ return super.getStatsLogManager().withDefaultInstanceId(mAllAppsSessionLogId);
+ }
+
+ /**
+ * Returns the current popup for testing, if any.
+ */
+ @VisibleForTesting
+ @Nullable
+ public ArrowPopup<?> getOptionsPopup() {
+ return findViewById(R.id.popup_container);
+ }
+
+ // End of Getters and Setters
}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 36d37c7..e8d5116 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -59,8 +59,12 @@
IS_STARTUP_DATA_MIGRATED.defaultValue
)
+ // TODO: Remove `item == TASKBAR_PINNING` once isBootAwareStartupDataEnabled is always true
private fun chooseSharedPreferences(item: Item): SharedPreferences =
- if (isBootAwareStartupDataEnabled && item.isBootAware && isStartupDataMigrated)
+ if (
+ (isBootAwareStartupDataEnabled && item.isBootAware && isStartupDataMigrated) ||
+ item == TASKBAR_PINNING
+ )
bootAwarePrefs
else item.encryptedPrefs
@@ -283,7 +287,7 @@
@JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
@JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", true)
@JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, true)
- @JvmField val TASKBAR_PINNING = backedUpItem(TASKBAR_PINNING_KEY, false)
+ @JvmField val TASKBAR_PINNING = backedUpItem(TASKBAR_PINNING_KEY, false, true)
@JvmField
val DEVICE_TYPE =
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index e724858..7867f44 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -37,6 +37,7 @@
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.views.ActivityContext;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -82,17 +83,29 @@
}
/**
+ * Calling {@link #setApps(AppInfo[], int, Map, boolean)} with shouldPreinflate set to
+ * {@code true}. This method should be called in launcher (not for taskbar).
+ */
+ public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
+ setApps(apps, flags, map, /* shouldPreinflate= */ true);
+ }
+
+ /**
* Sets the current set of apps and sets mapping for {@link PackageUserKey} to Uid for
* the current set of apps.
+ *
+ * <p> Note that shouldPreinflate param should be set to {@code false} for taskbar, because this
+ * method is too late to preinflate all apps, as user will open all apps in the same frame.
*/
- public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map) {
+ public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map,
+ boolean shouldPreinflate) {
mApps = apps == null ? EMPTY_ARRAY : apps;
mModelFlags = flags;
notifyUpdate();
mPackageUserKeytoUidMap = map;
// Preinflate all apps RV when apps has changed, which can happen after unlocking screen,
// rotating screen, or downloading/upgrading apps.
- if (ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
+ if (shouldPreinflate && ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
mAllAppsRecyclerViewPool.preInflateAllAppsViewHolders(mContext);
}
}
@@ -226,4 +239,13 @@
public interface OnUpdateListener {
void onAppsUpdated();
}
+
+ /** Generate a dumpsys for each app package name and position in the apps list */
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "\tAllAppsStore Apps[] size: " + mApps.length);
+ for (int i = 0; i < mApps.length; i++) {
+ writer.println(String.format("%s\tPackage index and name: %d/%s", prefix, i,
+ mApps[i].componentName.getPackageName()));
+ }
+ }
}
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 769c787..005e6df 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -29,7 +29,6 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
@@ -138,7 +137,6 @@
protected final OnClickListener mOnIconClickListener;
protected final OnLongClickListener mOnIconLongClickListener;
protected OnFocusChangeListener mIconFocusListener;
- private final int mExtraTextHeight;
public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) {
@@ -150,8 +148,6 @@
mOnIconLongClickListener = mActivityContext.getAllAppsItemLongClickListener();
mAdapterProvider = adapterProvider;
- mExtraTextHeight = Utilities.calculateTextHeight(
- mActivityContext.getDeviceProfile().allAppsIconTextSizePx);
}
/** Checks if the passed viewType represents all apps divider. */
@@ -177,9 +173,7 @@
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_ICON:
- int layout =
- !(Flags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get())
- ? R.layout.all_apps_icon
+ int layout = !FeatureFlags.enableTwolineAllapps() ? R.layout.all_apps_icon
: R.layout.all_apps_icon_twoline;
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
layout, parent, false);
@@ -190,9 +184,6 @@
// Ensure the all apps icon height matches the workspace icons in portrait mode.
icon.getLayoutParams().height =
mActivityContext.getDeviceProfile().allAppsCellHeightPx;
- if (Flags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()) {
- icon.getLayoutParams().height += mExtraTextHeight;
- }
return new ViewHolder(icon);
case VIEW_TYPE_EMPTY_SEARCH:
return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index da00fdc5..a3e68ba 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -35,13 +35,7 @@
/**
* Defines a set of flags used to control various launcher behaviors.
- *
- * Please only add flags to your assigned block to prevent merge conflicts. If you do not have
- * a block, please update the current empty block and add a new empty block below to prevent
- * merge conflicts with the previous block.
- * List of blocks can be found:
- * <a href="http://go/gnl-flags-block-directory">here</a>
- *
+ * <p>
* <p>All the flags should be defined here with appropriate default values.
*/
public final class FeatureFlags {
@@ -157,13 +151,6 @@
// TODO(Block 8): Clean up flags
// TODO(Block 9): Clean up flags
- public static final BooleanFlag ENABLE_DOWNLOAD_APP_UX_V2 = getReleaseFlag(270395134,
- "ENABLE_DOWNLOAD_APP_UX_V2", ENABLED, "Updates the download app UX"
- + " to have better visuals");
-
- public static final BooleanFlag ENABLE_DOWNLOAD_APP_UX_V3 = getDebugFlag(270395186,
- "ENABLE_DOWNLOAD_APP_UX_V3", ENABLED, "Updates the download app UX"
- + " to have better visuals, improve contrast, and color");
public static final BooleanFlag SHOW_DOT_PAGINATION = getDebugFlag(270395278,
"SHOW_DOT_PAGINATION", ENABLED, "Enable showing dot pagination in workspace");
@@ -191,6 +178,10 @@
"ENABLE_PARAMETRIZE_REORDER", DISABLED,
"Enables generating the reorder using a set of parameters");
+ public static final BooleanFlag ENABLE_NO_LONG_PRESS_DRAG = getDebugFlag(299748096,
+ "ENABLE_NO_LONG_PRESS_DRAG", DISABLED,
+ "Don't trigger the drag if we are still under long press");
+
// TODO(Block 12): Clean up flags
public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(270396680,
"ENABLE_MULTI_INSTANCE", DISABLED,
@@ -238,6 +229,9 @@
// Aconfig migration complete for ENABLE_TWOLINE_ALLAPPS.
public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(270390937,
"ENABLE_TWOLINE_ALLAPPS", DISABLED, "Enables two line label inside all apps.");
+ public static boolean enableTwolineAllapps() {
+ return ENABLE_TWOLINE_ALLAPPS.get() || Flags.enableTwolineAllapps();
+ }
public static final BooleanFlag IME_STICKY_SNACKBAR_EDU = getDebugFlag(270391693,
"IME_STICKY_SNACKBAR_EDU", ENABLED, "Show sticky IME edu in AllApps");
@@ -321,6 +315,15 @@
return ENABLE_GRID_ONLY_OVERVIEW.get() || Flags.enableGridOnlyOverview();
}
+ // Aconfig migration complete for ENABLE_OVERVIEW_ICON_MENU.
+ @VisibleForTesting
+ public static final BooleanFlag ENABLE_OVERVIEW_ICON_MENU = getDebugFlag(257950105,
+ "ENABLE_OVERVIEW_ICON_MENU", TEAMFOOD,
+ "Enable updated overview icon and menu within task.");
+ public static boolean enableOverviewIconMenu() {
+ return ENABLE_OVERVIEW_ICON_MENU.get() || Flags.enableOverviewIconMenu();
+ }
+
// Aconfig migration complete for ENABLE_CURSOR_HOVER_STATES.
@VisibleForTesting
public static final BooleanFlag ENABLE_CURSOR_HOVER_STATES = getDebugFlag(243191650,
@@ -430,13 +433,19 @@
public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
"ENABLE_ALL_APPS_RV_PREINFLATION", ENABLED,
"Enables preinflating all apps icons to avoid scrolling jank.");
-
- // TODO(Block 34): Clean up flags
public static final BooleanFlag ALL_APPS_GONE_VISIBILITY = getDebugFlag(291651514,
"ALL_APPS_GONE_VISIBILITY", ENABLED,
"Set all apps container view's hidden visibility to GONE instead of INVISIBLE.");
- // TODO(Block 35): Empty block
+ // TODO(Block 34): Empty block
+ // Please only add flags to your assigned block. If you do not have a block:
+ // 1. Assign yourself this block
+ // 2. Add your flag to this block
+ // 3. Add a new empty block below this one
+ // 4. Move this comment to that new empty block
+ // This is all to prevent merge conflicts in the future and help keep track of who owns which
+ // flags.
+ // List of assigned blocks can be found: http://go/gnl-flags-block-directory
public static class BooleanFlag {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 0d51d48..777f4d5 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -17,6 +17,7 @@
package com.android.launcher3.dragndrop;
import static com.android.launcher3.Utilities.ATLEAST_Q;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_NO_LONG_PRESS_DRAG;
import android.graphics.Point;
import android.graphics.Rect;
@@ -87,6 +88,10 @@
private int mLastTouchClassification;
protected int mDistanceSinceScroll = 0;
+ /**
+ * This variable is to differentiate between a long press and a drag, if it's true that means
+ * it's a long press and when it's false means that we are no longer in a long press.
+ */
protected boolean mIsInPreDrag;
private final int DRAG_VIEW_SCALE_DURATION_MS = 500;
@@ -370,7 +375,7 @@
@Override
public void onDriverDragEnd(float x, float y) {
if (!endWithFlingAnimation()) {
- drop(findDropTarget((int) x, (int) y, mCoordinatesTemp), null);
+ drop(findDropTarget((int) x, (int) y), null);
}
endDrag();
}
@@ -432,13 +437,6 @@
protected void handleMoveEvent(int x, int y) {
mDragObject.dragView.move(x, y);
- // Drop on someone?
- final int[] coordinates = mCoordinatesTemp;
- DropTarget dropTarget = findDropTarget(x, y, coordinates);
- mDragObject.x = coordinates[0];
- mDragObject.y = coordinates[1];
- checkTouchMove(dropTarget);
-
// Check if we are hovering over the scroll areas
mDistanceSinceScroll += Math.hypot(mLastTouch.x - x, mLastTouch.y - y);
mLastTouch.set(x, y);
@@ -451,6 +449,9 @@
&& mOptions.preDragCondition.shouldStartDrag(distanceDragged)) {
callOnDragStart();
}
+
+ // Drop on someone?
+ checkTouchMove(x, y);
}
public float getDistanceDragged() {
@@ -458,14 +459,15 @@
}
public void forceTouchMove() {
- int[] placeholderCoordinates = mCoordinatesTemp;
- DropTarget dropTarget = findDropTarget(mLastTouch.x, mLastTouch.y, placeholderCoordinates);
- mDragObject.x = placeholderCoordinates[0];
- mDragObject.y = placeholderCoordinates[1];
- checkTouchMove(dropTarget);
+ checkTouchMove(mLastTouch.x, mLastTouch.y);
}
- private void checkTouchMove(DropTarget dropTarget) {
+ private DropTarget checkTouchMove(final int x, final int y) {
+ // If we are in predrag, don't trigger any other event until we get out of it
+ if (ENABLE_NO_LONG_PRESS_DRAG.get() && mIsInPreDrag) {
+ return mLastDropTarget;
+ }
+ DropTarget dropTarget = findDropTarget(x, y);
if (dropTarget != null) {
if (mLastDropTarget != dropTarget) {
if (mLastDropTarget != null) {
@@ -474,12 +476,11 @@
dropTarget.onDragEnter(mDragObject);
}
dropTarget.onDragOver(mDragObject);
- } else {
- if (mLastDropTarget != null) {
- mLastDropTarget.onDragExit(mDragObject);
- }
+ } else if (mLastDropTarget != null) {
+ mLastDropTarget.onDragExit(mDragObject);
}
mLastDropTarget = dropTarget;
+ return mLastDropTarget;
}
/**
@@ -487,13 +488,8 @@
* we manually ensure appropriate drag and drop events get emulated for accessible drag.
*/
public void completeAccessibleDrag(int[] location) {
- final int[] coordinates = mCoordinatesTemp;
-
// We make sure that we prime the target for drop.
- DropTarget dropTarget = findDropTarget(location[0], location[1], coordinates);
- mDragObject.x = coordinates[0];
- mDragObject.y = coordinates[1];
- checkTouchMove(dropTarget);
+ DropTarget dropTarget = checkTouchMove(location[0], location[1]);
dropTarget.prepareAccessibilityDrop();
// Perform the drop
@@ -502,10 +498,6 @@
}
protected void drop(DropTarget dropTarget, Runnable flingAnimation) {
- final int[] coordinates = mCoordinatesTemp;
- mDragObject.x = coordinates[0];
- mDragObject.y = coordinates[1];
-
// Move dragging to the final target.
if (dropTarget != mLastDropTarget) {
if (mLastDropTarget != null) {
@@ -542,9 +534,9 @@
dispatchDropComplete(dropTargetAsView, accepted);
}
- private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
- mDragObject.x = x;
- mDragObject.y = y;
+ private DropTarget findDropTarget(final int x, final int y) {
+ mCoordinatesTemp[0] = x;
+ mCoordinatesTemp[1] = y;
final Rect r = mRectTemp;
final ArrayList<DropTarget> dropTargets = mDropTargets;
@@ -556,17 +548,17 @@
target.getHitRectRelativeToDragLayer(r);
if (r.contains(x, y)) {
- dropCoordinates[0] = x;
- dropCoordinates[1] = y;
- mActivity.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates);
+ mActivity.getDragLayer().mapCoordInSelfToDescendant((View) target,
+ mCoordinatesTemp);
+ mDragObject.x = mCoordinatesTemp[0];
+ mDragObject.y = mCoordinatesTemp[1];
return target;
}
}
- // Pass all unhandled drag to workspace. Workspace finds the correct
- // cell layout to drop to in the existing drag/drop logic.
- dropCoordinates[0] = x;
- dropCoordinates[1] = y;
- return getDefaultDropTarget(dropCoordinates);
+ DropTarget dropTarget = getDefaultDropTarget(mCoordinatesTemp);
+ mDragObject.x = mCoordinatesTemp[0];
+ mDragObject.y = mCoordinatesTemp[1];
+ return dropTarget;
}
protected abstract DropTarget getDefaultDropTarget(int[] dropCoordinates);
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 307052a..3e77c78 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -19,8 +19,6 @@
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V3;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -76,10 +74,8 @@
// Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
private static final float COMPLETE_ANIM_FRACTION = 1f;
- private static final float SMALL_SCALE = ENABLE_DOWNLOAD_APP_UX_V3.get() ? 0.8f : 0.7f;
- private static final float PROGRESS_STROKE_SCALE = ENABLE_DOWNLOAD_APP_UX_V2.get()
- ? 0.055f
- : 0.075f;
+ private static final float SMALL_SCALE = 0.8f;
+ private static final float PROGRESS_STROKE_SCALE = 0.055f;
private static final float PROGRESS_BOUNDS_SCALE = 0.075f;
private static final int PRELOAD_ACCENT_COLOR_INDEX = 0;
private static final int PRELOAD_BACKGROUND_COLOR_INDEX = 1;
@@ -119,8 +115,6 @@
private ObjectAnimator mCurrentAnim;
- private boolean mIsStartable;
-
public PreloadIconDrawable(ItemInfoWithIcon info, Context context) {
this(
info,
@@ -144,9 +138,7 @@
mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
- if (ENABLE_DOWNLOAD_APP_UX_V3.get()) {
- mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
- }
+ mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
mIndicatorColor = indicatorColor;
// This is the color
@@ -181,9 +173,6 @@
mIconScaleMultiplier.updateValue(info.getProgressLevel() == 0 ? 0 : 1);
setLevel(info.getProgressLevel());
- if (!ENABLE_DOWNLOAD_APP_UX_V2.get()) {
- setIsStartable(info.isAppStartable());
- }
}
@Override
@@ -212,54 +201,31 @@
return;
}
- if (mInternalStateProgress > 0
- && (ENABLE_DOWNLOAD_APP_UX_V3.get() || !ENABLE_DOWNLOAD_APP_UX_V2.get())) {
+ if (mInternalStateProgress > 0) {
// Draw background.
- mProgressPaint.setStyle(ENABLE_DOWNLOAD_APP_UX_V3.get()
- ? Paint.Style.FILL
- : Paint.Style.FILL_AND_STROKE);
- mProgressPaint.setColor(ENABLE_DOWNLOAD_APP_UX_V3.get()
- ? mPlateColor
- : mSystemBackgroundColor);
+ mProgressPaint.setStyle(Paint.Style.FILL);
+ mProgressPaint.setColor(mPlateColor);
canvas.drawPath(mScaledTrackPath, mProgressPaint);
}
- if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || mInternalStateProgress > 0) {
+ if (mInternalStateProgress > 0) {
// Draw track and progress.
mProgressPaint.setStyle(Paint.Style.STROKE);
- mProgressPaint.setColor(ENABLE_DOWNLOAD_APP_UX_V3.get()
- ? mTrackColor
- : mSystemAccentColor);
- if (!ENABLE_DOWNLOAD_APP_UX_V3.get()) {
- mProgressPaint.setAlpha(TRACK_ALPHA);
- }
+ mProgressPaint.setColor(mTrackColor);
canvas.drawPath(mScaledTrackPath, mProgressPaint);
mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
- if (ENABLE_DOWNLOAD_APP_UX_V3.get()) {
- mProgressPaint.setColor(mProgressColor);
- }
+ mProgressPaint.setColor(mProgressColor);
canvas.drawPath(mScaledProgressPath, mProgressPaint);
}
int saveCount = canvas.save();
- float scale = ENABLE_DOWNLOAD_APP_UX_V2.get()
- ? 1 - mIconScaleMultiplier.value * (1 - SMALL_SCALE)
- : SMALL_SCALE;
+ float scale = 1 - mIconScaleMultiplier.value * (1 - SMALL_SCALE);
canvas.scale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY());
super.drawInternal(canvas, bounds);
canvas.restoreToCount(saveCount);
}
- @Override
- protected void updateFilter() {
- if (!ENABLE_DOWNLOAD_APP_UX_V2.get()) {
- setAlpha(mIsDisabled ? DISABLED_ICON_ALPHA : MAX_PAINT_ALPHA);
- } else {
- super.updateFilter();
- }
- }
-
/**
* Updates the install progress based on the level
*/
@@ -296,14 +262,6 @@
return !mRanFinishAnimation;
}
- /** Sets whether this icon should display the startable app UI. */
- public void setIsStartable(boolean isStartable) {
- if (mIsStartable != isStartable) {
- mIsStartable = isStartable;
- setIsDisabled(!isStartable);
- }
- }
-
private void updateInternalState(
float finalProgress, boolean isFinish, Runnable onFinishCallback) {
if (mCurrentAnim != null) {
@@ -355,7 +313,7 @@
*/
private void setInternalProgress(float progress) {
// Animate scale and alpha from pending to downloading state.
- if (ENABLE_DOWNLOAD_APP_UX_V2.get() && progress > 0 && mInternalStateProgress == 0) {
+ if (progress > 0 && mInternalStateProgress == 0) {
// Progress is changing for the first time, animate the icon scale
Animator iconScaleAnimator = mIconScaleMultiplier.animateToValue(1);
iconScaleAnimator.setDuration(SCALE_AND_ALPHA_ANIM_DURATION);
@@ -365,14 +323,11 @@
mInternalStateProgress = progress;
if (progress <= 0) {
- if (!ENABLE_DOWNLOAD_APP_UX_V2.get()) {
- mScaledTrackPath.reset();
- }
mIconScaleMultiplier.updateValue(0);
} else {
mPathMeasure.getSegment(
0, Math.min(progress, 1) * mTrackLength, mScaledProgressPath, true);
- if (progress > 1 && ENABLE_DOWNLOAD_APP_UX_V2.get()) {
+ if (progress > 1) {
// map the scale back to original value
mIconScaleMultiplier.updateValue(Utilities.mapBoundToRange(
progress - 1, 0, COMPLETE_ANIM_FRACTION, 1, 0, EMPHASIZED));
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 1917ba2..37a7171 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -44,7 +44,6 @@
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -68,7 +67,8 @@
*/
public class PackageUpdatedTask extends BaseModelUpdateTask {
- private static boolean DEBUG = false;
+ // TODO(b/290090023): Set to false after root causing is done.
+ private static final boolean DEBUG = true;
private static final String TAG = "PackageUpdatedTask";
public static final int OP_NONE = 0;
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index c356da9..d434ad2 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -27,6 +27,7 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
@@ -267,13 +268,19 @@
@Override
public float getTaskMenuX(float x, View thumbnailView,
- DeviceProfile deviceProfile, float taskInsetMargin) {
+ DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
+ if (enableOverviewIconMenu()) {
+ return x - (taskInsetMargin / 2f);
+ }
return thumbnailView.getMeasuredWidth() + x - taskInsetMargin;
}
@Override
public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin) {
+ View taskMenuView, float taskInsetMargin, View taskViewIcon) {
+ if (enableOverviewIconMenu()) {
+ return y - taskMenuView.getMeasuredHeight() - taskInsetMargin;
+ }
BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskMenuView.getLayoutParams();
int taskMenuWidth = lp.width;
if (stagePosition == STAGE_POSITION_UNDEFINED) {
@@ -537,14 +544,25 @@
}
@Override
+ public void setTaskIconMenuParams(FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin,
+ int thumbnailTopMargin) {
+ iconMenuParams.gravity = END | TOP;
+ iconMenuParams.setMarginStart(0);
+ iconMenuParams.topMargin = iconMenuParams.width + iconMenuMargin;
+ iconMenuParams.bottomMargin = 0;
+ iconMenuParams.setMarginEnd(iconMenuMargin);
+ }
+
+ @Override
public void setSplitIconParams(View primaryIconView, View secondaryIconView,
int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
DeviceProfile deviceProfile, SplitBounds splitConfig) {
FrameLayout.LayoutParams primaryIconParams =
(FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
- FrameLayout.LayoutParams secondaryIconParams =
- new FrameLayout.LayoutParams(primaryIconParams);
+ FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
+ ? (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams()
+ : new FrameLayout.LayoutParams(primaryIconParams);
// We calculate the "midpoint" of the thumbnail area, and place the icons there.
// This is the place where the thumbnail area splits by default, in a near-50/50 split.
@@ -560,11 +578,17 @@
int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness * midpointFromBottomPct);
int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
- primaryIconParams.gravity = BOTTOM | (isRtl ? START : END);
- secondaryIconParams.gravity = BOTTOM | (isRtl ? START : END);
+ primaryIconParams.gravity = enableOverviewIconMenu() ? TOP | (isRtl ? START : END)
+ : BOTTOM | (isRtl ? START : END);
+ secondaryIconParams.gravity = enableOverviewIconMenu() ? TOP | (isRtl ? START : END)
+ : BOTTOM | (isRtl ? START : END);
primaryIconView.setTranslationX(0);
secondaryIconView.setTranslationX(0);
- if (splitConfig.initiatedFromSeascape) {
+ if (enableOverviewIconMenu()) {
+ int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
+ splitConfig.visualDividerBounds.height());
+ secondaryIconView.setTranslationY(primarySnapshotHeight + dividerThickness);
+ } else if (splitConfig.initiatedFromSeascape) {
// if the split was initiated from seascape,
// the task on the right (secondary) is slightly larger
primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 39ef129..0069d9b 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -173,6 +173,8 @@
// Overview TaskMenuView methods
void setTaskIconParams(FrameLayout.LayoutParams iconParams,
int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl);
+ void setTaskIconMenuParams(FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin,
+ int thumbnailTopMargin);
void setSplitIconParams(View primaryIconView, View secondaryIconView,
int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
@@ -185,9 +187,9 @@
* getTaskMenuWidth()), so we directly use that in the calculations.
*/
float getTaskMenuX(float x, View thumbnailView, DeviceProfile deviceProfile,
- float taskInsetMargin);
+ float taskInsetMargin, View taskViewIcon);
float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin);
+ View taskMenuView, float taskInsetMargin, View taskViewIcon);
int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
@StagePosition int stagePosition);
/**
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index dc4621e..b3189b7 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -21,11 +21,13 @@
import static android.view.Gravity.END;
import static android.view.Gravity.START;
import static android.view.Gravity.TOP;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
@@ -264,8 +266,11 @@
@Override
public float getTaskMenuX(float x, View thumbnailView,
- DeviceProfile deviceProfile, float taskInsetMargin) {
- if (deviceProfile.isLandscape) {
+ DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
+ if (enableOverviewIconMenu()) {
+ return x + (thumbnailView.getLayoutDirection() == LAYOUT_DIRECTION_RTL
+ ? -(taskViewIcon.getWidth() / 2f) : 0);
+ } else if (deviceProfile.isLandscape) {
return x + taskInsetMargin
+ (thumbnailView.getMeasuredWidth() - thumbnailView.getMeasuredHeight()) / 2f;
} else {
@@ -275,13 +280,22 @@
@Override
public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin) {
+ View taskMenuView, float taskInsetMargin, View taskViewIcon) {
+ if (enableOverviewIconMenu()) {
+ return y;
+ }
return y + taskInsetMargin;
}
@Override
public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
@StagePosition int stagePosition) {
+ if (enableOverviewIconMenu()) {
+ int padding = thumbnailView.getResources().getDimensionPixelSize(
+ R.dimen.task_menu_vertical_padding);
+ return thumbnailView.getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_max_width) + (2 * padding);
+ }
return deviceProfile.isLandscape && !deviceProfile.isTablet
? thumbnailView.getMeasuredHeight()
: thumbnailView.getMeasuredWidth();
@@ -689,16 +703,53 @@
}
@Override
+ public void setTaskIconMenuParams(FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin,
+ int thumbnailTopMargin) {
+ iconMenuParams.gravity = TOP | START;
+ iconMenuParams.setMarginStart(iconMenuMargin);
+ iconMenuParams.topMargin = iconMenuMargin;
+ iconMenuParams.bottomMargin = 0;
+ iconMenuParams.setMarginEnd(0);
+ }
+
+ @Override
public void setSplitIconParams(View primaryIconView, View secondaryIconView,
int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
DeviceProfile deviceProfile, SplitBounds splitConfig) {
FrameLayout.LayoutParams primaryIconParams =
(FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
- FrameLayout.LayoutParams secondaryIconParams =
- new FrameLayout.LayoutParams(primaryIconParams);
+ FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
+ ? (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams()
+ : new FrameLayout.LayoutParams(primaryIconParams);
- if (deviceProfile.isLandscape) {
+ if (enableOverviewIconMenu()) {
+ primaryIconParams.gravity = TOP | START;
+ secondaryIconParams.gravity = TOP | START;
+ secondaryIconParams.topMargin = primaryIconParams.topMargin;
+ secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart());
+ if (deviceProfile.isLandscape) {
+ int fullscreenInsetThickness = deviceProfile.isSeascape()
+ ? deviceProfile.getInsets().right
+ : deviceProfile.getInsets().left;
+ int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
+ - fullscreenInsetThickness) / 2);
+ float midpointFromEndPct = (float) fullscreenMidpointFromBottom
+ / deviceProfile.widthPx;
+ int bottomToMidpointOffset = (int) (groupedTaskViewWidth * midpointFromEndPct);
+ if (isRtl) {
+ primaryIconView.setTranslationX(-bottomToMidpointOffset);
+ } else {
+ secondaryIconView.setTranslationX(bottomToMidpointOffset);
+ }
+ } else {
+ secondaryIconView.setTranslationX(0);
+ int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
+ splitConfig.visualDividerBounds.height());
+ secondaryIconView.setTranslationY(
+ primarySnapshotHeight + (deviceProfile.isTablet ? 0 : dividerThickness));
+ }
+ } else if (deviceProfile.isLandscape) {
// We calculate the "midpoint" of the thumbnail area, and place the icons there.
// This is the place where the thumbnail area splits by default, in a near-50/50 split.
// It is usually not exactly 50/50, due to insets/screen cutouts.
@@ -754,8 +805,10 @@
secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
secondaryIconView.setTranslationX(taskIconHeight / 2f);
}
- primaryIconView.setTranslationY(0);
- secondaryIconView.setTranslationY(0);
+ if (!enableOverviewIconMenu()) {
+ primaryIconView.setTranslationY(0);
+ secondaryIconView.setTranslationY(0);
+ }
primaryIconView.setLayoutParams(primaryIconParams);
secondaryIconView.setLayoutParams(secondaryIconParams);
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index ec01231..dac7964 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -22,6 +22,7 @@
import static android.view.Gravity.RIGHT;
import static android.view.Gravity.START;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
@@ -86,13 +87,19 @@
@Override
public float getTaskMenuX(float x, View thumbnailView,
- DeviceProfile deviceProfile, float taskInsetMargin) {
+ DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
+ if (enableOverviewIconMenu()) {
+ return x + taskViewIcon.getHeight() + taskInsetMargin * 2;
+ }
return x + taskInsetMargin;
}
@Override
public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin) {
+ View taskMenuView, float taskInsetMargin, View taskViewIcon) {
+ if (enableOverviewIconMenu()) {
+ return y + taskViewIcon.getWidth() - taskViewIcon.getHeight();
+ }
BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskMenuView.getLayoutParams();
int taskMenuWidth = lp.width;
if (stagePosition == STAGE_POSITION_UNDEFINED) {
@@ -208,13 +215,24 @@
public void setTaskIconParams(FrameLayout.LayoutParams iconParams,
int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
- iconParams.leftMargin = -taskIconHeight - taskIconMargin / 2;
+ iconParams.leftMargin =
+ enableOverviewIconMenu() ? 0 : -taskIconHeight - taskIconMargin / 2;
iconParams.rightMargin = 0;
- iconParams.topMargin = thumbnailTopMargin / 2;
+ iconParams.topMargin = enableOverviewIconMenu() ? 0 : thumbnailTopMargin / 2;
iconParams.bottomMargin = 0;
}
@Override
+ public void setTaskIconMenuParams(FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin,
+ int thumbnailTopMargin) {
+ iconMenuParams.gravity = BOTTOM | START;
+ iconMenuParams.setMarginStart(0);
+ iconMenuParams.topMargin = 0;
+ iconMenuParams.bottomMargin = 0;
+ iconMenuParams.setMarginEnd(0);
+ }
+
+ @Override
public void setSplitIconParams(View primaryIconView, View secondaryIconView,
int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
@@ -245,7 +263,11 @@
secondaryIconParams.gravity = BOTTOM | (isRtl ? END : START);
primaryIconView.setTranslationX(0);
secondaryIconView.setTranslationX(0);
- if (splitConfig.initiatedFromSeascape) {
+ if (enableOverviewIconMenu()) {
+ int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
+ splitConfig.visualDividerBounds.height());
+ secondaryIconView.setTranslationY(-primarySnapshotHeight - dividerThickness);
+ } else if (splitConfig.initiatedFromSeascape) {
// if the split was initiated from seascape,
// the task on the right (secondary) is slightly larger
primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index a7c94bb..26ab5b4 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -20,6 +20,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TRANSIENT_TASKBAR;
@@ -32,6 +33,7 @@
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -82,9 +84,11 @@
public static final int CHANGE_DENSITY = 1 << 2;
public static final int CHANGE_SUPPORTED_BOUNDS = 1 << 3;
public static final int CHANGE_NAVIGATION_MODE = 1 << 4;
+ public static final int CHANGE_TASKBAR_PINNING = 1 << 5;
public static final int CHANGE_ALL = CHANGE_ACTIVE_SCREEN | CHANGE_ROTATION
- | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE;
+ | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE
+ | CHANGE_TASKBAR_PINNING;
private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
private static final String TARGET_OVERLAY_PACKAGE = "android";
@@ -104,13 +108,17 @@
private Info mInfo;
private boolean mDestroyed = false;
- private final LauncherPrefs mPrefs;
+ private SharedPreferences.OnSharedPreferenceChangeListener
+ mTaskbarPinningPreferenceChangeListener;
@VisibleForTesting
protected DisplayController(Context context) {
mContext = context;
mDM = context.getSystemService(DisplayManager.class);
- mPrefs = LauncherPrefs.get(context);
+
+ if (ENABLE_TASKBAR_PINNING.get()) {
+ attachTaskbarPinningSharedPreferenceChangeListener(mContext);
+ }
Display display = mDM.getDisplay(DEFAULT_DISPLAY);
if (Utilities.ATLEAST_S) {
@@ -131,6 +139,21 @@
FileLog.i(TAG, "(CTOR) perDisplayBounds: " + mInfo.mPerDisplayBounds);
}
+ private void attachTaskbarPinningSharedPreferenceChangeListener(Context context) {
+ mTaskbarPinningPreferenceChangeListener =
+ (sharedPreferences, key) -> {
+ if (TASKBAR_PINNING_KEY.equals(key)
+ && mInfo.mIsTaskbarPinned != LauncherPrefs.get(mContext).get(
+ TASKBAR_PINNING)
+ ) {
+ handleInfoChange(mWindowContext.getDisplay());
+ }
+ };
+
+ LauncherPrefs.get(context).addListener(
+ mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
+ }
+
/**
* Returns the current navigation mode
*/
@@ -142,25 +165,7 @@
* Returns whether taskbar is transient.
*/
public static boolean isTransientTaskbar(Context context) {
- return INSTANCE.get(context).isTransientTaskbar();
- }
-
- /**
- * Returns whether taskbar is transient.
- */
- public boolean isTransientTaskbar() {
- // TODO(b/258604917): When running in test harness, use !sTransientTaskbarStatusForTests
- // once tests are updated to expect new persistent behavior such as not allowing long press
- // to stash.
- if (!Utilities.isRunningInTestHarness()
- && ENABLE_TASKBAR_PINNING.get()
- && mPrefs.get(TASKBAR_PINNING)) {
- return false;
- }
- return getInfo().navigationMode == NavigationMode.NO_BUTTON
- && (Utilities.isRunningInTestHarness()
- ? sTransientTaskbarStatusForTests
- : ENABLE_TRANSIENT_TASKBAR.get());
+ return INSTANCE.get(context).getInfo().isTransientTaskbar();
}
/**
@@ -174,6 +179,10 @@
@Override
public void close() {
mDestroyed = true;
+ if (ENABLE_TASKBAR_PINNING.get()) {
+ LauncherPrefs.get(mContext).removeListener(
+ mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
+ }
if (mWindowContext != null) {
mWindowContext.unregisterComponentCallbacks(this);
} else {
@@ -256,7 +265,8 @@
}
@AnyThread
- private void handleInfoChange(Display display) {
+ @VisibleForTesting
+ public void handleInfoChange(Display display) {
WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext);
Info oldInfo = mInfo;
@@ -289,6 +299,9 @@
FileLog.w(TAG,
"(CHANGE_SUPPORTED_BOUNDS) perDisplayBounds: " + newInfo.mPerDisplayBounds);
}
+ if (newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned) {
+ change |= CHANGE_TASKBAR_PINNING;
+ }
if (DEBUG) {
Log.d(TAG, "handleInfoChange - change: " + getChangeFlagsString(change));
}
@@ -331,6 +344,8 @@
private final ArrayMap<CachedDisplayInfo, List<WindowBounds>> mPerDisplayBounds =
new ArrayMap<>();
+ private final boolean mIsTaskbarPinned;
+
public Info(Context displayInfoContext) {
/* don't need system overrides for external displays */
this(displayInfoContext, new WindowManagerProxy(), new ArrayMap<>());
@@ -387,6 +402,26 @@
Log.d(TAG, "normalizedDisplayInfo: " + normalizedDisplayInfo);
Log.d(TAG, "perDisplayBounds: " + mPerDisplayBounds);
}
+
+ mIsTaskbarPinned = LauncherPrefs.get(displayInfoContext).get(TASKBAR_PINNING);
+ }
+
+ /**
+ * Returns whether taskbar is transient.
+ */
+ public boolean isTransientTaskbar() {
+ // TODO(b/258604917): Once ENABLE_TASKBAR_PINNING is enabled, remove usage of
+ // sTransientTaskbarStatusForTests and update test to directly
+ // toggle shred preference to switch transient taskbar on/of
+ if (!Utilities.isRunningInTestHarness()
+ && ENABLE_TASKBAR_PINNING.get()
+ && mIsTaskbarPinned) {
+ return false;
+ }
+ return navigationMode == NavigationMode.NO_BUTTON
+ && (Utilities.isRunningInTestHarness()
+ ? sTransientTaskbarStatusForTests
+ : ENABLE_TRANSIENT_TASKBAR.get() && !mIsTaskbarPinned);
}
/**
@@ -426,6 +461,7 @@
appendFlag(result, change, CHANGE_DENSITY, "CHANGE_DENSITY");
appendFlag(result, change, CHANGE_SUPPORTED_BOUNDS, "CHANGE_SUPPORTED_BOUNDS");
appendFlag(result, change, CHANGE_NAVIGATION_MODE, "CHANGE_NAVIGATION_MODE");
+ appendFlag(result, change, CHANGE_TASKBAR_PINNING, "CHANGE_TASKBAR_VARIANT");
return result.toString();
}
@@ -440,6 +476,7 @@
pw.println(" fontScale=" + info.fontScale);
pw.println(" densityDpi=" + info.densityDpi);
pw.println(" navigationMode=" + info.navigationMode.name());
+ pw.println(" isTaskbarPinned=" + info.mIsTaskbarPinned);
pw.println(" currentSize=" + info.currentSize);
info.mPerDisplayBounds.forEach((key, value) -> pw.println(
" perDisplayBounds - " + key + ": " + value));
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index dec4b5c..07000ed 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.util;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
@@ -62,7 +64,9 @@
/** A background executor to preinflate views. */
public static final ExecutorService VIEW_PREINFLATION_EXECUTOR =
- java.util.concurrent.Executors.newSingleThreadExecutor();
+ java.util.concurrent.Executors.newSingleThreadExecutor(
+ new SimpleThreadFactory(
+ "preinflate-allapps-icons", THREAD_PRIORITY_BACKGROUND));
/**
* Utility method to get a started handler thread statically
diff --git a/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java b/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java
new file mode 100644
index 0000000..3ec339d
--- /dev/null
+++ b/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 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.util;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
+
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+import android.view.Menu;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
+import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.views.OptionsPopupView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Delegate to define the keyboard shortcuts.
+ */
+public class KeyboardShortcutsDelegate {
+
+ Launcher mLauncher;
+
+ public KeyboardShortcutsDelegate(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ /**
+ * Populates the list of shortcuts.
+ */
+ public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+ ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
+ if (mLauncher.isInState(NORMAL)) {
+ shortcutInfos.add(
+ new KeyboardShortcutInfo(mLauncher.getString(R.string.all_apps_button_label),
+ KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
+ shortcutInfos.add(
+ new KeyboardShortcutInfo(mLauncher.getString(R.string.widget_button_text),
+ KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON));
+ }
+ getSupportedActions(mLauncher, mLauncher.getCurrentFocus()).forEach(la ->
+ shortcutInfos.add(new KeyboardShortcutInfo(
+ la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON)));
+ if (!shortcutInfos.isEmpty()) {
+ data.add(new KeyboardShortcutGroup(mLauncher.getString(R.string.home_screen),
+ shortcutInfos));
+ }
+ }
+
+ /**
+ * Handles combinations of keys like ctrl+s or ctrl+c and runs before onKeyDown.
+ * @param keyCode code of the key being pressed.
+ * @see android.view.KeyEvent
+ * @return weather the event is already handled and if it should be passed to other components.
+ */
+ public Boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_A:
+ if (mLauncher.isInState(NORMAL)) {
+ mLauncher.getStateManager().goToState(ALL_APPS);
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_W:
+ if (mLauncher.isInState(NORMAL)) {
+ OptionsPopupView.openWidgets(mLauncher);
+ return true;
+ }
+ break;
+ default:
+ for (BaseAccessibilityDelegate.LauncherAction la : getSupportedActions(
+ mLauncher, mLauncher.getCurrentFocus())) {
+ if (la.keyCode == keyCode) {
+ return la.invokeFromKeyboard(mLauncher.getCurrentFocus());
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Handle key down event.
+ * @param keyCode code of the key being pressed.
+ * @see android.view.KeyEvent
+ */
+ public Boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
+ // Close any open floating views.
+ mLauncher.closeOpenViews();
+ return true;
+ }
+ return null;
+ }
+
+ /**
+ * Handle key up event.
+ * @param keyCode code of the key being pressed.
+ * @see android.view.KeyEvent
+ */
+ public Boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_MENU) {
+ // KEYCODE_MENU is sent by some tests, for example
+ // LauncherJankTests#testWidgetsContainerFling. Don't just remove its handling.
+ if (!mLauncher.getDragController().isDragging()
+ && !mLauncher.getWorkspace().isSwitchingState()
+ && mLauncher.isInState(NORMAL)) {
+ // Close any open floating views.
+ mLauncher.closeOpenViews();
+
+ // Setting the touch point to (-1, -1) will show the options popup in the center of
+ // the screen.
+ if (Utilities.isRunningInTestHarness()) {
+ Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up");
+ }
+ mLauncher.showDefaultOptions(-1, -1);
+ }
+ return true;
+ }
+ return null;
+ }
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 1471c08..62d232c 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -43,11 +43,13 @@
name: "launcher-oop-tests-src",
srcs: [
"src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java",
+ "src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java",
"src/com/android/launcher3/dragging/TaplDragTest.java",
"src/com/android/launcher3/dragging/TaplUninstallRemove.java",
"src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
"src/com/android/launcher3/ui/PortraitLandscapeRunner.java",
"src/com/android/launcher3/ui/TaplTestsLauncher3.java",
+ "src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java",
"src/com/android/launcher3/util/LauncherLayoutBuilder.java",
"src/com/android/launcher3/util/TestUtil.java",
"src/com/android/launcher3/util/Wait.java",
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
index b8f4c0b..ec32680 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
index a512277..d69be3f 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
index a3a8dc5..7e92620 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
index 55066cb..a9bee2b 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
index 6e764c2..42b022b 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
index 7650082..53f8580 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
index 2b241a1..87189fa 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
index 6d38d27..0ade560 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
index 5799de7..d24457d 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
index b4956ff..38dc2c9 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
index 15afb61..5d23147 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
index 6cbed1f..5b53509 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
@@ -108,6 +108,7 @@
overviewTaskIconSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+ overviewTaskIconAppChipMenuDrawableSizePx: 0.0px (0.0dp)
overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
overviewActionsTopMarginPx: 0.0px (0.0dp)
overviewActionsHeight: 0.0px (0.0dp)
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 0798e97..54a1c08 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -126,6 +126,7 @@
"taskbar-all-apps-top-padding";
public static final String REQUEST_ALL_APPS_TOP_PADDING = "all-apps-top-padding";
public static final String REQUEST_ALL_APPS_BOTTOM_PADDING = "all-apps-bottom-padding";
+ public static final String REQUEST_REFRESH_OVERVIEW_TARGET = "refresh-overview-target";
public static final String REQUEST_WORKSPACE_CELL_LAYOUT_SIZE = "workspace-cell-layout-size";
public static final String REQUEST_WORKSPACE_CELL_CENTER = "workspace-cell-center";
@@ -155,7 +156,6 @@
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
- public static final String FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP = "b/286084688";
public static final String ICON_MISSING = "b/282963545";
public static final String INCORRECT_HOME_STATE = "b/293191790";
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index ed8e324..a52ba9e 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -40,6 +40,7 @@
import org.junit.Rule
import org.mockito.ArgumentMatchers
import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
import org.mockito.Mockito.`when` as whenever
/**
@@ -306,9 +307,9 @@
}
context = runningContext.createConfigurationContext(config)
- val info = DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache)
+ val info = spy(DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache))
whenever(displayController.info).thenReturn(info)
- whenever(displayController.isTransientTaskbar).thenReturn(isGestureMode)
+ whenever(info.isTransientTaskbar).thenReturn(isGestureMode)
}
/** Create a new dump of DeviceProfile, saves to a file in the device and returns it */
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index c22cf40..42338bf 100644
--- a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -47,6 +47,7 @@
protected var transposeLayoutWithOrientation: Boolean = false
protected var useTwoPanels: Boolean = false
protected var isGestureMode: Boolean = true
+ protected var isTransientTaskbar: Boolean = true
@Before
fun setUp() {
@@ -68,7 +69,8 @@
useTwoPanels,
isGestureMode,
DEFAULT_PROVIDER,
- DEFAULT_DIMENSION_PROVIDER
+ DEFAULT_DIMENSION_PROVIDER,
+ isTransientTaskbar,
)
protected fun initializeVarsForPhone(
@@ -93,6 +95,7 @@
whenever(info.smallestSizeDp(any())).thenReturn(411f)
this.isGestureMode = isGestureMode
+ this.isTransientTaskbar = false
transposeLayoutWithOrientation = true
inv =
@@ -175,6 +178,7 @@
whenever(info.smallestSizeDp(any())).thenReturn(800f)
this.isGestureMode = isGestureMode
+ this.isTransientTaskbar = true
useTwoPanels = false
inv =
@@ -258,6 +262,7 @@
whenever(info.smallestSizeDp(any())).thenReturn(700f)
this.isGestureMode = isGestureMode
+ this.isTransientTaskbar = true
useTwoPanels = true
inv =
diff --git a/tests/src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java b/tests/src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java
index 7d6b7f9..f9dadaa 100644
--- a/tests/src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java
+++ b/tests/src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java
@@ -17,11 +17,14 @@
import static com.android.launcher3.ui.TaplTestsLauncher3.expectFail;
import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
+import android.platform.test.annotations.PlatinumTest;
+
import com.android.launcher3.LauncherState;
import com.android.launcher3.tapl.AllApps;
import com.android.launcher3.ui.AbstractLauncherUiTest;
@@ -88,6 +91,7 @@
/**
* Make sure the swipe up gesture can take us back to the workspace from AllApps
*/
+ @PlatinumTest(focusArea = "launcher")
@Test
@PortraitLandscape
public void testAllAppsSwipeUpToWorkspace() {
diff --git a/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
new file mode 100644
index 0000000..85cf52e
--- /dev/null
+++ b/tests/src/com/android/launcher3/appiconmenu/TaplAppIconMenuTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 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.appiconmenu;
+
+import static com.android.launcher3.ui.TaplTestsLauncher3.APP_NAME;
+import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.PlatinumTest;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.popup.ArrowPopup;
+import com.android.launcher3.tapl.AllApps;
+import com.android.launcher3.tapl.AppIconMenu;
+import com.android.launcher3.tapl.AppIconMenuItem;
+import com.android.launcher3.tapl.HomeAllApps;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * This test run in both Out of process (Oop) and in-process (Ipc).
+ * Tests the AppIconMenu (the menu that appears when you long press an app icon) and also make sure
+ * we can launch a shortcut from it.
+ */
+public class TaplAppIconMenuTest extends AbstractLauncherUiTest {
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ initialize(this);
+ }
+
+ private boolean isOptionsPopupVisible(Launcher launcher) {
+ final ArrowPopup<?> popup = launcher.getOptionsPopup();
+ return popup != null && popup.isShown();
+ }
+
+ /**
+ * Open All apps then open the AppIconMenu then launch a shortcut from the menu and make sure it
+ * launches.
+ */
+ @Test
+ @PortraitLandscape
+ @PlatinumTest(focusArea = "launcher")
+ public void testLaunchMenuItem() {
+ final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+ allApps.freeze();
+ try {
+ final AppIconMenu menu = allApps.getAppIcon(APP_NAME).openDeepShortcutMenu();
+
+ executeOnLauncher(
+ launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
+ isOptionsPopupVisible(launcher)));
+
+ final AppIconMenuItem menuItem = menu.getMenuItem(1);
+ assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
+ menuItem.launch(getAppPackageName());
+ } finally {
+ allApps.unfreeze();
+ }
+ }
+
+ /**
+ * Drag icon from AllApps to the workspace and then open the AppIconMenu and launch a shortcut
+ * from it.
+ */
+ @PlatinumTest(focusArea = "launcher")
+ @Test
+ public void testLaunchHomeScreenMenuItem() {
+ // Drag the test app icon to home screen and open short cut menu from the icon
+ final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+ allApps.freeze();
+ try {
+ allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
+ final AppIconMenu menu = mLauncher.getWorkspace().getWorkspaceAppIcon(
+ APP_NAME).openDeepShortcutMenu();
+
+ executeOnLauncher(
+ launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
+ isOptionsPopupVisible(launcher)));
+
+ final AppIconMenuItem menuItem = menu.getMenuItem(1);
+ assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
+ menuItem.launch(getAppPackageName());
+ } finally {
+ allApps.unfreeze();
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index bf7a21c..fb364ad 100644
--- a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -29,13 +29,13 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.ModelTestExtensions;
import java.util.ArrayList;
import java.util.function.Supplier;
@@ -60,8 +60,7 @@
public void commit() {
LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
// Load the model once so that there is no pending migration:
- loadModelSync(model);
-
+ ModelTestExtensions.INSTANCE.loadModelSync(model);
runOnExecutorSync(MODEL_EXECUTOR, () -> {
ModelDbController controller = model.getModelDbController();
// Migrate any previous data so that the DB state is correct
@@ -105,16 +104,7 @@
// Reload model
runOnExecutorSync(MAIN_EXECUTOR, model::forceReload);
- loadModelSync(model);
- }
-
- private void loadModelSync(LauncherModel model) {
- Callbacks mockCb = new Callbacks() { };
- runOnExecutorSync(MAIN_EXECUTOR, () -> model.addCallbacksAndLoad(mockCb));
- runOnExecutorSync(MODEL_EXECUTOR, () -> { });
-
- runOnExecutorSync(MAIN_EXECUTOR, () -> { });
- runOnExecutorSync(MAIN_EXECUTOR, () -> model.removeCallbacks(mockCb));
+ ModelTestExtensions.INSTANCE.loadModelSync(model);
}
/**
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
index 00d7ce6..3d388d60 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.celllayout;
+import static android.platform.uiautomator_helpers.DeviceHelpers.getContext;
+
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -28,13 +30,16 @@
import androidx.test.filters.SmallTest;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.MultipageCellLayout;
import com.android.launcher3.tapl.Widget;
import com.android.launcher3.tapl.WidgetResizeFrame;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.ModelTestExtensions;
import com.android.launcher3.util.rule.ShellCommandRule;
+import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
@@ -68,6 +73,13 @@
TaplTestsLauncher3.initialize(this);
}
+ @After
+ public void tearDown() {
+ ModelTestExtensions.INSTANCE.clearModelDb(
+ LauncherAppState.getInstance(getContext()).getModel()
+ );
+ }
+
/**
* Validate if the given board represent the current CellLayout
**/
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index f734fe5..e837b8b 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -16,9 +16,10 @@
package com.android.launcher3.ui;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
-import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -110,15 +111,22 @@
protected String mTargetPackage;
private int mLauncherPid;
+ /** Detects activity leaks and throws an exception if a leak is found. */
public static void checkDetectedLeaks(LauncherInstrumentation launcher) {
+ checkDetectedLeaks(launcher, false);
+ }
+
+ /** Detects activity leaks and throws an exception if a leak is found. */
+ public static void checkDetectedLeaks(LauncherInstrumentation launcher,
+ boolean requireOneActiveActivity) {
if (sActivityLeakReported) return;
// Check whether activity leak detector has found leaked activities.
- Wait.atMost(() -> getActivityLeakErrorMessage(launcher),
+ Wait.atMost(() -> getActivityLeakErrorMessage(launcher, requireOneActiveActivity),
() -> {
launcher.forceGc();
return MAIN_EXECUTOR.submit(
- () -> launcher.noLeakedActivities()).get();
+ () -> launcher.noLeakedActivities(requireOneActiveActivity)).get();
}, DEFAULT_UI_TIMEOUT, launcher);
}
@@ -126,13 +134,16 @@
return getInstrumentation().getContext().getPackageName();
}
- private static String getActivityLeakErrorMessage(LauncherInstrumentation launcher) {
+ private static String getActivityLeakErrorMessage(LauncherInstrumentation launcher,
+ boolean requireOneActiveActivity) {
sActivityLeakReported = true;
- return "Activity leak detector has found leaked activities, "
- + dumpHprofData(launcher, false) + ".";
+ return "Activity leak detector has found leaked activities, requirining 1 activity: "
+ + requireOneActiveActivity + "; "
+ + dumpHprofData(launcher, false, requireOneActiveActivity) + ".";
}
- public static String dumpHprofData(LauncherInstrumentation launcher, boolean intentionalLeak) {
+ private static String dumpHprofData(LauncherInstrumentation launcher, boolean intentionalLeak,
+ boolean requireOneActiveActivity) {
if (intentionalLeak) return "intentional leak; not generating dump";
String result;
@@ -151,7 +162,7 @@
"am dumpheap " + device.getLauncherPackageName() + " " + fileName);
}
Log.d(TAG, "Saved leak dump, the leak is still present: "
- + !launcher.noLeakedActivities());
+ + !launcher.noLeakedActivities(requireOneActiveActivity));
sDumpWasGenerated = true;
result = "saved memory dump as an artifact";
} catch (Throwable e) {
@@ -210,7 +221,7 @@
.outerRule(new PortraitLandscapeRunner(this))
.around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
.around(viewCaptureRule)
- .around(new TestIsolationRule(mLauncher));
+ .around(new TestIsolationRule(mLauncher, true));
return TestHelpers.isInLauncherProcess()
? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner)
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index bba8c89..2cdcf24 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -20,13 +20,13 @@
import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS;
import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TWOLINE_ALLAPPS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import android.content.Context;
import android.graphics.Typeface;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.ViewGroup;
import com.android.launcher3.BubbleTextView;
@@ -36,14 +36,11 @@
import com.android.launcher3.search.StringMatcherUtility;
import com.android.launcher3.util.ActivityContextWrapper;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.StaticMockitoRule;
import com.android.launcher3.views.BaseDragLayer;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
/**
@@ -55,7 +52,7 @@
*/
public class BubbleTextViewTest {
- @Rule public StaticMockitoRule mockitoRule = new StaticMockitoRule(Flags.class);
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final StringMatcherUtility.StringMatcher
MATCHER = StringMatcherUtility.StringMatcher.getInstance();
private static final int ONE_LINE = 1;
@@ -75,6 +72,10 @@
"LEGO®\nBuilder";
private static final String EMPTY_STRING = "";
private static final int CHAR_CNT = 7;
+ private static final int MAX_HEIGHT = Integer.MAX_VALUE;
+ private static final int LIMITED_HEIGHT = 357; /* allowedHeight in Pixel6 */
+ private static final float SPACE_MULTIPLIER = 1;
+ private static final float SPACE_EXTRA = 0;
private BubbleTextView mBubbleTextView;
private ItemInfoWithIcon mItemInfoWithIcon;
@@ -84,7 +85,9 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- Mockito.when(Flags.enableTwolineAllapps()).thenReturn(false);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_CURSOR_HOVER_STATES);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU);
Utilities.enableRunningInTestHarnessForTests();
mContext = new ActivityContextWrapper(getApplicationContext());
mBubbleTextView = new BubbleTextView(mContext);
@@ -111,160 +114,150 @@
@Test
public void testEmptyString_flagOn() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- mItemInfoWithIcon.title = EMPTY_STRING;
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertNotEquals(TWO_LINE, mBubbleTextView.getMaxLines());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ mItemInfoWithIcon.title = EMPTY_STRING;
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertNotEquals(TWO_LINE, mBubbleTextView.getMaxLines());
}
@Test
public void testEmptyString_flagOff() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
- mItemInfoWithIcon.title = EMPTY_STRING;
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ mItemInfoWithIcon.title = EMPTY_STRING;
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testStringWithSpaceLongerThanCharLimit_flagOn() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "Battery Stats"
- mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "Battery Stats"
+ mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testStringWithSpaceLongerThanCharLimit_flagOff() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
- // test string: "Battery Stats"
- mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "Battery Stats"
+ mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringNoSpaceLongerThanCharLimit_flagOn() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "flutterappflorafy"
- mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "flutterappflorafy"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringNoSpaceLongerThanCharLimit_flagOff() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
- // test string: "flutterappflorafy"
- mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "flutterappflorafy"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringWithSpaceLongerThanCharLimit_flagOn() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "System UWB Field Test"
- mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "System UWB Field Test"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringWithSpaceLongerThanCharLimit_flagOff() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
- // test string: "System UWB Field Test"
- mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "System UWB Field Test"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringSymbolLongerThanCharLimit_flagOn() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "LEGO®Builder"
- mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "LEGO®Builder"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
@Test
public void testLongStringSymbolLongerThanCharLimit_flagOff() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
- // test string: "LEGO®Builder"
- mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "LEGO®Builder"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
}
@Test
@@ -273,8 +266,11 @@
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+ MAX_HEIGHT,
TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
- breakPoints);
+ breakPoints,
+ SPACE_MULTIPLIER,
+ SPACE_EXTRA);
assertEquals(TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
}
@@ -283,9 +279,11 @@
// test string: "flutterappflorafy"
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
- CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+ CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth, 0,
TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
- breakPoints);
+ breakPoints,
+ SPACE_MULTIPLIER,
+ SPACE_EXTRA);
assertEquals(TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, newString);
}
@@ -295,8 +293,11 @@
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+ MAX_HEIGHT,
TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
- breakPoints);
+ breakPoints,
+ SPACE_MULTIPLIER,
+ SPACE_EXTRA);
assertEquals(TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
}
@@ -305,25 +306,57 @@
// test string: "LEGO®Builder"
IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, MATCHER);
- CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+ CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(
+ mLimitedWidth,
+ MAX_HEIGHT,
TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
- breakPoints);
+ breakPoints,
+ SPACE_MULTIPLIER,
+ SPACE_EXTRA);
assertEquals(TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
}
@Test
- public void testEnsurePredictionRowIsOneLine() {
- try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
- // test string: "Battery Stats"
- mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
- mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
- mBubbleTextView.applyLabel(mItemInfoWithIcon);
- mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, 0);
- mBubbleTextView.onPreDraw();
- assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ public void testEnsurePredictionRowIsTwoLine() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "Battery Stats"
+ mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
+ }
+
+ @Test
+ public void modifyTitleToSupportMultiLine_whenLimitedHeight_shouldBeOneLine() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "LEGO®Builder"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
+ }
+
+ @Test
+ public void modifyTitleToSupportMultiLine_whenUnlimitedHeight_shouldBeTwoLine() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+ // test string: "LEGO®Builder"
+ mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+ mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+ mBubbleTextView.applyLabel(mItemInfoWithIcon);
+ mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+
+ mBubbleTextView.onPreDraw();
+
+ assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 8f2ac98..9aaca54 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -16,9 +16,6 @@
package com.android.launcher3.ui;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -36,23 +33,15 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.tapl.AllApps;
import com.android.launcher3.tapl.AppIcon;
-import com.android.launcher3.tapl.AppIconMenu;
-import com.android.launcher3.tapl.AppIconMenuItem;
import com.android.launcher3.tapl.HomeAllApps;
import com.android.launcher3.tapl.HomeAppIcon;
-import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.util.rule.TISBindRule;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
-import com.android.launcher3.widget.picker.WidgetsFullSheet;
-import com.android.launcher3.widget.picker.WidgetsRecyclerView;
import org.junit.After;
import org.junit.Before;
@@ -96,7 +85,7 @@
test.waitForResumed("Launcher internal state is still Background");
// Check that we switched to home.
test.mLauncher.getWorkspace();
- AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher);
+ AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher, true);
}
@After
@@ -125,10 +114,6 @@
return launcher.getWorkspace().getCurrentPage();
}
- private WidgetsRecyclerView getWidgetsView(Launcher launcher) {
- return WidgetsFullSheet.getWidgetsView(launcher);
- }
-
@Test
public void testDevicePressMenu() throws Exception {
mDevice.pressMenu();
@@ -211,91 +196,6 @@
runIconLaunchFromAllAppsTest(this, allApps);
}
- @Test
- @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/293191790
- @ScreenRecord
- @PortraitLandscape
- public void testWidgets() throws Exception {
- // Test opening widgets.
- executeOnLauncher(launcher ->
- assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null));
- Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
- assertNotNull("openAllWidgets() returned null", widgets);
- widgets = mLauncher.getAllWidgets();
- assertNotNull("getAllWidgets() returned null", widgets);
- executeOnLauncher(launcher ->
- assertTrue("Widgets is not shown", getWidgetsView(launcher).isShown()));
- executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening",
- 0, getWidgetsScroll(launcher)));
-
- // Test flinging widgets.
- widgets.flingForward();
- Integer flingForwardY = getFromLauncher(launcher -> getWidgetsScroll(launcher));
- executeOnLauncher(launcher -> assertTrue("Flinging forward didn't scroll widgets",
- flingForwardY > 0));
-
- widgets.flingBackward();
- executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets",
- getWidgetsScroll(launcher) < flingForwardY));
-
- mLauncher.goHome();
- waitForLauncherCondition("Widgets were not closed",
- launcher -> getWidgetsView(launcher) == null);
- }
-
- private int getWidgetsScroll(Launcher launcher) {
- return getWidgetsView(launcher).computeVerticalScrollOffset();
- }
-
- private boolean isOptionsPopupVisible(Launcher launcher) {
- final ArrowPopup<?> popup = launcher.getOptionsPopup();
- return popup != null && popup.isShown();
- }
-
- @Test
- @PortraitLandscape
- @PlatinumTest(focusArea = "launcher")
- public void testLaunchMenuItem() throws Exception {
- final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
- allApps.freeze();
- try {
- final AppIconMenu menu = allApps.
- getAppIcon(APP_NAME).
- openDeepShortcutMenu();
-
- executeOnLauncher(
- launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
- isOptionsPopupVisible(launcher)));
-
- final AppIconMenuItem menuItem = menu.getMenuItem(1);
- assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
- menuItem.launch(getAppPackageName());
- } finally {
- allApps.unfreeze();
- }
- }
-
- @Test
- public void testLaunchHomeScreenMenuItem() {
- // Drag the test app icon to home screen and open short cut menu from the icon
- final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
- allApps.freeze();
- try {
- allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
- final AppIconMenu menu = mLauncher.getWorkspace().getWorkspaceAppIcon(
- APP_NAME).openDeepShortcutMenu();
-
- executeOnLauncher(
- launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
- isOptionsPopupVisible(launcher)));
-
- final AppIconMenuItem menuItem = menu.getMenuItem(1);
- assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
- menuItem.launch(getAppPackageName());
- } finally {
- allApps.unfreeze();
- }
- }
@FlakyTest(bugId = 256615483)
@Test
@PortraitLandscape
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 64ab206..fd4b7f1 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.ui.widget;
-import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static org.junit.Assert.assertNotNull;
import android.platform.test.annotations.PlatinumTest;
@@ -99,6 +98,7 @@
/**
* Test dragging a widget to the workspace and resize it.
*/
+ @PlatinumTest(focusArea = "launcher")
@Test
public void testResizeWidget() throws Throwable {
new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java b/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
new file mode 100644
index 0000000..a5e9868
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 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.ui.widget;
+
+import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.tapl.Widgets;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
+import com.android.launcher3.util.rule.TestStabilityRule.Stability;
+import com.android.launcher3.widget.picker.WidgetsFullSheet;
+import com.android.launcher3.widget.picker.WidgetsRecyclerView;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * This test run in both Out of process (Oop) and in-process (Ipc).
+ * Make sure the basic interactions with the WidgetPicker works.
+ */
+public class TaplWidgetPickerTest extends AbstractLauncherUiTest {
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ initialize(this);
+ }
+
+ private WidgetsRecyclerView getWidgetsView(Launcher launcher) {
+ return WidgetsFullSheet.getWidgetsView(launcher);
+ }
+
+ private int getWidgetsScroll(Launcher launcher) {
+ return getWidgetsView(launcher).computeVerticalScrollOffset();
+ }
+
+ /**
+ * Open Widget picker, make sure the widget picker can scroll and then go to home screen.
+ */
+ @Test
+ @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/293191790
+ @ScreenRecord
+ @PortraitLandscape
+ public void testWidgets() {
+ // Test opening widgets.
+ executeOnLauncher(launcher ->
+ assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null));
+ Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
+ assertNotNull("openAllWidgets() returned null", widgets);
+ widgets = mLauncher.getAllWidgets();
+ assertNotNull("getAllWidgets() returned null", widgets);
+ executeOnLauncher(launcher ->
+ assertTrue("Widgets is not shown", getWidgetsView(launcher).isShown()));
+ executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening",
+ 0, getWidgetsScroll(launcher)));
+
+ // Test flinging widgets.
+ widgets.flingForward();
+ Integer flingForwardY = getFromLauncher(launcher -> getWidgetsScroll(launcher));
+ executeOnLauncher(launcher -> assertTrue("Flinging forward didn't scroll widgets",
+ flingForwardY > 0));
+
+ widgets.flingBackward();
+ executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets",
+ getWidgetsScroll(launcher) < flingForwardY));
+
+ mLauncher.goHome();
+ waitForLauncherCondition("Widgets were not closed",
+ launcher -> getWidgetsView(launcher) == null);
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
index 8e4e998..a94dd2e 100644
--- a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -30,8 +30,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
+import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
import com.android.launcher3.util.window.CachedDisplayInfo
@@ -89,6 +91,7 @@
MockitoAnnotations.initMocks(this)
whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
+ whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
// Mock WindowManagerProxy
val displayInfo =
@@ -107,6 +110,7 @@
bounds[i.getArgument<CachedDisplayInfo>(1).rotation]
}
+ whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON)
// Mock context
whenever(context.createWindowContext(any(), any(), nullable())).thenReturn(context)
whenever(context.getSystemService(eq(DisplayManager::class.java)))
@@ -156,4 +160,13 @@
verify(displayInfoChangeListener).onDisplayInfoChanged(any(), any(), eq(CHANGE_DENSITY))
}
+
+ @Test
+ @UiThreadTest
+ fun testTaskbarPinning() {
+ whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(true)
+ displayController.handleInfoChange(display)
+ verify(displayInfoChangeListener)
+ .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+ }
}
diff --git a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
new file mode 100644
index 0000000..61ec669
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -0,0 +1,30 @@
+package com.android.launcher3.util
+
+import com.android.launcher3.LauncherModel
+import com.android.launcher3.model.BgDataModel
+
+object ModelTestExtensions {
+ /** Clears and reloads Launcher db to cleanup the workspace */
+ fun LauncherModel.clearModelDb() {
+ // Load the model once so that there is no pending migration:
+ loadModelSync()
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ modelDbController.run {
+ tryMigrateDB()
+ createEmptyDB()
+ clearEmptyDbFlag()
+ }
+ }
+ // Reload model
+ TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { forceReload() }
+ loadModelSync()
+ }
+
+ fun LauncherModel.loadModelSync() {
+ val mockCb: BgDataModel.Callbacks = object : BgDataModel.Callbacks {}
+ TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { addCallbacksAndLoad(mockCb) }
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {}
+ TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
+ TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { removeCallbacks(mockCb) }
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java b/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java
index 592cc9b..2b45902 100644
--- a/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java
@@ -16,6 +16,8 @@
package com.android.launcher3.util.rule;
import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.ui.AbstractLauncherUiTest;
@@ -28,10 +30,12 @@
* Isolates tests from some of the state created by the previous test.
*/
public class TestIsolationRule implements TestRule {
- final LauncherInstrumentation mLauncher;
+ private final LauncherInstrumentation mLauncher;
+ private final boolean mRequireOneActiveActivity;
- public TestIsolationRule(LauncherInstrumentation launcher) {
+ public TestIsolationRule(LauncherInstrumentation launcher, boolean requireOneActiveActivity) {
mLauncher = launcher;
+ mRequireOneActiveActivity = requireOneActiveActivity;
}
@NonNull
@@ -42,8 +46,9 @@
public void evaluate() throws Throwable {
base.evaluate();
// Make sure that Launcher workspace looks correct.
- mLauncher.goHome();
- AbstractLauncherUiTest.checkDetectedLeaks(mLauncher);
+
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).pressHome();
+ AbstractLauncherUiTest.checkDetectedLeaks(mLauncher, mRequireOneActiveActivity);
}
};
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 44875d5..dbb3cc3 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -16,6 +16,8 @@
package com.android.launcher3.tapl;
+import static android.view.KeyEvent.KEYCODE_META_RIGHT;
+
import static com.android.launcher3.tapl.LauncherInstrumentation.DEFAULT_POLL_INTERVAL;
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
@@ -42,7 +44,8 @@
/**
* Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview.
*/
-public abstract class AllApps extends LauncherInstrumentation.VisibleContainer {
+public abstract class AllApps extends LauncherInstrumentation.VisibleContainer
+ implements KeyboardQuickSwitchSource {
// Defer updates flag used to defer all apps updates by a test's request.
private static final int DEFER_UPDATES_TEST = 1 << 1;
@@ -65,6 +68,16 @@
.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ @Override
+ public LauncherInstrumentation getLauncher() {
+ return mLauncher;
+ }
+
+ @Override
+ public LauncherInstrumentation.ContainerType getStartingContainerType() {
+ return getContainerType();
+ }
+
private boolean hasClickableIcon(UiObject2 allAppsContainer, UiObject2 appListRecycler,
BySelector appIconSelector, int displayBottom) {
final UiObject2 icon;
@@ -359,6 +372,17 @@
}
}
+ /** Presses the meta keyboard shortcut to dismiss AllApps. */
+ public void dismissByKeyboardShortcut() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT);
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "pressed meta key")) {
+ verifyVisibleContainerOnDismiss();
+ }
+ }
+ }
+
protected abstract void verifyVisibleContainerOnDismiss();
/**
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 677f204..8713b68 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -39,7 +39,8 @@
* Indicates the base state with a UI other than Overview running as foreground. It can also
* indicate Launcher as long as Launcher is not in Overview state.
*/
-public abstract class Background extends LauncherInstrumentation.VisibleContainer {
+public abstract class Background extends LauncherInstrumentation.VisibleContainer
+ implements KeyboardQuickSwitchSource {
private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
private static final Pattern SQUARE_BUTTON_EVENT = Pattern.compile("onOverviewToggle");
@@ -47,6 +48,16 @@
super(launcher);
}
+ @Override
+ public LauncherInstrumentation getLauncher() {
+ return mLauncher;
+ }
+
+ @Override
+ public LauncherInstrumentation.ContainerType getStartingContainerType() {
+ return getContainerType();
+ }
+
/**
* Swipes up or presses the square button to switch to Overview.
* Returns the base overview, which can be either in Launcher or the fallback recents.
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index 252435b..85e28e8 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -62,4 +62,9 @@
protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() {
return true;
}
+
+ @Override
+ public boolean isHomeState() {
+ return true;
+ }
}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
index 8542f91..d9b179c 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -113,4 +113,9 @@
protected void verifyVisibleContainerOnDismiss() {
mLauncher.getWorkspace();
}
+
+ @Override
+ public boolean isHomeState() {
+ return true;
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
new file mode 100644
index 0000000..2a98a24
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2023 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.tapl;
+
+import static com.android.launcher3.tapl.LauncherInstrumentation.KEYBOARD_QUICK_SWITCH_RES_ID;
+
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.testing.shared.TestProtocol;
+
+import java.util.regex.Pattern;
+
+/**
+ * Operations on the Keyboard Quick Switch View
+ */
+public final class KeyboardQuickSwitch {
+
+ private static final Pattern EVENT_ALT_TAB_DOWN = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_TAB"
+ + ".*?metaState=META_ALT_ON");
+ private static final Pattern EVENT_ALT_TAB_UP = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB"
+ + ".*?metaState=META_ALT_ON");
+
+ private static final Pattern EVENT_ALT_SHIFT_TAB_DOWN = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_TAB"
+ + ".*?metaState=META_ALT_ON|META_SHIFT_ON");
+ private static final Pattern EVENT_ALT_SHIFT_TAB_UP = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_TAB"
+ + ".*?metaState=META_ALT_ON|META_SHIFT_ON");
+ private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_DOWN"
+ + ".*?keyCode=KEYCODE_ESCAPE.*?metaState=META_ALT_ON");
+ private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP"
+ + ".*?keyCode=KEYCODE_ESCAPE.*?metaState=META_ALT_ON");
+ private static final Pattern EVENT_ALT_LEFT_UP = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_UP"
+ + ".*?keyCode=KEYCODE_ALT_LEFT");
+
+ private final LauncherInstrumentation mLauncher;
+ private final LauncherInstrumentation.ContainerType mStartingContainerType;
+ private final boolean mExpectHomeKeyEventsOnDismiss;
+
+ KeyboardQuickSwitch(
+ LauncherInstrumentation launcher,
+ LauncherInstrumentation.ContainerType startingContainerType,
+ boolean expectHomeKeyEventsOnDismiss) {
+ mLauncher = launcher;
+ mStartingContainerType = startingContainerType;
+ mExpectHomeKeyEventsOnDismiss = expectHomeKeyEventsOnDismiss;
+ }
+
+ /**
+ * Focuses the next task in the Keyboard quick switch view.
+ * <p>
+ * Tasks are ordered left-to-right in LTR, and vice versa in RLT, in a carousel.
+ * <ul>
+ * <li>If no task has been focused yet, and there is only one task, then that task will be
+ * focused</li>
+ * <li>If no task has been focused yet, and there are two or more tasks, then the second
+ * task will be focused</li>
+ * <li>If the currently-focused task is at the end of the list, the first task will be
+ * focused</li>
+ * </ul>
+ */
+ public KeyboardQuickSwitch moveFocusForward() {
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "want to move keyboard quick switch focus forward")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_TAB_UP);
+ mLauncher.assertTrue("Failed to press alt+tab",
+ mLauncher.getDevice().pressKeyCode(
+ KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON));
+
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "pressed alt+tab")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ return this;
+ }
+ }
+ }
+ }
+
+ /**
+ * Focuses the next task in the Keyboard quick switch view.
+ * <p>
+ * Tasks are ordered left-to-right in LTR, and vice versa in RLT, in a carousel.
+ * <ul>
+ * <li>If no task has been focused yet, and there is only one task, then that task will be
+ * focused</li>
+ * <li>If no task has been focused yet, and there are two or more tasks, then the second
+ * task will be focused</li>
+ * <li>If the currently-focused task is at the start of the list, the last task will be
+ * focused</li>
+ * </ul>
+ */
+ public KeyboardQuickSwitch moveFocusBackward() {
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "want to move keyboard quick switch focus backward")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_SHIFT_TAB_UP);
+ mLauncher.assertTrue("Failed to press alt+shift+tab",
+ mLauncher.getDevice().pressKeyCode(
+ KeyEvent.KEYCODE_TAB,
+ KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON));
+
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "pressed alt+shift+tab")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ return this;
+ }
+ }
+ }
+ }
+
+ /**
+ * Dismisses the Keyboard Quick Switch view without launching the focused task.
+ * <p>
+ * The device will return to the same state it started in before displaying the Keyboard Quick
+ * Switch view.
+ */
+ public void dismiss() {
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "want to dismiss keyboard quick switch view")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
+ mLauncher.assertTrue("Failed to press alt+tab",
+ mLauncher.getDevice().pressKeyCode(
+ KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_ALT_ON));
+
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "pressed alt+esc")) {
+ mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
+ if (mExpectHomeKeyEventsOnDismiss) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_LEFT_UP);
+ }
+ mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
+
+ // Verify the final state is the same as the initial state
+ mLauncher.verifyContainerType(mStartingContainerType);
+ }
+ }
+ }
+ }
+
+ /**
+ * Launches the currently-focused app task.
+ * <p>
+ * This method should only be used if the focused task is for a recent running app, otherwise
+ * use {@link #launchFocusedOverviewTask()}.
+ *
+ * @param expectedPackageName the package name of the expected launched app
+ */
+ public LaunchedAppState launchFocusedAppTask(@NonNull String expectedPackageName) {
+ return (LaunchedAppState) launchFocusedTask(expectedPackageName);
+ }
+
+ /**
+ * Launches the currently-focused overview task.
+ * <p>
+ * This method only should be used if the focused task is for overview, otherwise use
+ * {@link #launchFocusedAppTask(String)}.
+ */
+ public Overview launchFocusedOverviewTask() {
+ return (Overview) launchFocusedTask(null);
+ }
+
+ private LauncherInstrumentation.VisibleContainer launchFocusedTask(
+ @Nullable String expectedPackageName) {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to launch focused task: "
+ + (expectedPackageName == null ? "Overview" : expectedPackageName))) {
+ mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
+ mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
+
+ if (expectedPackageName != null) {
+ mLauncher.assertAppLaunched(expectedPackageName);
+ return mLauncher.getLaunchedAppState();
+ } else {
+ return mLauncher.getOverview();
+ }
+ }
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java
new file mode 100644
index 0000000..b7e3d38
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 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.tapl;
+
+import static com.android.launcher3.tapl.LauncherInstrumentation.KEYBOARD_QUICK_SWITCH_RES_ID;
+
+import android.view.KeyEvent;
+
+/**
+ * {@link com.android.launcher3.tapl.LauncherInstrumentation.VisibleContainer} that can be used to
+ * show the keyboard quick switch view.
+ */
+interface KeyboardQuickSwitchSource {
+
+ /**
+ * Shows the Keyboard Quick Switch view.
+ */
+ default KeyboardQuickSwitch showQuickSwitchView() {
+ LauncherInstrumentation launcher = getLauncher();
+
+ try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer(
+ "want to show keyboard quick switch object")) {
+ launcher.pressAndHoldKeyCode(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_LEFT_ON);
+
+ try (LauncherInstrumentation.Closable c2 = launcher.addContextLayer(
+ "press and held alt+tab")) {
+ launcher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
+ launcher.unpressKeyCode(KeyEvent.KEYCODE_TAB, 0);
+
+ return new KeyboardQuickSwitch(
+ launcher, getStartingContainerType(), isHomeState());
+ }
+ }
+ }
+
+ /** This method requires public access, however should not be called in tests. */
+ LauncherInstrumentation getLauncher();
+
+ /** This method requires public access, however should not be called in tests. */
+ LauncherInstrumentation.ContainerType getStartingContainerType();
+
+ /** This method requires public access, however should not be called in tests. */
+ boolean isHomeState();
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 3450ea7..f6fcfa64 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -20,15 +20,10 @@
import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
-import android.app.UiAutomation;
import android.graphics.Point;
import android.view.MotionEvent;
-import android.view.accessibility.AccessibilityEvent;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -57,7 +52,19 @@
*/
public LaunchedAppState launch(String expectedPackageName) {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- return launch(By.pkg(expectedPackageName));
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "want to launch an app from " + launchableType())) {
+ LauncherInstrumentation.log("Launchable.launch before click "
+ + mObject.getVisibleCenter() + " in "
+ + mLauncher.getVisibleBounds(mObject));
+
+ mLauncher.clickLauncherObject(mObject);
+
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
+ expectActivityStartEvents();
+ return mLauncher.assertAppLaunched(expectedPackageName);
+ }
+ }
}
}
@@ -65,21 +72,6 @@
protected abstract String launchableType();
- private LaunchedAppState launch(BySelector selector) {
- try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
- "want to launch an app from " + launchableType())) {
- LauncherInstrumentation.log("Launchable.launch before click "
- + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
-
- mLauncher.clickLauncherObject(mObject);
-
- try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
- expectActivityStartEvents();
- return assertAppLaunched(selector);
- }
- }
- }
-
/**
* Clicks a launcher object to initiate splitscreen, where the selected app will be one of two
* apps running on the screen. Should be called when Launcher is in a "split staging" state
@@ -107,14 +99,6 @@
}
}
- protected LaunchedAppState assertAppLaunched(BySelector selector) {
- mLauncher.assertTrue(
- "App didn't start: (" + selector + ")",
- mLauncher.getDevice().wait(Until.hasObject(selector),
- LauncherInstrumentation.WAIT_TIME_MS));
- return new LaunchedAppState(mLauncher);
- }
-
Point startDrag(long downTime, Runnable expectLongClickEvents, boolean runToSpringLoadedState) {
final Point iconCenter = getObject().getVisibleCenter();
final Point dragStartCenter = new Point(iconCenter.x,
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 30417c0..9f8fb92 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -34,7 +34,6 @@
import android.view.MotionEvent;
import android.view.ViewConfiguration;
-import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Condition;
import androidx.test.uiautomator.UiDevice;
@@ -72,6 +71,11 @@
return LauncherInstrumentation.ContainerType.LAUNCHED_APP;
}
+ @Override
+ public boolean isHomeState() {
+ return false;
+ }
+
/**
* Returns the taskbar.
*
@@ -200,8 +204,8 @@
try (LauncherInstrumentation.Closable c4 = launcher.addContextLayer(
"dropped item")) {
- launchable.assertAppLaunched(By.pkg(expectedNewPackageName));
- launchable.assertAppLaunched(By.pkg(expectedExistingPackageName));
+ launcher.assertAppLaunched(expectedNewPackageName);
+ launcher.assertAppLaunched(expectedExistingPackageName);
}
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 71ca77f..d7f9c78 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -20,7 +20,10 @@
import static android.content.pm.PackageManager.DONT_KILL_APP;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.view.KeyEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
+
import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID;
import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -49,6 +52,9 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.WindowManager;
@@ -170,6 +176,7 @@
private static final String OPEN_FOLDER_RES_ID = "folder_content";
static final String TASKBAR_RES_ID = "taskbar_view";
private static final String SPLIT_PLACEHOLDER_RES_ID = "split_placeholder";
+ static final String KEYBOARD_QUICK_SWITCH_RES_ID = "keyboard_quick_switch_view";
public static final int WAIT_TIME_MS = 30000;
static final long DEFAULT_POLL_INTERVAL = 1000;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
@@ -697,6 +704,7 @@
/**
* Set the trackpad gesture type of the interaction.
+ *
* @param trackpadGestureType whether it's not from trackpad, two-finger, three-finger, or
* four-finger gesture.
*/
@@ -755,7 +763,7 @@
return isTablet() ? getLauncherPackageName() : SYSTEMUI_PACKAGE;
}
- private UiObject2 verifyContainerType(ContainerType containerType) {
+ UiObject2 verifyContainerType(ContainerType containerType) {
waitForLauncherInitialized();
if (mExpectedRotationCheckEnabled && mExpectedRotation != null) {
@@ -784,6 +792,7 @@
waitUntilLauncherObjectGone(WIDGETS_RES_ID);
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
if (is3PLauncher() && isTablet()) {
waitForSystemLauncherObject(TASKBAR_RES_ID);
@@ -798,6 +807,7 @@
waitUntilLauncherObjectGone(APPS_RES_ID);
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
if (is3PLauncher() && isTablet()) {
waitForSystemLauncherObject(TASKBAR_RES_ID);
@@ -813,6 +823,7 @@
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
return waitForLauncherObject(APPS_RES_ID);
}
@@ -821,6 +832,7 @@
waitUntilLauncherObjectGone(WIDGETS_RES_ID);
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
if (is3PLauncher() && isTablet()) {
waitForSystemLauncherObject(TASKBAR_RES_ID);
@@ -841,6 +853,7 @@
waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
}
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
return waitForSystemLauncherObject(OVERVIEW_RES_ID);
}
@@ -855,6 +868,7 @@
}
waitForSystemLauncherObject(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
return waitForSystemLauncherObject(OVERVIEW_RES_ID);
}
case LAUNCHED_APP: {
@@ -863,6 +877,7 @@
waitUntilLauncherObjectGone(WIDGETS_RES_ID);
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
if (mIgnoreTaskbarVisibility) {
return null;
@@ -981,6 +996,25 @@
}
/**
+ * Goes to home from immersive fullscreen app by first swiping up to bring navbar, and then
+ * performing {@code goHome()} action.
+ * Currently only supports gesture navigation mode.
+ *
+ * @return the Workspace object.
+ */
+ public Workspace goHomeFromImmersiveFullscreenApp() {
+ assertTrue("expected gesture navigation mode",
+ getNavigationModel() == NavigationModel.ZERO_BUTTON);
+ final Point displaySize = getRealDisplaySize();
+ linearGesture(
+ displaySize.x / 2, displaySize.y - 1,
+ displaySize.x / 2, 0,
+ ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
+ false, GestureScope.EXPECT_PILFER);
+ return goHome();
+ }
+
+ /**
* Goes to home by swiping up in zero-button mode or pressing Home button.
* Calling it after another TAPL call is safe because all TAPL methods wait for the animations
* to finish.
@@ -1170,6 +1204,14 @@
}
}
+ LaunchedAppState assertAppLaunched(@NonNull String expectedPackageName) {
+ BySelector packageSelector = By.pkg(expectedPackageName);
+ assertTrue("App didn't start: (" + packageSelector + ")",
+ mDevice.wait(Until.hasObject(packageSelector),
+ LauncherInstrumentation.WAIT_TIME_MS));
+ return new LaunchedAppState(this);
+ }
+
void waitUntilLauncherObjectGone(String resId) {
waitUntilGoneBySelector(getLauncherObjectSelector(resId));
}
@@ -1302,6 +1344,16 @@
}
}
+ void waitForObjectFocused(UiObject2 object, String waitReason) {
+ try {
+ assertTrue("Timed out waiting for object to be focused for " + waitReason + " "
+ + object.getResourceName(),
+ object.wait(Until.focused(true), WAIT_TIME_MS));
+ } catch (StaleObjectException e) {
+ fail("The object disappeared from screen");
+ }
+ }
+
@NonNull
UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) {
return waitForObjectsInContainer(container, selector).get(0);
@@ -1735,6 +1787,11 @@
InputDevice.SOURCE_TOUCHSCREEN);
}
+ private void injectEvent(InputEvent event) {
+ assertTrue("injectInputEvent failed: event=" + event,
+ mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
+ }
+
public void sendPointer(long downTime, long currentTime, int action, Point point,
GestureScope gestureScope, int source) {
final boolean hasTIS = hasTIS();
@@ -1765,16 +1822,46 @@
final MotionEvent event = isTrackpadGesture
? getTrackpadMotionEvent(
- downTime, currentTime, action, point.x, point.y, pointerCount,
- mTrackpadGestureType)
+ downTime, currentTime, action, point.x, point.y, pointerCount,
+ mTrackpadGestureType)
: getMotionEvent(downTime, currentTime, action, point.x, point.y, source);
if (action == MotionEvent.ACTION_BUTTON_PRESS
|| action == MotionEvent.ACTION_BUTTON_RELEASE) {
event.setActionButton(MotionEvent.BUTTON_PRIMARY);
}
- assertTrue("injectInputEvent failed",
- mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
- event.recycle();
+ injectEvent(event);
+ }
+
+ private KeyEvent createKeyEvent(int keyCode, int metaState, boolean actionDown) {
+ long eventTime = SystemClock.uptimeMillis();
+ return KeyEvent.obtain(
+ eventTime,
+ eventTime,
+ actionDown ? ACTION_DOWN : ACTION_UP,
+ keyCode,
+ /* repeat= */ 0,
+ metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD,
+ /* scancode= */ 0,
+ /* flags= */ 0,
+ InputDevice.SOURCE_KEYBOARD,
+ /* characters =*/ null);
+ }
+
+ /**
+ * Sends a {@link KeyEvent} with {@link ACTION_DOWN} for the given key codes without sending
+ * a {@link KeyEvent} with {@link ACTION_UP}.
+ */
+ public void pressAndHoldKeyCode(int keyCode, int metaState) {
+ injectEvent(createKeyEvent(keyCode, metaState, true));
+ }
+
+
+ /**
+ * Sends a {@link KeyEvent} with {@link ACTION_UP} for the given key codes.
+ */
+ public void unpressKeyCode(int keyCode, int metaState) {
+ injectEvent(createKeyEvent(keyCode, metaState, false));
}
public long movePointer(long downTime, long startTime, long duration, Point from, Point to,
@@ -1966,6 +2053,12 @@
getTestInfo(TestProtocol.REQUEST_RECREATE_TASKBAR);
}
+ // TODO(b/270393900): Remove with ENABLE_ALL_APPS_SEARCH_IN_TASKBAR flag cleanup.
+ /** Refreshes the known overview target in TIS. */
+ public void refreshOverviewTarget() {
+ getTestInfo(TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET);
+ }
+
public List<String> getHotseatIconNames() {
return getTestInfo(TestProtocol.REQUEST_HOTSEAT_ICON_NAMES)
.getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
@@ -1980,14 +2073,16 @@
return String.join(", ", getActivities());
}
- public boolean noLeakedActivities() {
+ /** Returns whether no leaked activities are detected. */
+ public boolean noLeakedActivities(boolean requireOneActiveActivity) {
final String[] activities = getActivities();
+
for (String activity : activities) {
if (activity.contains("(destroyed)")) {
return false;
}
}
- return activities.length <= 2;
+ return activities.length <= (requireOneActiveActivity ? 1 : 2);
}
public int getActivitiesCreated() {
@@ -2125,6 +2220,8 @@
containerBounds.bottom,
getRealDisplaySize().y - getImeInsets().bottom);
int y = (bottomBound - containerBounds.top) / 2;
+ // Do not tap in the status bar.
+ y = Math.max(y, getWindowInsets().top);
final long downTime = SystemClock.uptimeMillis();
final Point tapTarget = new Point(x, y);
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index e4cfc52..95a4802 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -168,7 +168,7 @@
}
}
- /** Taps the task menu. */
+ /** Taps the task menu. Returns the task menu object. */
@NonNull
public OverviewTaskMenu tapMenu() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
@@ -184,6 +184,22 @@
}
}
+ /** Taps the task menu of the split task. Returns the split task's menu object. */
+ @NonNull
+ public OverviewTaskMenu tapSplitTaskMenu() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to tap the split task's menu")) {
+ mLauncher.clickLauncherObject(
+ mLauncher.waitForObjectInContainer(mTask.getParent(), "bottomRight_icon"));
+
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "tapped the split task's menu")) {
+ return new OverviewTaskMenu(mLauncher);
+ }
+ }
+ }
+
boolean isTaskSplit() {
return mLauncher.findObjectInContainer(mTask.getParent(), "bottomright_snapshot") != null;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
index 859e504..25c73de 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
@@ -91,4 +91,11 @@
return new OverviewTaskMenuItem(mLauncher,
mLauncher.waitForObjectInContainer(mMenu, By.text(menuItemName)));
}
+
+ /**
+ * Taps outside task menu to dismiss it.
+ */
+ public void touchOutsideTaskMenuToDismiss() {
+ mLauncher.touchOutsideContainer(mMenu, false);
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Qsb.java b/tests/tapl/com/android/launcher3/tapl/Qsb.java
index 0f2aff8..5ca80a3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Qsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/Qsb.java
@@ -25,7 +25,7 @@
/**
* Operations on qsb from either Home screen or AllApp screen.
*/
-public abstract class Qsb {
+public abstract class Qsb implements SearchInputSource {
private static final String ASSISTANT_APP_PACKAGE = "com.google.android.googlequicksearchbox";
private static final String ASSISTANT_ICON_RES_ID = "mic_icon";
@@ -125,6 +125,16 @@
}
}
+ @Override
+ public LauncherInstrumentation getLauncher() {
+ return mLauncher;
+ }
+
+ @Override
+ public SearchResultFromQsb getSearchResultForInput() {
+ return createSearchResult();
+ }
+
protected SearchResultFromQsb createSearchResult() {
return new SearchResultFromQsb(mLauncher);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchInputSource.java b/tests/tapl/com/android/launcher3/tapl/SearchInputSource.java
new file mode 100644
index 0000000..032948f
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SearchInputSource.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+import com.android.launcher3.testing.shared.TestProtocol;
+
+/**
+ * Container that can be used to input a search query and retrieve a {@link SearchResultFromQsb}
+ * instance.
+ */
+interface SearchInputSource {
+ String INPUT_RES = "input";
+
+ /** Set the already focused search input edit text and update search results. */
+ default SearchResultFromQsb searchForInput(String input) {
+ LauncherInstrumentation launcher = getLauncher();
+ try (LauncherInstrumentation.Closable c = launcher.addContextLayer(
+ "want to search for result with an input");
+ LauncherInstrumentation.Closable e = launcher.eventsCheck()) {
+ launcher.executeAndWaitForLauncherEvent(
+ () -> {
+ UiObject2 editText = launcher.waitForLauncherObject(INPUT_RES);
+ launcher.waitForObjectFocused(editText, "search input");
+ editText.setText(input);
+ },
+ event -> TestProtocol.SEARCH_RESULT_COMPLETE.equals(event.getClassName()),
+ () -> "Didn't receive a search result completed message", "searching");
+ return getSearchResultForInput();
+ }
+ }
+
+ /** This method requires public access, however should not be called in tests. */
+ LauncherInstrumentation getLauncher();
+
+ /** This method requires public access, however should not be called in tests. */
+ SearchResultFromQsb getSearchResultForInput();
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index 513d6bb..f0a8aa2 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -20,16 +20,12 @@
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
-import com.android.launcher3.testing.shared.TestProtocol;
-
import java.util.ArrayList;
/**
* Operations on search result page opened from qsb.
*/
-public class SearchResultFromQsb {
- // The input resource id in the search box.
- private static final String INPUT_RES = "input";
+public class SearchResultFromQsb implements SearchInputSource {
private static final String BOTTOM_SHEET_RES_ID = "bottom_sheet_background";
// This particular ID change should happen with caution
@@ -41,18 +37,6 @@
mLauncher.waitForLauncherObject("search_container_all_apps");
}
- /** Set the input to the search input edit text and update search results. */
- public void searchForInput(String input) {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
- "want to search for result with an input");
- LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- mLauncher.executeAndWaitForLauncherEvent(
- () -> mLauncher.waitForLauncherObject(INPUT_RES).setText(input),
- event -> TestProtocol.SEARCH_RESULT_COMPLETE.equals(event.getClassName()),
- () -> "Didn't receive a search result completed message", "searching");
- }
- }
-
/** Find the app from search results with app name. */
public AppIcon findAppIcon(String appName) {
UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName));
@@ -114,4 +98,14 @@
protected void verifyVisibleContainerOnDismiss() {
mLauncher.getWorkspace();
}
+
+ @Override
+ public LauncherInstrumentation getLauncher() {
+ return mLauncher;
+ }
+
+ @Override
+ public SearchResultFromQsb getSearchResultForInput() {
+ return this;
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java b/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java
index ce1c3c0..2870877 100644
--- a/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java
+++ b/tests/tapl/com/android/launcher3/tapl/SplitscreenDragSource.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.tapl;
-/** Launchable that can serve as a source for dragging and dropping to splitscreen. */
+/** {@link Launchable} that can serve as a source for dragging and dropping to splitscreen. */
interface SplitscreenDragSource {
/**
@@ -35,5 +35,6 @@
}
}
+ /** This method requires public access, however should not be called in tests. */
Launchable getLaunchable();
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Taskbar.java b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
index 4293ee8..da26694 100644
--- a/tests/tapl/com/android/launcher3/tapl/Taskbar.java
+++ b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.tapl;
+import static android.view.KeyEvent.KEYCODE_META_RIGHT;
+
import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING;
@@ -105,6 +107,17 @@
}
}
+ /** Opens the Taskbar all apps page with the meta keyboard shortcut. */
+ public TaskbarAllApps openAllAppsFromKeyboardShortcut() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT);
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "pressed meta key")) {
+ return getAllApps();
+ }
+ }
+ }
+
/** Returns {@link TaskbarAllApps} if it is open, otherwise fails. */
public TaskbarAllApps getAllApps() {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAllApps.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAllApps.java
index c1234fe..3d39041 100644
--- a/tests/tapl/com/android/launcher3/tapl/TaskbarAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAllApps.java
@@ -73,4 +73,9 @@
protected void verifyVisibleContainerOnDismiss() {
mLauncher.getLaunchedAppState().assertTaskbarVisible();
}
+
+ @Override
+ public boolean isHomeState() {
+ return false;
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 79b54ba..105bc3b 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -34,7 +34,8 @@
/**
* All widgets container.
*/
-public final class Widgets extends LauncherInstrumentation.VisibleContainer {
+public final class Widgets extends LauncherInstrumentation.VisibleContainer
+ implements KeyboardQuickSwitchSource {
private static final int FLING_STEPS = 10;
private static final int SCROLL_ATTEMPTS = 60;
@@ -43,6 +44,21 @@
verifyActiveContainer();
}
+ @Override
+ public LauncherInstrumentation getLauncher() {
+ return mLauncher;
+ }
+
+ @Override
+ public LauncherInstrumentation.ContainerType getStartingContainerType() {
+ return getContainerType();
+ }
+
+ @Override
+ public boolean isHomeState() {
+ return true;
+ }
+
/**
* Flings forward (down) and waits the fling's end.
*/
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index cf48ebc..fc589bd 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -16,6 +16,7 @@
package com.android.launcher3.tapl;
+import static android.view.KeyEvent.KEYCODE_META_RIGHT;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED;
import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
@@ -115,6 +116,20 @@
}
}
+ /** Opens the Launcher all apps page with the meta keyboard shortcut. */
+ public HomeAllApps openAllAppsFromKeyboardShortcut() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c =
+ mLauncher.addContextLayer("want to open all apps search")) {
+ verifyActiveContainer();
+ mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT);
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "pressed meta key")) {
+ return new HomeAllApps(mLauncher);
+ }
+ }
+ }
+
/**
* Returns the home qsb.
*
diff --git a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
index 141476c..5a4d562 100644
--- a/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
+++ b/tests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
@@ -19,7 +19,7 @@
import java.util.function.Supplier;
-/** Launchable that can serve as a source for dragging and dropping to the workspace. */
+/** {@link Launchable} that can serve as a source for dragging and dropping to the workspace. */
interface WorkspaceDragSource {
/**