Merge "Make DISPLAY_SEARCH_RESULT_SMALL not badged" 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/go/src/com/android/launcher3/model/LauncherBinder.java b/go/src/com/android/launcher3/model/LauncherBinder.java
index 437d8ca..7a0dce8 100644
--- a/go/src/com/android/launcher3/model/LauncherBinder.java
+++ b/go/src/com/android/launcher3/model/LauncherBinder.java
@@ -38,4 +38,8 @@
@Override
public void bindWidgets() {
}
+
+ @Override
+ public void bindSmartspaceWidget() {
+ }
}
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/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/model/PredictionHelper.java b/quickstep/src/com/android/launcher3/model/PredictionHelper.java
index 738dd83..dbd99e1 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionHelper.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionHelper.java
@@ -67,6 +67,9 @@
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
context.getPackageName(), info.user).build();
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
+ return new AppTarget.Builder(new AppTargetId("app_pair:" + info.id),
+ context.getPackageName(), info.user).build();
}
return null;
}
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/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index e6dfe0f..1c7d7e0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -337,7 +337,7 @@
}
public boolean isBubbleBarEnabled() {
- return BubbleBarController.BUBBLE_BAR_ENABLED;
+ return BubbleBarController.isBubbleBarEnabled();
}
/** Whether the bubble bar has any bubbles. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index df6626a..4b16019 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -191,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;
@@ -237,10 +238,10 @@
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)));
}
@@ -736,7 +737,9 @@
if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
NavButtonLayoutter navButtonLayoutter =
NavButtonLayoutFactory.Companion.getUiLayoutter(
- dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
+ dp, mNavButtonsView, mImeSwitcherButton,
+ mControllers.rotationButtonController.getRotationButton(),
+ mA11yButton, res, isInKidsMode, isInSetup, isThreeButtonNav,
TaskbarManager.isPhoneMode(dp),
mWindowManagerProxy.getRotation(mContext));
navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 0aa02f2..9f8f82a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -218,7 +218,8 @@
// If Bubble bar is present, TaskbarControllers depends on it so build it first.
Optional<BubbleControllers> bubbleControllersOptional = Optional.empty();
- if (BubbleBarController.BUBBLE_BAR_ENABLED && bubbleBarView != null) {
+ BubbleBarController.onTaskbarRecreated();
+ if (BubbleBarController.isBubbleBarEnabled() && bubbleBarView != null) {
bubbleControllersOptional = Optional.of(new BubbleControllers(
new BubbleBarController(this, bubbleBarView),
new BubbleBarViewController(this, bubbleBarView),
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/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
index a0ce976..712374d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -17,7 +17,7 @@
import static android.view.View.VISIBLE;
-import static com.android.launcher3.taskbar.bubbles.BubbleBarController.BUBBLE_BAR_ENABLED;
+import static com.android.launcher3.taskbar.bubbles.BubbleBarController.isBubbleBarEnabled;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
@@ -83,7 +83,7 @@
* Updates the scrim state based on the flags.
*/
public void updateStateForSysuiFlags(int stateFlags, boolean skipAnim) {
- if (BUBBLE_BAR_ENABLED && DisplayController.isTransientTaskbar(mActivity)) {
+ if (isBubbleBarEnabled() && DisplayController.isTransientTaskbar(mActivity)) {
// These scrims aren't used if bubble bar & transient taskbar are active.
return;
}
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/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index bd11efd..3fb7247 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -85,17 +85,31 @@
* information to render each of the bubbles & dispatches changes to
* {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed.
*
- * For details around the behavior of the bubble bar, see {@link BubbleBarView}.
+ * <p>For details around the behavior of the bubble bar, see {@link BubbleBarView}.
*/
public class BubbleBarController extends IBubblesListener.Stub {
private static final String TAG = BubbleBarController.class.getSimpleName();
private static final boolean DEBUG = false;
- // Whether bubbles are showing in the bubble bar from launcher
- public static final boolean BUBBLE_BAR_ENABLED =
+ /**
+ * Determines whether bubbles can be shown in the bubble bar. This value updates when the
+ * taskbar is recreated.
+ *
+ * @see #onTaskbarRecreated()
+ */
+ private static boolean sBubbleBarEnabled =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+ /** Whether showing bubbles in the launcher bubble bar is enabled. */
+ public static boolean isBubbleBarEnabled() {
+ return sBubbleBarEnabled;
+ }
+
+ /** Re-reads the value of the flag from SystemProperties when taskbar is recreated. */
+ public static void onTaskbarRecreated() {
+ sBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+ }
private static final int MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
@@ -167,7 +181,7 @@
mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
- if (BUBBLE_BAR_ENABLED) {
+ if (sBubbleBarEnabled) {
mSystemUiProxy.setBubblesListener(this);
}
mMainExecutor = MAIN_EXECUTOR;
@@ -191,9 +205,9 @@
bubbleControllers.runAfterInit(() -> {
mBubbleBarViewController.setHiddenForBubbles(
- !BUBBLE_BAR_ENABLED || mBubbles.isEmpty());
+ !sBubbleBarEnabled || mBubbles.isEmpty());
mBubbleStashedHandleViewController.setHiddenForBubbles(
- !BUBBLE_BAR_ENABLED || mBubbles.isEmpty());
+ !sBubbleBarEnabled || mBubbles.isEmpty());
mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
key -> setSelectedBubble(mBubbles.get(key)));
});
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 13ffe6e..efe9d4b 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) {
@@ -81,6 +89,8 @@
}
}
}
+
+ repositionContextualButtons()
}
open fun addThreeButtons() {
@@ -89,4 +99,24 @@
navButtonContainer.addView(homeButton)
navButtonContainer.addView(backButton)
}
+
+ open fun repositionContextualButtons() {
+ 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()
+ }
+ }
}
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 2d62c3f..fd6e991 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -17,20 +17,30 @@
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.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 addThreeButtons() {
@@ -39,4 +49,24 @@
navButtonContainer.addView(homeButton)
navButtonContainer.addView(recentsButton)
}
+
+ override fun repositionContextualButtons() {
+ 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/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/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/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 3429df1..56765e5 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -78,7 +78,7 @@
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.bubbles.IBubbles;
import com.android.wm.shell.bubbles.IBubblesListener;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.desktopmode.IDesktopMode;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
import com.android.wm.shell.draganddrop.IDragAndDrop;
@@ -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);
@@ -797,7 +799,7 @@
/** Start multiple tasks in split-screen simultaneously. */
public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
- @StagePosition int splitPosition, @SnapPosition int snapPosition,
+ @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition,
RemoteTransition remoteTransition, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
@@ -811,7 +813,7 @@
public void startIntentAndTask(PendingIntent pendingIntent, int userId1, Bundle options1,
int taskId, Bundle options2, @StagePosition int splitPosition,
- @SnapPosition int snapPosition, RemoteTransition remoteTransition,
+ @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition,
InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
@@ -826,7 +828,7 @@
public void startIntents(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, Bundle options1, PendingIntent pendingIntent2,
int userId2, @Nullable ShortcutInfo shortcutInfo2, Bundle options2,
- @StagePosition int splitPosition, @SnapPosition int snapPosition,
+ @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition,
RemoteTransition remoteTransition, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
@@ -840,8 +842,9 @@
}
public void startShortcutAndTask(ShortcutInfo shortcutInfo, Bundle options1, int taskId,
- Bundle options2, @StagePosition int splitPosition, @SnapPosition int snapPosition,
- RemoteTransition remoteTransition, InstanceId instanceId) {
+ Bundle options2, @StagePosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition,
+ InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
@@ -856,8 +859,9 @@
* Start multiple tasks in split-screen simultaneously.
*/
public void startTasksWithLegacyTransition(int taskId1, Bundle options1, int taskId2,
- Bundle options2, @StagePosition int splitPosition, @SnapPosition int snapPosition,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ Bundle options2, @StagePosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
@@ -871,7 +875,8 @@
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
Bundle options1, int taskId, Bundle options2, @StagePosition int splitPosition,
- @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
@@ -886,7 +891,8 @@
public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, Bundle options1,
int taskId, Bundle options2, @StagePosition int splitPosition,
- @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
@@ -906,7 +912,8 @@
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
@Nullable Bundle options2, @StagePosition int sidePosition,
- @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, userId1,
@@ -1109,11 +1116,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 +1128,7 @@
Log.w(TAG, "Failed call setLauncherUnlockAnimationController", e);
}
}
-
+ mLauncherActivityClass = activityClass;
mLauncherUnlockAnimationController = controller;
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 2adc790..b3b7be4 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -50,6 +50,7 @@
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
@@ -128,12 +129,12 @@
/**
* A menu item, "Save app pair", that allows the user to preserve the current app combination as
- * a single persistent icon on the Home screen, allowing for quick split screen initialization.
+ * one persistent icon on the Home screen, allowing for quick split screen launching.
*/
class SaveAppPairSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
- private final TaskView mTaskView;
+ private final GroupedTaskView mTaskView;
- public SaveAppPairSystemShortcut(BaseDraggingActivity activity, TaskView taskView) {
+ public SaveAppPairSystemShortcut(BaseDraggingActivity activity, GroupedTaskView taskView) {
super(R.drawable.ic_save_app_pair, R.string.save_app_pair, activity,
taskView.getItemInfo(), taskView);
mTaskView = taskView;
@@ -318,7 +319,8 @@
return null;
}
- return Collections.singletonList(new SaveAppPairSystemShortcut(activity, taskView));
+ return Collections.singletonList(
+ new SaveAppPairSystemShortcut(activity, (GroupedTaskView) taskView));
}
@Override
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/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/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 1a7099d..8888831 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -20,46 +20,46 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.isPersistentSnapPosition;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import java.util.Arrays;
/**
- * Mini controller class that handles app pair interactions: saving, modifying, deleting, etc.
+ * Controller class that handles app pair interactions: saving, modifying, deleting, etc.
+ * <br>
+ * App pairs contain two "member" apps, which are determined at the time of app pair creation
+ * and never modified. The member apps are WorkspaceItemInfos, but use the "rank" attribute
+ * differently from other ItemInfos -- we use it to store information about the split position and
+ * ratio.
*/
public class AppPairsController {
-
- private static final int POINT_THREE_RATIO = 0;
- private static final int POINT_FIVE_RATIO = 1;
- private static final int POINT_SEVEN_RATIO = 2;
- /**
- * Used to calculate {@link #complement(int)}
- */
- private static final int FULL_RATIO = 2;
-
- private static final int LEFT_TOP = 0;
- private static final int RIGHT_BOTTOM = 1 << 2;
-
- // TODO (jeremysim b/274189428): Support saving different ratios in future.
- public int DEFAULT_RATIO = POINT_FIVE_RATIO;
+ // Used for encoding and decoding the "rank" attribute
+ private static final int BITMASK_SIZE = 16;
+ private static final int BITMASK_FOR_SNAP_POSITION = (1 << BITMASK_SIZE) - 1;
private final Context mContext;
private final SplitSelectStateController mSplitSelectStateController;
@@ -75,17 +75,21 @@
/**
* Creates a new app pair ItemInfo and adds it to the workspace
*/
- public void saveAppPair(TaskView taskView) {
- TaskView.TaskIdAttributeContainer[] attributes = taskView.getTaskIdAttributeContainers();
+ public void saveAppPair(GroupedTaskView gtv) {
+ TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers();
WorkspaceItemInfo app1 = attributes[0].getItemInfo().clone();
WorkspaceItemInfo app2 = attributes[1].getItemInfo().clone();
app1.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
app2.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- app1.rank = DEFAULT_RATIO + LEFT_TOP;
- app2.rank = complement(DEFAULT_RATIO) + RIGHT_BOTTOM;
+
+ @PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
+ if (!isPersistentSnapPosition(snapPosition)) {
+ throw new RuntimeException("tried to save an app pair with illegal snapPosition");
+ }
+
+ app1.rank = encodeRank(SPLIT_POSITION_TOP_OR_LEFT, snapPosition);
+ app2.rank = encodeRank(SPLIT_POSITION_BOTTOM_OR_RIGHT, snapPosition);
FolderInfo newAppPair = FolderInfo.createAppPair(app1, app2);
- // TODO (jeremysim b/274189428): Generate default title here.
- newAppPair.title = "App pair 1";
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
MODEL_EXECUTOR.execute(() -> {
@@ -94,6 +98,8 @@
member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
iconCache.getTitleAndIcon(member, member.usingLowResIcon());
});
+ newAppPair.title = getDefaultTitle(newAppPair.contents.get(0).title,
+ newAppPair.contents.get(1).title);
MAIN_EXECUTOR.execute(() -> {
LauncherAccessibilityDelegate delegate =
Launcher.getLauncher(mContext).getAccessibilityDelegate();
@@ -128,7 +134,7 @@
}
mSplitSelectStateController.setInitialTaskSelect(task1Intent,
- SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ AppPairsController.convertRankToStagePosition(app1.rank),
app1,
LAUNCHER_APP_PAIR_LAUNCH,
task1Id);
@@ -141,18 +147,42 @@
app2.intent, app2.user);
}
- mSplitSelectStateController.launchSplitTasks();
+ mSplitSelectStateController.launchSplitTasks(
+ AppPairsController.convertRankToSnapPosition(app1.rank));
});
}
/**
- * Used to calculate the "opposite" side of the split ratio, so we can know how big the split
- * apps are supposed to be. This math works because POINT_THREE_RATIO is internally represented
- * by 0, POINT_FIVE_RATIO is represented by 1, and POINT_SEVEN_RATIO is represented by 2. There
- * are no other supported ratios for now.
+ * App pair members have a "rank" attribute that contains information about the split position
+ * and ratio. We implement this by splitting the int in half (e.g. 16 bits each), then use one
+ * half to store splitPosition (left vs right) and the other half to store snapPosition
+ * (30-70 split vs 50-50 split)
*/
- private int complement(int ratio1) {
- int ratio2 = FULL_RATIO - ratio1;
- return ratio2;
+ @VisibleForTesting
+ public int encodeRank(int splitPosition, int snapPosition) {
+ return (splitPosition << BITMASK_SIZE) + snapPosition;
+ }
+
+ /**
+ * Returns the desired stage position for the app pair to be launched in (decoded from the
+ * "rank" integer).
+ */
+ public static int convertRankToStagePosition(int rank) {
+ return rank >> BITMASK_SIZE;
+ }
+
+ /**
+ * Returns the desired split ratio for the app pair to be launched in (decoded from the "rank"
+ * integer).
+ */
+ public static int convertRankToSnapPosition(int rank) {
+ return rank & BITMASK_FOR_SNAP_POSITION;
+ }
+
+ /**
+ * Returns a formatted default title for the app pair.
+ */
+ public String getDefaultTitle(CharSequence app1, CharSequence app2) {
+ return mContext.getString(R.string.app_pair_default_title, app1, app2);
}
}
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/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index c8831c7..b9829f7 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -98,7 +98,7 @@
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.splitscreen.ISplitSelectListener;
import java.io.PrintWriter;
@@ -289,7 +289,7 @@
* To be called when the both split tasks are ready to be launched. Call after launcher side
* animations are complete.
*/
- public void launchSplitTasks(@SnapPosition int snapPosition,
+ public void launchSplitTasks(@PersistentSnapPosition int snapPosition,
@Nullable Consumer<Boolean> callback) {
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
LogUtils.getShellShareableInstanceId();
@@ -302,6 +302,14 @@
}
/**
+ * A version of {@link #launchTasks(Consumer, boolean, int, InstanceId)} with no success
+ * callback.
+ */
+ public void launchSplitTasks(@PersistentSnapPosition int snapPosition) {
+ launchSplitTasks(snapPosition, /* callback */ null);
+ }
+
+ /**
* A version of {@link #launchSplitTasks(int, Consumer)} that launches with default split ratio.
*/
public void launchSplitTasks(@Nullable Consumer<Boolean> callback) {
@@ -350,7 +358,7 @@
* foreground (quickswitch, launching previous pairs from overview)
*/
public void launchTasks(@Nullable Consumer<Boolean> callback, boolean freezeTaskList,
- @SnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) {
+ @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
final ActivityOptions options1 = ActivityOptions.makeBasic();
@@ -455,7 +463,8 @@
*/
public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
- Consumer<Boolean> callback, boolean freezeTaskList, @SnapPosition int snapPosition) {
+ Consumer<Boolean> callback, boolean freezeTaskList,
+ @PersistentSnapPosition int snapPosition) {
mLaunchingTaskView = groupedTaskView;
final ActivityOptions options1 = ActivityOptions.makeBasic();
if (freezeTaskList) {
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 32d6582..dc6b5a2 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -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,7 +330,7 @@
}
@Override
- protected boolean showTaskMenuWithContainer(IconView iconView) {
+ protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) {
return false;
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 7e58763..71758ad 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;
@@ -32,11 +35,13 @@
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
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);
}
}
}
@@ -186,9 +200,9 @@
}
/**
- * Returns the {@link SnapPosition} of this pair of tasks.
+ * Returns the {@link PersistentSnapPosition} of this pair of tasks.
*/
- public int getSnapPosition() {
+ public @PersistentSnapPosition int getSnapPosition() {
if (mSplitBoundsConfig == null) {
throw new IllegalStateException("mSplitBoundsConfig is 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/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 d5b43a8..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;
@@ -120,6 +122,8 @@
import java.util.function.Consumer;
import java.util.stream.Stream;
+import kotlin.Unit;
+
/**
* A task in the Recents view.
*/
@@ -350,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;
@@ -440,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) {
@@ -521,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;
@@ -563,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);
}
/**
@@ -587,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));
}
/**
@@ -629,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);
}
@@ -745,7 +758,7 @@
return new TaskThumbnailView[]{mSnapshotView};
}
- public IconView getIconView() {
+ public TaskViewIcon getIconView() {
return mIconView;
}
@@ -1075,6 +1088,7 @@
mIconLoadRequest = iconCache.updateIconInBackground(mTask,
(task) -> {
setIcon(mIconView, task.icon);
+ setText(mIconView, TaskUtils.getTitle(getContext(), task));
mDigitalWellBeingToast.initialize(task);
});
}
@@ -1090,6 +1104,7 @@
}
if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
setIcon(mIconView, null);
+ setText(mIconView, null);
}
}
}
@@ -1109,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;
@@ -1126,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()) {
@@ -1142,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 -> {
@@ -1168,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) {
@@ -1898,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;
@@ -1930,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 7d82944..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;
@@ -61,6 +59,7 @@
import com.android.launcher3.util.rule.FailureWatcher;
import com.android.launcher3.util.rule.SamplerRule;
import com.android.launcher3.util.rule.ScreenRecordRule;
+import com.android.launcher3.util.rule.TestIsolationRule;
import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.launcher3.util.rule.ViewCaptureRule;
import com.android.quickstep.views.RecentsView;
@@ -94,9 +93,6 @@
public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification();
@Rule
- public final TestRule mSetLauncherCommand;
-
- @Rule
public final TestRule mOrderSensitiveRules;
@Rule
@@ -116,19 +112,7 @@
Utilities.enableRunningInTestHarnessForTests();
}
- final ViewCaptureRule viewCaptureRule = new ViewCaptureRule(
- RecentsActivity.ACTIVITY_TRACKER::getCreatedActivity);
- mOrderSensitiveRules = RuleChain
- .outerRule(new SamplerRule())
- .around(new NavigationModeSwitchRule(mLauncher))
- .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
- .around(viewCaptureRule);
-
- mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
- getHomeIntentInPackage(context),
- MATCH_DISABLED_COMPONENTS).get(0).activityInfo;
-
- mSetLauncherCommand = (base, desc) -> new Statement() {
+ final TestRule setLauncherCommand = (base, desc) -> new Statement() {
@Override
public void evaluate() throws Throwable {
TestCommandReceiver.callCommand(TestCommandReceiver.ENABLE_TEST_LAUNCHER);
@@ -152,6 +136,21 @@
}
};
+ final ViewCaptureRule viewCaptureRule = new ViewCaptureRule(
+ RecentsActivity.ACTIVITY_TRACKER::getCreatedActivity);
+ mOrderSensitiveRules = RuleChain
+ .outerRule(new SamplerRule())
+ .around(new TestStabilityRule())
+ .around(new NavigationModeSwitchRule(mLauncher))
+ .around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
+ .around(viewCaptureRule)
+ .around(new TestIsolationRule(mLauncher, false))
+ .around(setLauncherCommand);
+
+ mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
+ getHomeIntentInPackage(context),
+ MATCH_DISABLED_COMPONENTS).get(0).activityInfo;
+
if (TestHelpers.isInLauncherProcess()) {
mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
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..3039261 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;
@@ -89,6 +83,7 @@
@After
public void tearDown() {
executeOnLauncher(launcher -> {
+ if (launcher == null) return;
RecentsView recentsView = launcher.getOverviewPanel();
recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
});
@@ -203,6 +198,7 @@
}
+ @PlatinumTest(focusArea = "launcher")
@Test
public void testOverviewActionsMenu() throws Exception {
startTestAppsWithCheck();
@@ -215,6 +211,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 +333,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 +353,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/util/AppPairsControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
new file mode 100644
index 0000000..1723844
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+class AppPairsControllerTest {
+ @Mock lateinit var context: Context
+ @Mock lateinit var splitSelectStateController: SplitSelectStateController
+ @Mock lateinit var statsLogManager: StatsLogManager
+
+ private lateinit var appPairsController: AppPairsController
+
+ private val left30: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_30_70)
+ }
+ private val left50: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_50_50)
+ }
+ private val left70: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_70_30)
+ }
+ private val right30: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_30_70)
+ }
+ private val right50: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_50_50)
+ }
+ private val right70: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_70_30)
+ }
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ appPairsController =
+ AppPairsController(context, splitSelectStateController, statsLogManager)
+ }
+
+ @Test
+ fun shouldEncodeRankCorrectly() {
+ assertEquals("left + 30-70 should encode as 0 (0b0)", 0, left30)
+ assertEquals("left + 50-50 should encode as 1 (0b1)", 1, left50)
+ assertEquals("left + 70-30 should encode as 2 (0b10)", 2, left70)
+ // See AppPairsController#BITMASK_SIZE and BITMASK_FOR_SNAP_POSITION for context
+ assertEquals("right + 30-70 should encode as 1 followed by 16 0s", 1 shl 16, right30)
+ assertEquals("right + 50-50 should encode as the above value + 1", (1 shl 16) + 1, right50)
+ assertEquals("right + 70-30 should encode as the above value + 2", (1 shl 16) + 2, right70)
+ }
+
+ @Test
+ fun shouldDecodeRankCorrectly() {
+ assertEquals(
+ "left + 30-70 should decode to left",
+ STAGE_POSITION_TOP_OR_LEFT,
+ AppPairsController.convertRankToStagePosition(left30),
+ )
+ assertEquals(
+ "left + 30-70 should decode to 30-70",
+ SNAP_TO_30_70,
+ AppPairsController.convertRankToSnapPosition(left30),
+ )
+
+ assertEquals(
+ "left + 50-50 should decode to left",
+ STAGE_POSITION_TOP_OR_LEFT,
+ AppPairsController.convertRankToStagePosition(left50),
+ )
+ assertEquals(
+ "left + 50-50 should decode to 50-50",
+ SNAP_TO_50_50,
+ AppPairsController.convertRankToSnapPosition(left50),
+ )
+
+ assertEquals(
+ "left + 70-30 should decode to left",
+ STAGE_POSITION_TOP_OR_LEFT,
+ AppPairsController.convertRankToStagePosition(left70),
+ )
+ assertEquals(
+ "left + 70-30 should decode to 70-30",
+ SNAP_TO_70_30,
+ AppPairsController.convertRankToSnapPosition(left70),
+ )
+
+ assertEquals(
+ "right + 30-70 should decode to right",
+ STAGE_POSITION_BOTTOM_OR_RIGHT,
+ AppPairsController.convertRankToStagePosition(right30),
+ )
+ assertEquals(
+ "right + 30-70 should decode to 30-70",
+ SNAP_TO_30_70,
+ AppPairsController.convertRankToSnapPosition(right30),
+ )
+
+ assertEquals(
+ "right + 50-50 should decode to right",
+ STAGE_POSITION_BOTTOM_OR_RIGHT,
+ AppPairsController.convertRankToStagePosition(right50),
+ )
+ assertEquals(
+ "right + 50-50 should decode to 50-50",
+ SNAP_TO_50_50,
+ AppPairsController.convertRankToSnapPosition(right50),
+ )
+
+ assertEquals(
+ "right + 70-30 should decode to right",
+ STAGE_POSITION_BOTTOM_OR_RIGHT,
+ AppPairsController.convertRankToStagePosition(right70),
+ )
+ assertEquals(
+ "right + 70-30 should decode to 70-30",
+ SNAP_TO_70_30,
+ AppPairsController.convertRankToSnapPosition(right70),
+ )
+ }
+}
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/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 10f47cb..7661bd7 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -399,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 a2f4a61..37bd4f1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -44,6 +44,8 @@
<!-- App pairs -->
<string name="save_app_pair">Save app pair</string>
+ <!-- App pair default title -->
+ <string name="app_pair_default_title"><xliff:g id="app1" example="Chrome">%1$s</xliff:g> | <xliff:g id="app2" example="YouTube">%2$s</xliff:g></string>
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 8240694..6d14b31 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -17,7 +17,6 @@
package com.android.launcher3;
import static android.text.Layout.Alignment.ALIGN_NORMAL;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;
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;
@@ -880,7 +879,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);
}
}
@@ -917,9 +916,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);
@@ -944,9 +941,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 bf35a0f..9a2193f 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);
@@ -698,6 +705,17 @@
}
/**
+ * Return maximum of all apps row count displayed on screen. Note that 1) Partially displayed
+ * row is counted as 1 row, and 2) we don't exclude the space of floating search bar. This
+ * method is used for calculating number of {@link BubbleTextView} we need to pre-inflate. Thus
+ * reasonable over estimation is fine.
+ */
+ public int getMaxAllAppsRowCount() {
+ return (int) (Math.ceil((availableHeightPx - allAppsTopPadding)
+ / (float) allAppsCellHeightPx));
+ }
+
+ /**
* QSB width is always calculated because when in 3 button nav the width doesn't follow the
* width of the hotseat.
*/
@@ -2016,6 +2034,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",
@@ -2123,10 +2143,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) {
@@ -2177,6 +2200,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");
@@ -2198,7 +2230,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..66a35d7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -28,6 +28,7 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
+import static com.android.launcher3.BuildConfig.APPLICATION_ID;
import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
@@ -43,10 +44,12 @@
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.WorkspaceLayoutManager.FIRST_SCREEN_ID;
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;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import static com.android.launcher3.logging.StatsLogManager.EventEnum;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -114,7 +117,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 +139,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 +206,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 +337,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 +430,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 +679,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 +792,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 +1133,6 @@
mDeferOverlayCallbacks = true;
}
- public LauncherOverlayManager getOverlayManager() {
- return mOverlayManager;
- }
-
@Override
public void onStateSetStart(LauncherState state) {
super.onStateSetStart(state);
@@ -1589,79 +1540,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 +1778,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 +2007,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 +2068,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 +2094,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;
@@ -2352,11 +2181,14 @@
public void bindScreens(IntArray orderedScreenIds) {
mWorkspace.mPageIndicator.setAreScreensBinding(true);
int firstScreenPosition = 0;
- if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
- orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != firstScreenPosition) {
- orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
- orderedScreenIds.add(firstScreenPosition, Workspace.FIRST_SCREEN_ID);
- } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
+ if ((FeatureFlags.QSB_ON_FIRST_SCREEN
+ && !shouldShowFirstPageWidget())
+ && orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition) {
+ orderedScreenIds.removeValue(FIRST_SCREEN_ID);
+ orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID);
+ } else if ((!FeatureFlags.QSB_ON_FIRST_SCREEN
+ || shouldShowFirstPageWidget())
+ && orderedScreenIds.isEmpty()) {
// If there are no screens, we need to have an empty screen
mWorkspace.addExtraEmptyScreens();
}
@@ -2404,7 +2236,9 @@
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
int screenId = orderedScreenIds.get(i);
- if (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID) {
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN
+ && !shouldShowFirstPageWidget()
+ && screenId == FIRST_SCREEN_ID) {
// No need to bind the first screen, as its always bound.
continue;
}
@@ -3105,14 +2939,39 @@
}
@Override
- public void bindStringCache(StringCache cache) {
- mStringCache = cache;
- mAppsView.updateWorkUI();
+ public void bindSmartspaceWidget() {
+ CellLayout cl = mWorkspace.getScreenWithId(FIRST_SCREEN_ID);
+ int spanX = InvariantDeviceProfile.INSTANCE.get(this).numSearchContainerColumns;
+ if (cl != null) {
+ for (int col = 0; col < spanX; col++) {
+ if (cl.isOccupied(col, 0)) {
+ return;
+ }
+ }
+ } else {
+ return;
+ }
+
+ WidgetsListBaseEntry widgetsListBaseEntry = getPopupDataProvider()
+ .getAllWidgets().stream().filter(
+ item -> item.mPkgItem.packageName.equals(
+ APPLICATION_ID))
+ .findFirst()
+ .orElse(null);
+ if (widgetsListBaseEntry != null) {
+ LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
+ widgetsListBaseEntry.mWidgets.get(0).widgetInfo;
+ PendingAddWidgetInfo info = new PendingAddWidgetInfo(launcherAppWidgetProviderInfo,
+ CONTAINER_DESKTOP);
+ addPendingItem(info, info.container, FIRST_SCREEN_ID, new int[]{0, 0}, info.spanX,
+ info.spanY);
+ }
}
@Override
- public StringCache getStringCache() {
- return mStringCache;
+ public void bindStringCache(StringCache cache) {
+ mStringCache = cache;
+ mAppsView.updateWorkUI();
}
/**
@@ -3168,6 +3027,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 +3043,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 +3098,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 +3153,7 @@
getStateManager().goToState(LauncherState.NORMAL);
}
- private void closeOpenViews() {
+ public void closeOpenViews() {
closeOpenViews(true);
}
@@ -3346,10 +3161,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 +3169,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 +3206,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 +3239,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 +3461,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..bdd92eb 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -21,6 +21,7 @@
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
import com.android.launcher3.allapps.WorkProfileManager
@@ -59,8 +60,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
@@ -274,6 +279,7 @@
@JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
+ const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
@JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "", true)
@JvmField
val ALL_APPS_OVERVIEW_THRESHOLD =
@@ -283,11 +289,14 @@
@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 =
backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, true)
+ @JvmField
+ val SHOULD_SHOW_SMARTSPACE =
+ backedUpItem("SHOULD_SHOW_SMARTSPACE_KEY", WIDGET_ON_FIRST_SCREEN, true)
@JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", true)
@JvmField
val RESTORE_DEVICE =
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a1a3974..c20d602 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -29,6 +29,7 @@
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
@@ -71,6 +72,7 @@
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
@@ -594,7 +596,8 @@
* Initializes and binds the first page
*/
public void bindAndInitFirstWorkspaceScreen() {
- if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ if (!FeatureFlags.QSB_ON_FIRST_SCREEN
+ || shouldShowFirstPageWidget()) {
return;
}
@@ -1012,7 +1015,9 @@
int id = mWorkspaceScreens.keyAt(i);
CellLayout cl = mWorkspaceScreens.valueAt(i);
// FIRST_SCREEN_ID can never be removed.
- if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
+ if (((!FeatureFlags.QSB_ON_FIRST_SCREEN
+ || shouldShowFirstPageWidget())
+ || id > FIRST_SCREEN_ID)
&& cl.getShortcutsAndWidgets().getChildCount() == 0) {
removeScreens.add(id);
}
@@ -2858,6 +2863,10 @@
view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, mLauncher, cellLayout,
(FolderInfo) info);
break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
+ view = AppPairIcon.inflateIcon(R.layout.app_pair_icon, mLauncher, cellLayout,
+ (FolderInfo) info);
+ break;
default:
throw new IllegalStateException("Unknown item type: " + info.itemType);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 7edbeac..cffddfc 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -16,6 +16,7 @@
package com.android.launcher3.allapps;
import static com.android.launcher3.config.FeatureFlags.ALL_APPS_GONE_VISIBILITY;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import static com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN;
@@ -96,16 +97,17 @@
protected void updatePoolSize() {
DeviceProfile grid = ActivityContext.lookupContext(getContext()).getDeviceProfile();
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
- int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
- // If all apps' hidden visibility is INVISIBLE, we will need to preinflate one page of
- // all apps icons for smooth scrolling.
- int maxPoolSizeForAppIcons = (approxRows + 1) * grid.numShownAllAppsColumns;
- if (ALL_APPS_GONE_VISIBILITY.get()) {
- // If all apps' hidden visibility is GONE, we need to increase prefinated icons number
- // by [PREINFLATE_ICONS_ROW_COUNT] rows + [EXTRA_ICONS_COUNT] for fast opening all apps.
+ // By default the max num of pool size for app icons is num of app icons in one page of
+ // all apps.
+ int maxPoolSizeForAppIcons = grid.getMaxAllAppsRowCount()
+ * grid.numShownAllAppsColumns;
+ if (ALL_APPS_GONE_VISIBILITY.get() && ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
+ // If we set all apps' hidden visibility to GONE and enable pre-inflation, we want to
+ // preinflate one page of all apps icons plus [PREINFLATE_ICONS_ROW_COUNT] rows +
+ // [EXTRA_ICONS_COUNT]. Thus we need to bump the max pool size of app icons accordingly.
maxPoolSizeForAppIcons +=
PREINFLATE_ICONS_ROW_COUNT * grid.numShownAllAppsColumns + EXTRA_ICONS_COUNT;
}
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/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 211bc0d..86d66c4 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -16,6 +16,7 @@
package com.android.launcher3.config;
+import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
import static com.android.launcher3.config.FeatureFlags.FlagState.DISABLED;
import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
@@ -136,7 +137,7 @@
// TODO(Block 6): Clean up flags
public static final BooleanFlag ENABLE_ALL_APPS_SEARCH_IN_TASKBAR = getDebugFlag(270393900,
- "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", TEAMFOOD,
+ "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", ENABLED,
"Enables Search box in Taskbar All Apps.");
public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(270395140,
@@ -151,21 +152,9 @@
// 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");
-
- public static final BooleanFlag LARGE_SCREEN_WIDGET_PICKER = getDebugFlag(270395809,
- "LARGE_SCREEN_WIDGET_PICKER", ENABLED, "Enable new widget picker that takes "
- + "advantage of large screen format");
-
public static final BooleanFlag UNFOLDED_WIDGET_PICKER = getDebugFlag(301918659,
"UNFOLDED_WIDGET_PICKER", DISABLED, "Enable new widget picker that takes "
+ "advantage of the unfolded foldable format");
@@ -177,6 +166,10 @@
public static final BooleanFlag SMARTSPACE_AS_A_WIDGET = getDebugFlag(299181941,
"SMARTSPACE_AS_A_WIDGET", DISABLED, "Enable SmartSpace as a widget");
+ public static boolean shouldShowFirstPageWidget() {
+ return SMARTSPACE_AS_A_WIDGET.get() && WIDGET_ON_FIRST_SCREEN;
+ }
+
// TODO(Block 10): Clean up flags
public static final BooleanFlag ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION = getDebugFlag(270614790,
"ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION", DISABLED,
@@ -190,6 +183,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,
@@ -323,6 +320,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,
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/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index ae44f0a..7ce5c20 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
@@ -526,7 +527,8 @@
}
// Add first page QSB
- if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN
+ && !shouldShowFirstPageWidget()) {
CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen, false);
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(
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/logging/StartupLatencyLogger.kt b/src/com/android/launcher3/logging/StartupLatencyLogger.kt
index 93e9de5..7d7564b 100644
--- a/src/com/android/launcher3/logging/StartupLatencyLogger.kt
+++ b/src/com/android/launcher3/logging/StartupLatencyLogger.kt
@@ -30,6 +30,11 @@
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
var workspaceLoadStartTime: Long = UNSET_LONG
+ // StartupLatencyLogger should only send launcher startup logs once in each launcher activity
+ // lifecycle. After launcher activity startup is completed, the logger should be torn down and
+ // reject all logging calls. This flag should be checked at all APIs to prevent logging invalid
+ // startup metrics (such as loading workspace in screen rotation).
+ var isTornDown = false
private var isInTest = false
/** Subclass can override this method to handle collected latency metrics. */
@@ -45,6 +50,9 @@
@MainThread
fun logWorkspaceLoadStartTime(startTimeMs: Long): StartupLatencyLogger {
Preconditions.assertUIThread()
+ if (isTornDown) {
+ return this
+ }
workspaceLoadStartTime = startTimeMs
return this
}
@@ -56,6 +64,9 @@
@MainThread
fun logCardinality(cardinality: Int): StartupLatencyLogger {
Preconditions.assertUIThread()
+ if (isTornDown) {
+ return this
+ }
this.cardinality = cardinality
return this
}
@@ -67,6 +78,9 @@
fun logStart(event: LauncherLatencyEvent, startTimeMs: Long): StartupLatencyLogger {
// In unit test no looper is attached to current thread
Preconditions.assertUIThread()
+ if (isTornDown) {
+ return this
+ }
if (validateLoggingEventAtStart(event)) {
startTimeByEvent.put(event.id, startTimeMs)
}
@@ -80,6 +94,9 @@
fun logEnd(event: LauncherLatencyEvent, endTimeMs: Long): StartupLatencyLogger {
// In unit test no looper is attached to current thread
Preconditions.assertUIThread()
+ if (isTornDown) {
+ return this
+ }
maybeLogStartOfWorkspaceLoadTime(event)
if (validateLoggingEventAtEnd(event)) {
endTimeByEvent.put(event.id, endTimeMs)
@@ -96,6 +113,7 @@
endTimeByEvent.clear()
cardinality = UNSET_INT
workspaceLoadStartTime = UNSET_LONG
+ isTornDown = true
}
@MainThread
@@ -181,6 +199,15 @@
"Cannot end ${event.name} event after ${LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.name}",
)
return false
+ } else if (
+ latencyType == LatencyType.COLD_DEVICE_REBOOTING &&
+ event == LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC
+ ) {
+ Log.e(
+ TAG,
+ "Cannot have ${LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC.name} in ${LatencyType.COLD_DEVICE_REBOOTING.name} startup type"
+ )
+ return false
}
return true
}
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 68544cf..7421205 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -172,6 +172,11 @@
public abstract void bindWidgets();
/**
+ * bindWidgets is abstract because it is a no-op for the go launcher.
+ */
+ public abstract void bindSmartspaceWidget();
+
+ /**
* Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right)
*/
protected void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile,
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 5e88b9b..0ffedc8 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -17,6 +17,7 @@
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
@@ -152,7 +153,9 @@
screenSet.add(item.screenId);
}
}
- if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) {
+ if ((FeatureFlags.QSB_ON_FIRST_SCREEN
+ && !shouldShowFirstPageWidget())
+ || screenSet.isEmpty()) {
screenSet.add(Workspace.FIRST_SCREEN_ID);
}
return screenSet.getArray();
@@ -505,6 +508,7 @@
default void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
default void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) { }
default void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
+ default void bindSmartspaceWidget() { }
/** Called when workspace has been bound. */
default void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks,
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index ecf5f67..8167b97 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -16,6 +16,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import android.content.ContentValues;
@@ -257,7 +258,8 @@
Favorites.SCREEN, IntArray.wrap(-777, -778)), null);
}
case 30: {
- if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN
+ && !shouldShowFirstPageWidget()) {
// Clean up first row in screen 0 as it might contain junk data.
Log.d(TAG, "Cleaning up first row");
db.delete(Favorites.TABLE_NAME,
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index 78875a3..eed4ee6 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
@@ -327,7 +328,9 @@
@NonNull final List<DbEntry> sortedItemsToPlace, final boolean matchingScreenIdOnly) {
final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
final Point trg = new Point(trgX, trgY);
- final Point next = new Point(0, screenId == 0 && FeatureFlags.QSB_ON_FIRST_SCREEN
+ final Point next = new Point(0, screenId == 0
+ && (FeatureFlags.QSB_ON_FIRST_SCREEN
+ && !shouldShowFirstPageWidget())
? 1 /* smartspace */ : 0);
List<DbEntry> existedEntries = destReader.mWorkspaceEntriesByScreenId.get(screenId);
if (existedEntries != null) {
@@ -465,6 +468,13 @@
}
break;
}
+ case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
+ int total = getFolderItemsCount(entry);
+ if (total != 2) {
+ throw new Exception("App pair contains fewer or more than 2 items");
+ }
+ break;
+ }
default:
throw new Exception("Invalid item type");
}
@@ -562,6 +572,13 @@
}
break;
}
+ case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
+ int total = getFolderItemsCount(entry);
+ if (total != 2) {
+ throw new Exception("App pair contains fewer or more than 2 items");
+ }
+ break;
+ }
default:
throw new Exception("Invalid item type");
}
@@ -679,6 +696,7 @@
public String getEntryMigrationId() {
switch (itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
return getFolderMigrationId();
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
return mProvider;
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 33332f0..9c4cd46 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -17,6 +17,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -528,7 +529,8 @@
if (!mOccupied.containsKey(item.screenId)) {
GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
- if (item.screenId == Workspace.FIRST_SCREEN_ID && FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ if (item.screenId == Workspace.FIRST_SCREEN_ID && (FeatureFlags.QSB_ON_FIRST_SCREEN
+ && !shouldShowFirstPageWidget())) {
// Mark the first X columns (X is width of the search container) in the first row as
// occupied (if the feature is enabled) in order to account for the search
// container.
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 08a6cf0..6ba6642 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,6 +16,7 @@
package com.android.launcher3.model;
+import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -57,6 +58,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
@@ -296,6 +298,19 @@
logASplit("bindWidgets");
verifyNotStopped();
+ if (LauncherPrefs.get(mApp.getContext()).get(LauncherPrefs.SHOULD_SHOW_SMARTSPACE)) {
+ mLauncherBinder.bindSmartspaceWidget();
+ // Turn off pref.
+ LauncherPrefs.get(mApp.getContext()).putSync(
+ LauncherPrefs.backedUpItem(
+ LauncherPrefs.SHOULD_SHOW_SMARTSPACE_KEY,
+ WIDGET_ON_FIRST_SCREEN,
+ true)
+ .to(false));
+ logASplit("bindSmartspaceWidget");
+ verifyNotStopped();
+ }
+
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
logASplit("otherDelegateItems");
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/model/WorkspaceItemSpaceFinder.java b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
index 1fc8a03..929f698 100644
--- a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
+++ b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
@@ -16,6 +16,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import android.util.LongSparseArray;
@@ -66,7 +67,8 @@
int screenCount = workspaceScreens.size();
// First check the preferred screen.
IntSet screensToExclude = new IntSet();
- if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN
+ && !shouldShowFirstPageWidget()) {
screensToExclude.add(FIRST_SCREEN_ID);
}
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 1e3be27..f0f376f 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -20,6 +20,8 @@
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_PROVIDER;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
+
import android.app.Activity;
import android.app.Fragment;
import android.app.SearchManager;
@@ -290,7 +292,7 @@
}
public boolean isQsbEnabled() {
- return FeatureFlags.QSB_ON_FIRST_SCREEN;
+ return FeatureFlags.QSB_ON_FIRST_SCREEN && !shouldShowFirstPageWidget();
}
protected Bundle createBindOptions() {
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index 3c59c1d..1d71805 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -93,9 +93,7 @@
EXTRA_ICONS_COUNT
if (FeatureFlags.ALL_APPS_GONE_VISIBILITY.get()) {
val grid = ActivityContext.lookupContext<T>(context).deviceProfile
- val approxRows =
- Math.ceil((grid.availableHeightPx / grid.allAppsIconSizePx).toDouble()).toInt()
- targetPreinflateCount += (approxRows + 1) * grid.numShownAllAppsColumns
+ targetPreinflateCount += grid.maxAllAppsRowCount * grid.numShownAllAppsColumns
}
val existingPreinflateCount = getRecycledViewCount(BaseAllAppsAdapter.VIEW_TYPE_ICON)
return targetPreinflateCount - existingPreinflateCount
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/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index dcc86a1..742d2dc 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -16,7 +16,6 @@
package com.android.launcher3.widget;
import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER;
import android.content.Context;
import android.graphics.Canvas;
@@ -194,9 +193,7 @@
int widthUsed;
if (deviceProfile.isTablet) {
int margin = deviceProfile.allAppsLeftRightMargin;
- if (deviceProfile.isLandscape
- && LARGE_SCREEN_WIDGET_PICKER.get()
- && !deviceProfile.isTwoPanels) {
+ if (deviceProfile.isLandscape && !deviceProfile.isTwoPanels) {
margin = getResources().getDimensionPixelSize(
R.dimen.widget_picker_landscape_tablet_left_right_margin);
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 4105a9a..2c094f2 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -18,7 +18,6 @@
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -679,8 +678,7 @@
/** Shows the {@link WidgetsFullSheet} on the launcher. */
public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
- boolean isTwoPane = LARGE_SCREEN_WIDGET_PICKER.get()
- && launcher.getDeviceProfile().isTablet
+ boolean isTwoPane = launcher.getDeviceProfile().isTablet
&& launcher.getDeviceProfile().isLandscape
&& (!launcher.getDeviceProfile().isTwoPanels
|| FeatureFlags.UNFOLDED_WIDGET_PICKER.get());
@@ -798,8 +796,7 @@
// Checks the orientation of the screen
if (mOrientation != newConfig.orientation) {
mOrientation = newConfig.orientation;
- if (LARGE_SCREEN_WIDGET_PICKER.get()
- && mDeviceProfile.isTablet && !mDeviceProfile.isTwoPanels) {
+ if (mDeviceProfile.isTablet && !mDeviceProfile.isTwoPanels) {
handleClose(false);
show(Launcher.getLauncher(getContext()), false);
} else {
diff --git a/src_build_config/com/android/launcher3/BuildConfig.java b/src_build_config/com/android/launcher3/BuildConfig.java
index 1f2e0e5..3841969 100644
--- a/src_build_config/com/android/launcher3/BuildConfig.java
+++ b/src_build_config/com/android/launcher3/BuildConfig.java
@@ -27,6 +27,11 @@
public static final boolean QSB_ON_FIRST_SCREEN = true;
/**
+ * Flag to state if the widget on the top of the first screen should be shown.
+ */
+ public static final boolean WIDGET_ON_FIRST_SCREEN = false;
+
+ /**
* Flag to control various developer centric features
*/
public static final boolean IS_DEBUG_DEVICE = false;
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java b/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
index e1a5f24..7e73ab5 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
@@ -51,4 +51,9 @@
mBgDataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
}
+
+ @Override
+ public void bindSmartspaceWidget() {
+ executeCallbacksTask(c -> c.bindSmartspaceWidget(), mUiExecutor);
+ }
}
diff --git a/tests/Android.bp b/tests/Android.bp
index e1b97de..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",
@@ -57,6 +59,7 @@
"src/com/android/launcher3/util/rule/SamplerRule.java",
"src/com/android/launcher3/util/rule/ScreenRecordRule.java",
"src/com/android/launcher3/util/rule/ShellCommandRule.java",
+ "src/com/android/launcher3/util/rule/TestIsolationRule.java",
"src/com/android/launcher3/util/rule/TestStabilityRule.java",
"src/com/android/launcher3/util/rule/TISBindRule.java",
"src/com/android/launcher3/util/viewcapture_analysis/*.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/logging/StartupLatencyLoggerTest.kt b/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
index fffa6d7..a29218c 100644
--- a/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
+++ b/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
@@ -357,6 +357,7 @@
assertThat(underTest.startTimeByEvent.size()).isEqualTo(4)
assertThat(underTest.endTimeByEvent.size()).isEqualTo(4)
assertThat(underTest.cardinality).isEqualTo(235)
+ assertThat(underTest.isTornDown).isFalse()
underTest.reset()
@@ -364,5 +365,26 @@
assertThat(underTest.endTimeByEvent.isEmpty()).isTrue()
assertThat(underTest.cardinality).isEqualTo(StartupLatencyLogger.UNSET_INT)
assertThat(underTest.workspaceLoadStartTime).isEqualTo(StartupLatencyLogger.UNSET_LONG)
+ assertThat(underTest.isTornDown).isTrue()
+ }
+
+ @Test
+ @UiThreadTest
+ fun tornDown_rejectLogs() {
+ underTest.reset()
+
+ underTest
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+ .logEnd(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 200
+ )
+ .logCardinality(123)
+ assertThat(underTest.startTimeByEvent.isEmpty()).isTrue()
+ assertThat(underTest.endTimeByEvent.isEmpty()).isTrue()
+ assertThat(underTest.cardinality).isEqualTo(StartupLatencyLogger.UNSET_INT)
}
}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 3e5d717..e837b8b 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -18,7 +18,6 @@
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;
@@ -68,6 +67,7 @@
import com.android.launcher3.util.rule.SamplerRule;
import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.util.rule.TestIsolationRule;
import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.launcher3.util.rule.ViewCaptureRule;
@@ -111,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);
}
@@ -127,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;
@@ -152,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 +220,8 @@
final RuleChain inner = RuleChain
.outerRule(new PortraitLandscapeRunner(this))
.around(new FailureWatcher(mLauncher, viewCaptureRule::getViewCaptureData))
- .around(viewCaptureRule);
+ .around(viewCaptureRule)
+ .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 9eac08d..6c2950c 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -99,6 +99,7 @@
MockitoAnnotations.initMocks(this);
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);
@@ -338,7 +339,7 @@
mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
mBubbleTextView.applyLabel(mItemInfoWithIcon);
mBubbleTextView.setTypeface(Typeface.MONOSPACE);
- mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+ mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
mBubbleTextView.onPreDraw();
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 5417e3f..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;
@@ -89,7 +78,6 @@
public static void initialize(
AbstractLauncherUiTest test, boolean clearWorkspace) throws Exception {
test.reinitializeLauncherData(clearWorkspace);
- test.mLauncher.resetFreezeRecentTaskList();
test.mDevice.pressHome();
test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
test.waitForState("Launcher internal state didn't switch to Home",
@@ -97,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
@@ -126,10 +114,6 @@
return launcher.getWorkspace().getCurrentPage();
}
- private WidgetsRecyclerView getWidgetsView(Launcher launcher) {
- return WidgetsFullSheet.getWidgetsView(launcher);
- }
-
@Test
public void testDevicePressMenu() throws Exception {
mDevice.pressMenu();
@@ -212,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/rule/TestIsolationRule.java b/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java
new file mode 100644
index 0000000..2b45902
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java
@@ -0,0 +1,55 @@
+/*
+ * 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.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;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Isolates tests from some of the state created by the previous test.
+ */
+public class TestIsolationRule implements TestRule {
+ private final LauncherInstrumentation mLauncher;
+ private final boolean mRequireOneActiveActivity;
+
+ public TestIsolationRule(LauncherInstrumentation launcher, boolean requireOneActiveActivity) {
+ mLauncher = launcher;
+ mRequireOneActiveActivity = requireOneActiveActivity;
+ }
+
+ @NonNull
+ @Override
+ public Statement apply(@NonNull Statement base, @NonNull Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ base.evaluate();
+ // Make sure that Launcher workspace looks correct.
+
+ 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 b7d2530..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,18 +763,7 @@
return isTablet() ? getLauncherPackageName() : SYSTEMUI_PACKAGE;
}
- /**
- * Resets the frozen recent tasks list if necessary from a previous quickswitch.
- */
- public void resetFreezeRecentTaskList() {
- try {
- mDevice.executeShellCommand("wm reset-freeze-recent-tasks");
- } catch (IOException e) {
- Log.e(TAG, "Failed to reset fozen recent tasks list", e);
- }
- }
-
- private UiObject2 verifyContainerType(ContainerType containerType) {
+ UiObject2 verifyContainerType(ContainerType containerType) {
waitForLauncherInitialized();
if (mExpectedRotationCheckEnabled && mExpectedRotation != null) {
@@ -795,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);
@@ -809,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);
@@ -824,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);
}
@@ -832,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);
@@ -852,6 +853,7 @@
waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
}
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
return waitForSystemLauncherObject(OVERVIEW_RES_ID);
}
@@ -866,6 +868,7 @@
}
waitForSystemLauncherObject(SPLIT_PLACEHOLDER_RES_ID);
+ waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
return waitForSystemLauncherObject(OVERVIEW_RES_ID);
}
case LAUNCHED_APP: {
@@ -874,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;
@@ -992,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.
@@ -1181,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));
}
@@ -1313,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);
@@ -1746,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();
@@ -1776,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,
@@ -1977,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);
@@ -1991,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() {
@@ -2136,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 {
/**