Merge "Change Taskbar Window height logic for taskbar pinning." into main
diff --git a/OWNERS b/OWNERS
index 7834396..353ac8e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -15,6 +15,7 @@
alexchau@google.com
patmanning@google.com
tsuharesu@google.com
+awickham@google.com
per-file FeatureFlags.java, globs = set noparent
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
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 ce644dc..cca0fd4 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -56,6 +56,7 @@
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
+import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -78,6 +79,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
@@ -92,6 +94,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.provider.Settings.Global;
import android.util.Pair;
import android.util.Size;
import android.view.CrossWindowBlurListeners;
@@ -231,6 +234,16 @@
private final StartingWindowListener mStartingWindowListener =
new StartingWindowListener(this);
+ private ContentObserver mAnimationRemovalObserver = new ContentObserver(
+ ORDERED_BG_EXECUTOR.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mAreAnimationsEnabled = Global.getFloat(mLauncher.getContentResolver(),
+ Global.ANIMATOR_DURATION_SCALE, 1f) > 0
+ || Global.getFloat(mLauncher.getContentResolver(),
+ Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
+ }
+ };;
private DeviceProfile mDeviceProfile;
@@ -260,6 +273,7 @@
// Pairs of window starting type and starting window background color for starting tasks
// Will never be larger than MAX_NUM_TASKS
private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
+ private boolean mAreAnimationsEnabled = true;
private final Interpolator mOpeningXInterpolator;
private final Interpolator mOpeningInterpolator;
@@ -270,6 +284,7 @@
mHandler = new Handler(Looper.getMainLooper());
mDeviceProfile = mLauncher.getDeviceProfile();
mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
+ checkAndMonitorIfAnimationsAreEnabled();
Resources res = mLauncher.getResources();
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
@@ -1160,6 +1175,8 @@
unregisterRemoteAnimations();
unregisterRemoteTransitions();
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
+ ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
+ .unregisterContentObserver(mAnimationRemovalObserver));
}
private void unregisterRemoteAnimations() {
@@ -1197,6 +1214,17 @@
}
}
+ private void checkAndMonitorIfAnimationsAreEnabled() {
+ ORDERED_BG_EXECUTOR.execute(() -> {
+ mAnimationRemovalObserver.onChange(true);
+ mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
+ Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver);
+ mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
+ Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver);
+
+ });
+ }
+
private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) {
for (RemoteAnimationTarget target : targets) {
if (target.mode == mode && target.taskInfo != null
@@ -1375,7 +1403,7 @@
(LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
isTransluscent, fallbackBackgroundColor);
- } else if (launcherView != null) {
+ } else if (launcherView != null && mAreAnimationsEnabled) {
floatingIconView = getFloatingIconView(mLauncher, launcherView, null,
mLauncher.getTaskbarUIController() == null
? null
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/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index 42e6809..00a282a 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -15,8 +15,10 @@
*/
package com.android.launcher3.statehandlers;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import android.os.Debug;
import android.os.SystemProperties;
import android.util.Log;
import android.view.View;
@@ -46,6 +48,7 @@
private boolean mFreeformTasksVisible;
private boolean mInOverviewState;
+ private boolean mBackgroundStateEnabled;
private boolean mGestureInProgress;
@Nullable
@@ -113,7 +116,11 @@
* Whether freeform windows are visible in desktop mode.
*/
public boolean areFreeformTasksVisible() {
- return mFreeformTasksVisible;
+ if (DEBUG) {
+ Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + mFreeformTasksVisible
+ + " overview=" + mInOverviewState);
+ }
+ return mFreeformTasksVisible && !mInOverviewState;
}
/**
@@ -121,7 +128,8 @@
*/
public void setFreeformTasksVisible(boolean freeformTasksVisible) {
if (DEBUG) {
- Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible);
+ Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible
+ + " currentValue=" + mFreeformTasksVisible);
}
if (!isDesktopModeSupported()) {
return;
@@ -146,11 +154,21 @@
}
/**
- * Sets whether the overview is visible and updates launcher visibility based on that.
+ * Process launcher state change and update launcher view visibility based on desktop state
*/
- public void setOverviewStateEnabled(boolean overviewStateEnabled) {
+ public void onLauncherStateChanged(LauncherState state) {
if (DEBUG) {
- Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled);
+ Log.d(TAG, "onLauncherStateChanged: newState=" + state);
+ }
+ setBackgroundStateEnabled(state == BACKGROUND_APP);
+ // Desktop visibility tracks overview and background state separately
+ setOverviewStateEnabled(state != BACKGROUND_APP && state.overviewUi);
+ }
+
+ private void setOverviewStateEnabled(boolean overviewStateEnabled) {
+ if (DEBUG) {
+ Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled
+ + " currentValue=" + mInOverviewState);
}
if (!isDesktopModeSupported()) {
return;
@@ -160,7 +178,7 @@
if (mInOverviewState) {
setLauncherViewsVisibility(View.VISIBLE);
markLauncherResumed();
- } else if (mFreeformTasksVisible && !mGestureInProgress) {
+ } else if (areFreeformTasksVisible() && !mGestureInProgress) {
// Switching out of overview state and gesture finished.
// If freeform tasks are still visible, hide launcher again.
setLauncherViewsVisibility(View.INVISIBLE);
@@ -169,6 +187,27 @@
}
}
+ private void setBackgroundStateEnabled(boolean backgroundStateEnabled) {
+ if (DEBUG) {
+ Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled
+ + " currentValue=" + mBackgroundStateEnabled);
+ }
+ if (!isDesktopModeSupported()) {
+ return;
+ }
+ if (backgroundStateEnabled != mBackgroundStateEnabled) {
+ mBackgroundStateEnabled = backgroundStateEnabled;
+ if (mBackgroundStateEnabled) {
+ setLauncherViewsVisibility(View.VISIBLE);
+ markLauncherResumed();
+ } else if (areFreeformTasksVisible() && !mGestureInProgress) {
+ // Switching out of background state. If freeform tasks are visible, pause launcher.
+ setLauncherViewsVisibility(View.INVISIBLE);
+ markLauncherPaused();
+ }
+ }
+ }
+
/**
* Whether recents gesture is currently in progress.
*/
@@ -183,6 +222,9 @@
if (!isDesktopModeSupported()) {
return;
}
+ if (DEBUG) {
+ Log.d(TAG, "setRecentsGestureStart");
+ }
setRecentsGestureInProgress(true);
}
@@ -194,6 +236,9 @@
if (!isDesktopModeSupported()) {
return;
}
+ if (DEBUG) {
+ Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget);
+ }
setRecentsGestureInProgress(false);
if (endTarget == null) {
@@ -203,9 +248,6 @@
}
private void setRecentsGestureInProgress(boolean gestureInProgress) {
- if (DEBUG) {
- Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
- }
if (gestureInProgress != mGestureInProgress) {
mGestureInProgress = gestureInProgress;
}
@@ -222,7 +264,8 @@
private void setLauncherViewsVisibility(int visibility) {
if (DEBUG) {
- Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility);
+ Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " "
+ + Debug.getCaller());
}
View workspaceView = mLauncher.getWorkspace();
if (workspaceView != null) {
@@ -236,7 +279,7 @@
private void markLauncherPaused() {
if (DEBUG) {
- Log.d(TAG, "markLauncherPaused");
+ Log.d(TAG, "markLauncherPaused " + Debug.getCaller());
}
StatefulActivity<LauncherState> activity =
QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
@@ -247,7 +290,7 @@
private void markLauncherResumed() {
if (DEBUG) {
- Log.d(TAG, "markLauncherResumed");
+ Log.d(TAG, "markLauncherResumed " + Debug.getCaller());
}
StatefulActivity<LauncherState> activity =
QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
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 3125076..d4faf47 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -219,7 +219,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/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 4ad5c88..6ddf9e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -21,6 +21,8 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
+import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
+import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -64,6 +66,7 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -626,7 +629,9 @@
if (tag instanceof ItemInfo) {
ItemInfo item = (ItemInfo) tag;
- if (item.container == CONTAINER_ALL_APPS || item.container == CONTAINER_PREDICTION) {
+ if (item.container == CONTAINER_ALL_APPS
+ || item.container == CONTAINER_PREDICTION
+ || isInSearchResultContainer(item)) {
if (mDisallowGlobalDrag) {
// We're dragging in taskbarAllApps, we don't have folders or shortcuts
return iconView;
@@ -648,6 +653,13 @@
return iconView;
}
+ private static boolean isInSearchResultContainer(ItemInfo item) {
+ ContainerInfo containerInfo = item.getContainerInfo();
+ return containerInfo.getContainerCase() == EXTENDED_CONTAINERS
+ && containerInfo.getExtendedContainers().getContainerCase()
+ == DEVICE_SEARCH_RESULT_CONTAINER;
+ }
+
private void setupReturnDragAnimator(float fromX, float fromY, View originalView,
TaskbarReturnPropertiesListener animListener) {
// Finish any pending return animation before starting a new return
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 5182a32..07d86e4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -158,10 +158,6 @@
if (mAppsView != null) {
return;
}
- // mControllers and getSharedState should never be null here. Do not handle null-pointer
- // to catch invalid states.
- mControllers.getSharedState().allAppsVisible = true;
-
mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
// Initialize search session for All Apps.
@@ -178,10 +174,7 @@
// Ensures All Apps gets touch events in case it is not the top floating view. Floating
// views above it may not be able to intercept the touch, so All Apps should try to.
mOverlayContext.getDragLayer().addTouchController(mSlideInView);
- mSlideInView.addOnCloseListener(() -> {
- mControllers.getSharedState().allAppsVisible = false;
- cleanUpOverlay();
- });
+ mSlideInView.addOnCloseListener(this::cleanUpOverlay);
TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
mOverlayContext,
mSlideInView,
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index 6d740c0..cf5fd59 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -18,17 +18,22 @@
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_TASKBAR_ALL_APPS;
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.allapps.AllAppsTransitionListener;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.appprediction.AppsDividerView;
import com.android.launcher3.taskbar.NavbarButtonsViewController;
import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarSharedState;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
import com.android.launcher3.util.DisplayController;
+import java.util.Optional;
+
/**
* Handles the {@link TaskbarAllAppsContainerView} behavior and synchronizes its transitions with
* taskbar stashing.
@@ -41,6 +46,7 @@
private final TaskbarStashController mTaskbarStashController;
private final NavbarButtonsViewController mNavbarButtonsViewController;
private final TaskbarOverlayController mOverlayController;
+ private final @Nullable TaskbarSharedState mTaskbarSharedState;
private final boolean mShowKeyboard;
TaskbarAllAppsViewController(
@@ -56,6 +62,7 @@
mTaskbarStashController = taskbarControllers.taskbarStashController;
mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
mOverlayController = taskbarControllers.taskbarOverlayController;
+ mTaskbarSharedState = taskbarControllers.getSharedState();
mShowKeyboard = showKeyboard;
mSlideInView.init(new TaskbarAllAppsCallbacks(searchSessionController));
@@ -87,8 +94,10 @@
mTaskbarStashController.applyState();
}
+ Optional.ofNullable(mTaskbarSharedState).ifPresent(s -> s.allAppsVisible = true);
mNavbarButtonsViewController.setSlideInViewVisible(true);
mSlideInView.setOnCloseBeginListener(() -> {
+ Optional.ofNullable(mTaskbarSharedState).ifPresent(s -> s.allAppsVisible = false);
mNavbarButtonsViewController.setSlideInViewVisible(false);
AbstractFloatingView.closeOpenContainer(
mContext, AbstractFloatingView.TYPE_ACTION_POPUP);
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..7529508 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..e7847f3 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,30 @@
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()
+ }
+ if (a11yButton != null) {
+ 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..c502cdb 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..d6ea7f0 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..54edd21 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,26 @@
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()
+ }
+ if (a11yButton != null) {
+ 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..34df282 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,25 @@
}
}
}
+
+ 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()
+ }
+ if (a11yButton != null) {
+ 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..cfe1276 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,26 @@
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()
+ }
+ if (a11yButton != null) {
+ 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..36f5150 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,30 @@
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()
+ }
+ if (a11yButton != null) {
+ 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..58dce69 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -20,22 +20,33 @@
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 */
+/**
+ * Layoutter for rendering task bar in large screen. {@param isContextualButtonShowing} is true in
+ * 3-button mode.
+ */
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 +88,32 @@
}
}
}
+
+ endContextualContainer.removeAllViews()
+ startContextualContainer.removeAllViews()
+
+ if (isContextualButtonShowing) {
+ 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()
+ }
+ if (a11yButton != null) {
+ endContextualContainer.addView(a11yButton)
+ }
+ if (rotationButton != null) {
+ endContextualContainer.addView(rotationButton.currentView)
+ rotationButton.currentView.layoutParams = getParamsToCenterView()
+ }
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 1b22c84..289041a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -186,6 +186,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -218,8 +219,6 @@
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
private SplitToWorkspaceController mSplitToWorkspaceController;
- private AsyncClockEventDelegate mAsyncClockEventDelegate;
-
/**
* If Launcher restarted while in the middle of an Overview split select, it needs this data to
* recover. In all other cases this will remain null.
@@ -497,10 +496,6 @@
mSplitSelectStateController.onDestroy();
}
- if (mAsyncClockEventDelegate != null) {
- mAsyncClockEventDelegate.onDestroy();
- }
-
super.onDestroy();
mHotseatPredictionController.destroy();
mSplitWithKeyboardShortcutController.onDestroy();
@@ -675,6 +670,9 @@
floatingTaskView.setAlpha(1);
floatingTaskView.addStagingAnimation(anim, startingTaskRect, tempRect,
false /* fadeWithThumbnail */, true /* isStagedTask */);
+ floatingTaskView.setOnClickListener(view ->
+ mSplitSelectStateController.getSplitAnimationController().
+ playAnimPlaceholderToFullscreen(this, view, Optional.empty()));
mSplitSelectStateController.setFirstFloatingTaskView(floatingTaskView);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -714,7 +712,8 @@
if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
// If Launcher pauses before both split apps are selected, exit split screen.
- if (!mSplitSelectStateController.isBothSplitAppsConfirmed()) {
+ if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
+ !mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
mSplitSelectStateController.getSplitAnimationController()
.playPlaceholderDismissAnim(this);
}
@@ -1346,18 +1345,12 @@
switch (name) {
case "TextClock", "android.widget.TextClock" -> {
TextClock tc = new TextClock(context, attrs);
- if (mAsyncClockEventDelegate == null) {
- mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
- }
- tc.setClockEventDelegate(mAsyncClockEventDelegate);
+ tc.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
return tc;
}
case "AnalogClock", "android.widget.AnalogClock" -> {
AnalogClock ac = new AnalogClock(context, attrs);
- if (mAsyncClockEventDelegate == null) {
- mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
- }
- ac.setClockEventDelegate(mAsyncClockEventDelegate);
+ ac.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
return ac;
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index a68e753..6279f63 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -116,8 +116,9 @@
boolean defaultValue = DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValueInCode);
if (IS_DEBUG_DEVICE) {
boolean currentValue = getSharedPreferences().getBoolean(key, defaultValue);
- DebugFlag flag = new DeviceFlag(key, description, flagState, currentValue,
- defaultValueInCode);
+ DebugFlag flag = new DeviceFlag(key, description,
+ (defaultValue == defaultValueInCode) ? flagState
+ : defaultValue ? ENABLED : DISABLED, currentValue, defaultValueInCode);
sDebugFlags.add(flag);
return flag;
} else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 3e7d45e..1d55da3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -21,9 +21,9 @@
import android.graphics.Rect;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.views.RecentsView;
/**
@@ -72,7 +72,7 @@
@Override
public boolean isTaskbarStashed(Launcher launcher) {
- if (FeatureFlags.enableGridOnlyOverview()) {
+ if (Flags.enableGridOnlyOverview()) {
return true;
}
return super.isTaskbarStashed(launcher);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index e788cc4..4dfa81d 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -2084,18 +2084,9 @@
}
private void finishCurrentTransitionToRecents() {
- if (mRecentsView != null
- && mActivityInterface.getDesktopVisibilityController() != null
- && mActivityInterface.getDesktopVisibilityController().areFreeformTasksVisible()) {
- mRecentsView.switchToScreenshot(() -> {
- mRecentsView.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
- () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
- });
- } else {
- mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
- if (mRecentsAnimationController != null) {
- mRecentsAnimationController.detachNavigationBarFromApp(true);
- }
+ mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ if (mRecentsAnimationController != null) {
+ mRecentsAnimationController.detachNavigationBarFromApp(true);
}
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 8925bd6..25389c5 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -50,10 +50,10 @@
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
@@ -242,7 +242,7 @@
public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
PagedOrientationHandler orientedState) {
if (dp.isTablet) {
- if (FeatureFlags.enableGridOnlyOverview()) {
+ if (Flags.enableGridOnlyOverview()) {
calculateGridTaskSize(context, dp, outRect, orientedState);
} else {
calculateFocusTaskSize(context, dp, outRect);
@@ -339,7 +339,7 @@
PagedOrientationHandler orientedState) {
Resources res = context.getResources();
Rect potentialTaskRect = new Rect();
- if (FeatureFlags.enableGridOnlyOverview()) {
+ if (Flags.enableGridOnlyOverview()) {
calculateGridSize(dp, potentialTaskRect);
} else {
calculateFocusTaskSize(context, dp, potentialTaskRect);
@@ -371,7 +371,7 @@
public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect,
PagedOrientationHandler orientedState) {
calculateTaskSize(context, dp, outRect, orientedState);
- boolean isGridOnlyOverview = dp.isTablet && FeatureFlags.enableGridOnlyOverview();
+ boolean isGridOnlyOverview = dp.isTablet && Flags.enableGridOnlyOverview();
int claimedSpaceBelow = isGridOnlyOverview
? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight
: (dp.heightPx - outRect.bottom - dp.getInsets().bottom);
diff --git a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt
index 35404a9..2fc4d04 100644
--- a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt
+++ b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt
@@ -19,7 +19,7 @@
import android.util.Log
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherPrefs
-import com.android.launcher3.isBootAwareStartupDataEnabled
+import com.android.launcher3.moveStartupDataToDeviceProtectedStorageIsEnabled
import com.android.launcher3.util.LockedUserState
/**
@@ -33,7 +33,8 @@
fun start(context: Context) {
val lp = LauncherPrefs.get(context)
when {
- LockedUserState.get(context).isUserUnlocked || !isBootAwareStartupDataEnabled -> {
+ LockedUserState.get(context).isUserUnlocked ||
+ !moveStartupDataToDeviceProtectedStorageIsEnabled -> {
/* No-Op */
}
lp.isStartupDataMigrated -> {
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 23def14..f898e2f 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -126,4 +126,14 @@
}
return name.toString();
}
+
+ /**
+ * Returns an input consumer of the given class type, or null if none is found.
+ */
+ default <T extends InputConsumer> T getInputConsumerOfClass(Class<T> c) {
+ if (getClass().equals(c)) {
+ return c.cast(this);
+ }
+ return null;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 9be9294..221ce48 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -161,6 +161,10 @@
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
SystemUiProxy.INSTANCE.get(mContext).isDragAndDropReady());
return response;
+
+ case TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET:
+ runOnTISBinder(TouchInteractionService.TISBinder::refreshOverviewTarget);
+ return response;
}
return super.call(method, arg, extras);
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 6ee2cfd..3bc77ff 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -16,6 +16,8 @@
package com.android.quickstep;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
@@ -269,6 +271,7 @@
TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
+ int numVisibleTasks = 0;
for (GroupedRecentTaskInfo rawTask : rawTasks) {
if (DESKTOP_MODE_SUPPORTED && rawTask.getType() == TYPE_FREEFORM) {
GroupTask desktopTask = createDesktopTask(rawTask);
@@ -285,12 +288,27 @@
task1.setLastSnapshotData(taskInfo1);
Task task2 = null;
if (taskInfo2 != null) {
+ // Is split task
Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
task2 = loadKeysOnly
? new Task(task2Key)
: Task.from(task2Key, taskInfo2,
tmpLockedUsers.get(task2Key.userId) /* isLocked */);
task2.setLastSnapshotData(taskInfo2);
+ } else {
+ // Is fullscreen task
+ if (numVisibleTasks > 0) {
+ boolean isExcluded = (taskInfo1.baseIntent.getFlags()
+ & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+ if (taskInfo1.isTopActivityTransparent && isExcluded) {
+ // If there are already visible tasks, then ignore the excluded tasks and
+ // don't add them to the returned list
+ continue;
+ }
+ }
+ }
+ if (taskInfo1.isVisible) {
+ numVisibleTasks++;
}
final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 3d332c2..5d26ec0 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -115,7 +115,8 @@
/* extras= */ 0,
/* gestureEvent= */ ON_START_RECENTS_ANIMATION);
notifyAnimationCanceled();
- animationController.finish(false /* toHome */, false /* sendUserLeaveHint */);
+ animationController.finish(false /* toHome */, false /* sendUserLeaveHint */,
+ null /* finishCb */);
return;
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 8972dc8..341e18c 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -21,6 +21,7 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
import android.content.Context;
+import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRecentsAnimationController;
@@ -32,6 +33,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
+import com.android.internal.os.IResultReceiver;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.util.ActiveGestureErrorDetector;
@@ -172,12 +174,19 @@
mFinishTargetIsLauncher = toRecents;
mOnFinishedListener.accept(this);
Runnable finishCb = () -> {
- mController.finish(toRecents, sendUserLeaveHint);
+ mController.finish(toRecents, sendUserLeaveHint, new IResultReceiver.Stub() {
+ @Override
+ public void send(int i, Bundle bundle) throws RemoteException {
+ ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation-callback");
+ MAIN_EXECUTOR.execute(() -> {
+ mPendingFinishCallbacks.executeAllAndDestroy();
+ });
+ }
+ });
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
InteractionJankMonitorWrapper.end(
InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
- MAIN_EXECUTOR.execute(mPendingFinishCallbacks::executeAllAndDestroy);
};
if (forceFinish) {
finishCb.run();
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 36a6eb6..e358a8e 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -17,7 +17,7 @@
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
-import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 419824a..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;
@@ -799,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 {
@@ -813,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 {
@@ -828,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 {
@@ -842,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,
@@ -858,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,
@@ -873,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,
@@ -888,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,
@@ -908,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,
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index f5a7ecc..4b47209 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -42,7 +42,6 @@
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -119,7 +118,8 @@
}
}
// But force-finish it anyways
- finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */);
+ finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */,
+ null /* forceFinishCb */);
if (mCallbacks != null) {
// If mCallbacks still != null, that means we are getting this startRecentsAnimation()
@@ -261,12 +261,6 @@
// to let the transition controller collect Home activity.
CachedTaskInfo cti = gestureState.getRunningTask();
boolean homeIsOnTop = cti != null && cti.isHomeTask();
- if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
- if (cti != null && cti.isFreeformTask()) {
- // No transient launch when desktop task is on top
- homeIsOnTop = true;
- }
- }
if (activityInterface.allowAllAppsFromOverview()) {
homeIsOnTop = true;
}
@@ -325,19 +319,20 @@
* Finishes the running recents animation.
*/
public void finishRunningRecentsAnimation(boolean toHome) {
- finishRunningRecentsAnimation(toHome, false /* forceFinish */);
+ finishRunningRecentsAnimation(toHome, false /* forceFinish */, null /* forceFinishCb */);
}
/**
* Finishes the running recents animation.
* @param forceFinish will synchronously finish the controller
*/
- public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
+ public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish,
+ Runnable forceFinishCb) {
if (mController != null) {
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "finishRunningRecentsAnimation", toHome);
if (forceFinish) {
- mController.finishController(toHome, null, false /* sendUserLeaveHint */,
+ mController.finishController(toHome, forceFinishCb, false /* sendUserLeaveHint */,
true /* forceFinish */);
} else {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index bd3ccb7..7d03d77 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -34,8 +34,8 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
@@ -80,7 +80,7 @@
boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
boolean isTablet = activity.getDeviceProfile().isTablet;
- boolean isGridOnlyOverview = isTablet && FeatureFlags.enableGridOnlyOverview();
+ boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
// Add overview actions to the menu when in in-place rotate landscape mode, or in
// grid-only overview.
if ((!canLauncherRotate && isInLandscape) || isGridOnlyOverview) {
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/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 2ca9f99..e5fca4b 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -15,7 +15,7 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
import android.content.Context;
import android.content.res.Resources;
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 01baed3..f1af2ed 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -16,7 +16,6 @@
package com.android.quickstep;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -229,12 +228,21 @@
}
/**
- * Returns true if the given task holds an Assistant activity that is excluded from recents
+ * If the given task holds an activity that is excluded from recents, and there
+ * is another running task that is not excluded from recents, returns that underlying task.
*/
- public boolean isExcludedAssistant() {
- return mTopTask != null && mTopTask.configuration.windowConfiguration
- .getActivityType() == ACTIVITY_TYPE_ASSISTANT
- && (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+ public @Nullable CachedTaskInfo otherVisibleTaskThisIsExcludedOver() {
+ if (mTopTask == null
+ || (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
+ // Not an excluded task.
+ return null;
+ }
+ List<RunningTaskInfo> visibleNonExcludedTasks = mAllCachedTasks.stream()
+ .filter(t -> t.isVisible
+ && (t.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)
+ .toList();
+ return visibleNonExcludedTasks.isEmpty() ? null
+ : new CachedTaskInfo(visibleNonExcludedTasks);
}
/**
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 02d0f39..f840707 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;
@@ -1075,13 +1079,19 @@
boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
&& gestureState.getRunningTask() != null
&& gestureState.getRunningTask().isRootChooseActivity();
- if (gestureState.getRunningTask() != null
- && gestureState.getRunningTask().isExcludedAssistant()) {
- // In the case where we are in the excluded assistant state, ignore it and treat the
- // running activity as the task behind the assistant
- gestureState.updateRunningTask(TopTaskTracker.INSTANCE.get(this)
- .getCachedTopTask(true /* filterOnlyVisibleRecents */));
- forceOverviewInputConsumer = gestureState.getRunningTask().isHomeTask();
+
+ // In the case where we are in an excluded, translucent overlay, ignore it and treat the
+ // running activity as the task behind the overlay.
+ TopTaskTracker.CachedTaskInfo otherVisibleTask = gestureState.getRunningTask() == null
+ ? null
+ : gestureState.getRunningTask().otherVisibleTaskThisIsExcludedOver();
+ if (otherVisibleTask != null) {
+ ActiveGestureLog.INSTANCE.addLog(new CompoundString("Changing active task to ")
+ .append(otherVisibleTask.getPackageName())
+ .append(" because the previous task running on top of this one (")
+ .append(gestureState.getRunningTask().getPackageName())
+ .append(") was excluded from recents"));
+ gestureState.updateRunningTask(otherVisibleTask);
}
boolean previousGestureAnimatedToLauncher =
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 63771f0..5557639 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -48,6 +48,14 @@
*/
protected abstract String getDelegatorName();
+ @Override
+ public <T extends InputConsumer> T getInputConsumerOfClass(Class<T> c) {
+ if (getClass().equals(c)) {
+ return c.cast(this);
+ }
+ return mDelegate.getInputConsumerOfClass(c);
+ }
+
protected void setActive(MotionEvent ev) {
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(getDelegatorName())
.append(" became active"));
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index addcfb8..fc3f3ab 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -52,9 +52,15 @@
if (isInArea(motionEvent.getRawX())) {
Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable();
if (longPressRunnable != null) {
- setActive(motionEvent);
-
- MAIN_EXECUTOR.post(longPressRunnable);
+ OtherActivityInputConsumer oaic = getInputConsumerOfClass(
+ OtherActivityInputConsumer.class);
+ if (oaic != null) {
+ oaic.setForceFinishRecentsTransitionCallback(longPressRunnable);
+ setActive(motionEvent);
+ } else {
+ setActive(motionEvent);
+ MAIN_EXECUTOR.post(longPressRunnable);
+ }
}
}
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 7e61167..28c00eb 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -125,6 +125,9 @@
// Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
private float mStartDisplacement;
+ // The callback called upon finishing the recents transition if it was force-canceled
+ private Runnable mForceFinishRecentsTransitionCallback;
+
public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
@@ -444,7 +447,7 @@
// animateToProgress so we have to manually finish here. In the case of
// ACTION_CANCEL, someone else may be doing something so finish synchronously.
mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */,
- isCanceled /* forceFinish */);
+ isCanceled /* forceFinish */, mForceFinishRecentsTransitionCallback);
} else {
// The animation hasn't started yet, so insert a replacement handler into the
// callbacks which immediately finishes the animation after it starts.
@@ -513,6 +516,14 @@
}
/**
+ * Sets a callback to be called when the recents transition is force-canceled by another input
+ * consumer being made active.
+ */
+ public void setForceFinishRecentsTransitionCallback(Runnable r) {
+ mForceFinishRecentsTransitionCallback = r;
+ }
+
+ /**
* A listener which just finishes the animation immediately after starting. Replaces
* AbsSwipeUpHandler if the gesture itself finishes before the animation even starts.
*/
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 3388642..7d3a860 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -21,7 +21,9 @@
import android.media.session.MediaSessionManager;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Utilities;
@@ -52,6 +54,7 @@
private final boolean mStartingInActivityBounds;
private boolean mTargetHandledTouch;
private boolean mHasSetTouchModeForFirstDPadEvent;
+ private boolean mIsWaitingForAttachToWindow;
public OverviewInputConsumer(GestureState gestureState, T activity,
@Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
@@ -118,21 +121,47 @@
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (!mHasSetTouchModeForFirstDPadEvent) {
- // When Overview is launched via meta+tab or swipe up from an app, the touch
- // mode somehow is not changed to false by the Android framework. The subsequent
- // key events (e.g. DPAD_LEFT, DPAD_RIGHT) can only be dispatched to focused
- // views, while focus can only be requested in
- // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To
- // note, here we launch overview with live tile.
- mHasSetTouchModeForFirstDPadEvent = true;
- mActivity.getRootView().getViewRootImpl().touchModeChanged(false);
+ if (mHasSetTouchModeForFirstDPadEvent) {
+ break;
}
+ View viewRoot = mActivity.getRootView();
+ if (viewRoot.isAttachedToWindow()) {
+ setTouchModeChanged(viewRoot);
+ break;
+ }
+ if (mIsWaitingForAttachToWindow) {
+ break;
+ }
+ mIsWaitingForAttachToWindow = true;
+ viewRoot.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ view.removeOnAttachStateChangeListener(this);
+ mIsWaitingForAttachToWindow = false;
+ setTouchModeChanged(viewRoot);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ // Do nothing
+ }
+ });
break;
default:
break;
}
mActivity.dispatchKeyEvent(ev);
}
+
+ private void setTouchModeChanged(@NonNull View viewRoot) {
+ // When Overview is launched via meta+tab or swipe up from an app, the touch
+ // mode somehow is not changed to false by the Android framework. The
+ // subsequent key events (e.g. DPAD_LEFT, DPAD_RIGHT) can only be dispatched
+ // to focused views, while focus can only be requested in
+ // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To
+ // note, here we launch overview with live tile.
+ mHasSetTouchModeForFirstDPadEvent = true;
+ viewRoot.getViewRootImpl().touchModeChanged(false);
+ }
}
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/AsyncClockEventDelegate.java b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
index 0dee5b3..cda87c0 100644
--- a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
+++ b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
@@ -32,6 +32,8 @@
import androidx.annotation.WorkerThread;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SettingsCache.OnChangeListener;
import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -42,7 +44,11 @@
/**
* Extension of {@link ClockEventDelegate} to support async event registration
*/
-public class AsyncClockEventDelegate extends ClockEventDelegate implements OnChangeListener {
+public class AsyncClockEventDelegate extends ClockEventDelegate
+ implements OnChangeListener, SafeCloseable {
+
+ public static final MainThreadInitializedObject<AsyncClockEventDelegate> INSTANCE =
+ new MainThreadInitializedObject<>(AsyncClockEventDelegate::new);
private final Context mContext;
private final SimpleBroadcastReceiver mReceiver =
@@ -55,7 +61,7 @@
private boolean mFormatRegistered = false;
private boolean mDestroyed = false;
- public AsyncClockEventDelegate(Context context) {
+ private AsyncClockEventDelegate(Context context) {
super(context);
mContext = context;
@@ -79,6 +85,9 @@
@Override
public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
+ if (mDestroyed) {
+ return;
+ }
synchronized (mFormatObservers) {
if (!mFormatRegistered && !mDestroyed) {
SettingsCache.INSTANCE.get(mContext).register(mFormatUri, this);
@@ -114,10 +123,8 @@
}
}
- /**
- * Unregisters all system callbacks and destroys this delegate
- */
- public void onDestroy() {
+ @Override
+ public void close() {
mDestroyed = true;
SettingsCache.INSTANCE.get(mContext).unregister(mFormatUri, this);
UI_HELPER_EXECUTOR.execute(() -> mReceiver.unregisterReceiverSafely(mContext));
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index c3774eb..ae497f0 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -30,16 +30,18 @@
import com.android.launcher3.DeviceProfile
import com.android.launcher3.Utilities
import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.statemanager.StatefulActivity
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.Optional
import java.util.function.Supplier
/**
@@ -82,7 +84,7 @@
return SplitAnimInitProps(container.thumbnailView,
container.thumbnailView.thumbnail, drawable!!,
fadeWithThumbnail = true, isStagedTask = true,
- iconView = container.iconView
+ iconView = container.iconView.asView()
)
}
}
@@ -94,7 +96,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 +107,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 +131,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) {
@@ -270,6 +272,45 @@
safeRemoveViewFromDragLayer(launcher, splitSelectStateController.splitInstructionsView)
}
+ /**
+ * Animates the first placeholder view to fullscreen and launches its task.
+ * TODO(b/276361926): Remove the [resetCallback] option once contextual launches
+ */
+ fun playAnimPlaceholderToFullscreen(launcher: StatefulActivity<*>, view: View,
+ resetCallback: Optional<Runnable>) {
+ val stagedTaskView = view as FloatingTaskView
+
+ val isTablet: Boolean = launcher.deviceProfile.isTablet
+ val duration = if (isTablet) SplitAnimationTimings.TABLET_CONFIRM_DURATION else
+ SplitAnimationTimings.PHONE_CONFIRM_DURATION
+ val pendingAnimation = PendingAnimation(duration.toLong())
+ val firstTaskStartingBounds = Rect()
+ val firstTaskEndingBounds = Rect()
+
+ stagedTaskView.getBoundsOnScreen(firstTaskStartingBounds)
+ launcher.dragLayer.getBoundsOnScreen(firstTaskEndingBounds)
+ splitSelectStateController.setLaunchingFirstAppFullscreen()
+
+ stagedTaskView.addConfirmAnimation(
+ pendingAnimation,
+ RectF(firstTaskStartingBounds),
+ firstTaskEndingBounds,
+ false /* fadeWithThumbnail */,
+ true /* isStagedTask */)
+
+ pendingAnimation.addEndListener {
+ splitSelectStateController.launchInitialAppFullscreen {
+ if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+ splitSelectStateController.resetState()
+ } else if (resetCallback.isPresent) {
+ resetCallback.get().run()
+ }
+ }
+ }
+
+ pendingAnimation.buildAnim().start()
+ }
+
private fun safeRemoveViewFromDragLayer(launcher: StatefulActivity<*>, view: View?) {
if (view != null) {
launcher.dragLayer.removeView(view)
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index c8831c7..f67d7dc 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;
@@ -140,6 +140,9 @@
@Nullable
private GroupedTaskView mLaunchingTaskView;
+ /** True when the first selected split app is being launched in fullscreen. */
+ private boolean mLaunchingFirstAppFullscreen;
+
private FloatingTaskView mFirstFloatingTaskView;
private SplitInstructionsView mSplitInstructionsView;
@@ -289,7 +292,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 +305,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 +361,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 +466,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) {
@@ -590,7 +602,7 @@
private final int mInitialTaskId;
private final int mSecondTaskId;
- private final Consumer<Boolean> mSuccessCallback;
+ private Consumer<Boolean> mSuccessCallback;
RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId,
@Nullable Consumer<Boolean> callback) {
@@ -617,6 +629,7 @@
finishAdapter.run();
if (mSuccessCallback != null) {
mSuccessCallback.accept(true);
+ mSuccessCallback = null;
}
resetState();
});
@@ -690,6 +703,7 @@
mDismissingFromSplitPair = false;
mFirstFloatingTaskView = null;
mSplitInstructionsView = null;
+ mLaunchingFirstAppFullscreen = false;
}
/**
@@ -708,6 +722,10 @@
return mSplitSelectDataHolder.isBothSplitAppsConfirmed();
}
+ public boolean isLaunchingFirstAppFullscreen() {
+ return mLaunchingFirstAppFullscreen;
+ }
+
public int getInitialTaskId() {
return mSplitSelectDataHolder.getInitialTaskId();
}
@@ -716,6 +734,9 @@
return mSplitSelectDataHolder.getSecondTaskId();
}
+ public void setLaunchingFirstAppFullscreen() {
+ mLaunchingFirstAppFullscreen = true;
+ }
public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) {
mFirstFloatingTaskView = floatingTaskView;
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 056f9aa..0fa8a29 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -30,15 +31,19 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.RecentsView;
@@ -52,12 +57,13 @@
private final SplitSelectStateController mController;
private final int mHalfDividerSize;
+ private final IconCache mIconCache;
public SplitToWorkspaceController(Launcher launcher, SplitSelectStateController controller) {
mLauncher = launcher;
mDP = mLauncher.getDeviceProfile();
mController = controller;
-
+ mIconCache = LauncherAppState.getInstanceNoCreate().getIconCache();
mHalfDividerSize = mLauncher.getResources().getDimensionPixelSize(
R.dimen.multi_window_task_divider_size) / 2;
}
@@ -74,17 +80,24 @@
return false;
}
- // Convert original widgetView into bitmap to use for animation
- // TODO(b/276361926) get the icon for this widget via PackageManager?
int width = view.getWidth();
int height = view.getHeight();
- Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- view.draw(canvas);
+ MODEL_EXECUTOR.execute(() -> {
+ PackageItemInfo infoInOut = new PackageItemInfo(pendingIntent.getCreatorPackage(),
+ pendingIntent.getCreatorUserHandle());
+ mIconCache.getTitleAndIconForApp(infoInOut, false);
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- mController.setSecondTask(pendingIntent);
+ view.post(() -> {
+ mController.setSecondTask(pendingIntent);
+ // Convert original widgetView into bitmap to use for animation
+ Canvas canvas = new Canvas(bitmap);
+ view.draw(canvas);
+ startWorkspaceAnimation(view, bitmap,
+ new BitmapDrawable(mLauncher.getResources(), infoInOut.bitmap.icon));
+ });
+ });
- startWorkspaceAnimation(view, bitmap, null /*icon*/);
return true;
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 510044d..767aa15 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -384,6 +384,7 @@
mInversePositionMatrix.mapRect(mTempRectF);
mTempRectF.roundOut(mTmpCropRect);
+ params.setProgress(1f - fullScreenProgress);
params.applySurfaceParams(params.createSurfaceParams(this));
if (!DEBUG) {
diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
index 1cbded6..ca680db 100644
--- a/quickstep/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -15,8 +15,8 @@
*/
package com.android.quickstep.util;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import android.util.FloatProperty;
import android.view.RemoteAnimationTarget;
@@ -54,6 +54,7 @@
}
};
+ /** Progress from 0 to 1 where 0 is in-app and 1 is Overview */
private float mProgress;
private float mTargetAlpha;
private float mCornerRadius;
@@ -135,6 +136,7 @@
return this;
}
+ /** Builds the SurfaceTransaction from the given BuilderProxy params. */
public SurfaceTransaction createSurfaceParams(BuilderProxy proxy) {
RemoteAnimationTargets targets = mTargetSet;
SurfaceTransaction transaction = new SurfaceTransaction();
@@ -150,8 +152,12 @@
if (activityType == ACTIVITY_TYPE_HOME) {
mHomeBuilderProxy.onBuildTargetParams(builder, app, this);
} else {
- // Fade out Assistant overlay.
- if (activityType == ACTIVITY_TYPE_ASSISTANT && app.isNotInRecents) {
+ // Fade out translucent overlay.
+ // TODO(b/303351074): use app.isNotInRecents directly once it is fixed.
+ boolean isNotInRecents = app.taskInfo != null
+ && (app.taskInfo.baseIntent.getFlags()
+ & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+ if (app.isTranslucent && isNotInRecents) {
float progress = Utilities.boundToRange(getProgress(), 0, 1);
builder.setAlpha(1 - Interpolators.DECELERATE_QUINT
.getInterpolation(progress));
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index cfb4d0d..fba847f 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -16,7 +16,7 @@
package com.android.quickstep.views;
-import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
import android.content.Context;
import android.util.AttributeSet;
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 8a6c197..9ff990e 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -18,7 +18,6 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
import android.content.Context;
@@ -41,7 +40,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
@@ -330,7 +328,7 @@
}
@Override
- protected boolean showTaskMenuWithContainer(IconView iconView) {
+ protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) {
return false;
}
@@ -338,16 +336,18 @@
@Override
public RunnableList launchTaskAnimated() {
RunnableList endCallback = new RunnableList();
- endCallback.add(() -> Launcher.getLauncher(mActivity).getStateManager().goToState(NORMAL));
+ RecentsView recentsView = getRecentsView();
DesktopRecentsTransitionController recentsController =
- getRecentsView().getDesktopRecentsController();
+ recentsView.getDesktopRecentsController();
if (recentsController != null) {
recentsController.launchDesktopFromRecents(this, success -> {
endCallback.executeAllAndDestroy();
});
}
+ // Callbacks get run from recentsView for case when recents animation already running
+ recentsView.addSideTaskLaunchCallback(endCallback);
return endCallback;
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 2e347ba..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,7 +35,7 @@
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;
@@ -54,7 +57,7 @@
@Nullable
private Task mSecondaryTask;
private TaskThumbnailView mSnapshotView2;
- private IconView mIconView2;
+ private TaskViewIcon mIconView2;
@Nullable
private CancellableTask<ThumbnailData> mThumbnailLoadRequest2;
@Nullable
@@ -99,8 +102,14 @@
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,
@@ -160,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);
@@ -174,6 +184,7 @@
}
if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
setIcon(mIconView2, null);
+ setText(mIconView2, null);
}
}
}
@@ -189,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");
}
@@ -305,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;
}
@@ -371,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();
}
@@ -388,7 +396,7 @@
int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- getPagedOrientationHandler().setSplitIconParams(mIconView, mIconView2,
+ getPagedOrientationHandler().setSplitIconParams(mIconView.asView(), mIconView2.asView(),
taskIconHeight, mSnapshotView.getMeasuredWidth(), mSnapshotView.getMeasuredHeight(),
getMeasuredHeight(), getMeasuredWidth(), isRtl, deviceProfile,
mSplitBoundsConfig);
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
new file mode 100644
index 0000000..960a09e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views;
+
+import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.touch.LandscapePagedViewHandler;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.SeascapePagedViewHandler;
+import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.util.RecentsOrientedState;
+
+/**
+ * An icon app menu view which can be used in place of an IconView in overview TaskViews.
+ */
+public class IconAppChipView extends FrameLayout implements TaskViewIcon {
+
+ private static final int MENU_BACKGROUND_REVEAL_DURATION = 417;
+ private static final int MENU_BACKGROUND_HIDE_DURATION = 333;
+
+ private IconView mIconView;
+ private TextView mIconTextView;
+ private ImageView mIconArrowView;
+ private ImageView mIconViewBackground;
+
+ private int mMaxIconBackgroundWidth;
+ private int mMinIconBackgroundWidth;
+ private int mMaxIconBackgroundHeight;
+ private int mMinIconBackgroundHeight;
+ private int mIconTextMinWidth;
+ private int mIconTextMaxWidth;
+ private int mInnerMargin;
+ private int mIconArrowSize;
+ private int mIconMenuMarginStart;
+ private int mArrowMaxTranslationX;
+
+ public IconAppChipView(Context context) {
+ this(context, null);
+ }
+
+ public IconAppChipView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public IconAppChipView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public IconAppChipView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mMaxIconBackgroundWidth = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_max_width);
+ mMinIconBackgroundWidth = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_min_width);
+ mMaxIconBackgroundHeight = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_max_height);
+ mMinIconBackgroundHeight = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_min_height);
+ mIconTextMaxWidth = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_text_max_width);
+ mInnerMargin = (int) getResources().getDimension(
+ R.dimen.task_thumbnail_icon_menu_arrow_margin);
+ mIconTextMinWidth = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_text_width) + (2 * mInnerMargin);
+ int taskIconHeight = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_drawable_touch_size);
+ int arrowWidth = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_arrow_size);
+ mIconArrowSize = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_arrow_drawable_size);
+ mIconMenuMarginStart = getResources().getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_margin);
+ mArrowMaxTranslationX =
+ mMaxIconBackgroundWidth - taskIconHeight - mIconTextMaxWidth + arrowWidth;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mIconView = findViewById(R.id.icon_view);
+ mIconTextView = findViewById(R.id.icon_text);
+ mIconArrowView = findViewById(R.id.icon_arrow);
+ mIconViewBackground = findViewById(R.id.icon_view_background);
+ }
+
+ protected IconView getIconView() {
+ return mIconView;
+ }
+
+ @Override
+ public void setText(CharSequence text) {
+ if (mIconTextView != null) {
+ mIconTextView.setText(text);
+ }
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return mIconView == null ? null : mIconView.getDrawable();
+ }
+
+ @Override
+ public void setDrawable(Drawable icon) {
+ if (mIconView != null) {
+ mIconView.setDrawable(icon);
+ }
+ }
+
+ @Override
+ public void setDrawableSize(int iconWidth, int iconHeight) {
+ if (mIconView != null) {
+ mIconView.setDrawableSize(iconWidth, iconHeight);
+ }
+ }
+
+ @Override
+ public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) {
+
+ PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ DeviceProfile deviceProfile =
+ ActivityContext.lookupContext(getContext()).getDeviceProfile();
+
+ int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+ int taskIconSize = deviceProfile.overviewTaskIconSizePx;
+ int taskMargin = deviceProfile.overviewTaskMarginPx;
+
+ LayoutParams iconMenuParams = (LayoutParams) getLayoutParams();
+ orientationHandler.setTaskIconMenuParams(iconMenuParams, mIconMenuMarginStart,
+ thumbnailTopMargin);
+ iconMenuParams.width = mMinIconBackgroundWidth;
+ iconMenuParams.height = taskIconSize;
+ if (orientationHandler instanceof SeascapePagedViewHandler) {
+ // Use half menu height to place the pivot within the X/Y center of icon in the menu.
+ setPivotX(getHeight() / 2f);
+ setPivotY(getHeight() / 2f - mIconMenuMarginStart);
+ } else if (orientationHandler instanceof LandscapePagedViewHandler) {
+ setPivotX(getWidth());
+ setPivotY(0);
+ }
+ // Pivot not updated for PortraitPagedViewHandler case, as it has 0 rotation.
+
+ setTranslationY(0);
+ setRotation(orientationHandler.getDegreesRotated());
+
+ LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
+ orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconSize,
+ thumbnailTopMargin, isRtl);
+ iconParams.width = iconParams.height = taskIconSize;
+ iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
+ mIconView.setLayoutParams(iconParams);
+
+ int iconDrawableSize = enableOverviewIconMenu()
+ ? deviceProfile.overviewTaskIconAppChipMenuDrawableSizePx
+ : isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx
+ : deviceProfile.overviewTaskIconDrawableSizePx;
+ mIconView.setDrawableSize(iconDrawableSize, iconDrawableSize);
+
+ LayoutParams iconTextParams = (LayoutParams) mIconTextView.getLayoutParams();
+ orientationHandler.setTaskIconParams(iconTextParams, 0, taskIconSize,
+ thumbnailTopMargin, isRtl);
+ iconTextParams.width = mIconTextMaxWidth;
+ iconTextParams.height = taskIconSize;
+ iconTextParams.setMarginStart(taskIconSize);
+ iconTextParams.topMargin = (getHeight() - mIconTextView.getHeight()) / 2;
+ iconTextParams.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
+ mIconTextView.setLayoutParams(iconTextParams);
+ mIconTextView.setRevealClip(true, 0, taskIconSize / 2f, mIconTextMinWidth);
+
+ LayoutParams iconArrowParams = (LayoutParams) mIconArrowView.getLayoutParams();
+ iconArrowParams.gravity = Gravity.CENTER_VERTICAL | Gravity.END;
+ iconArrowParams.setMarginStart(taskIconSize + mIconTextMinWidth);
+ iconArrowParams.setMarginEnd(mInnerMargin);
+ mIconArrowView.setLayoutParams(iconArrowParams);
+ mIconArrowView.getDrawable().setBounds(0, 0, mIconArrowSize, mIconArrowSize);
+
+ LayoutParams backgroundParams = (LayoutParams) mIconViewBackground.getLayoutParams();
+ backgroundParams.width = mMinIconBackgroundWidth;
+ backgroundParams.height = taskIconSize;
+ mIconViewBackground.setPivotX(
+ isRtl ? mMinIconBackgroundWidth - (taskIconSize / 2f - mInnerMargin)
+ : taskIconSize / 2f - mInnerMargin);
+ mIconViewBackground.setPivotY(taskIconSize / 2f);
+
+ requestLayout();
+ }
+
+ @Override
+ public void setIconColorTint(int color, float amount) {
+ if (mIconView != null) {
+ mIconView.setIconColorTint(color, amount);
+ }
+ }
+
+ @Override
+ public int getDrawableWidth() {
+ return mIconView == null ? 0 : mIconView.getDrawableWidth();
+ }
+
+ @Override
+ public int getDrawableHeight() {
+ return mIconView == null ? 0 : mIconView.getDrawableHeight();
+ }
+
+ protected void revealAnim(boolean isRevealing) {
+ if (isRevealing) {
+ ((AnimatedVectorDrawable) mIconArrowView.getDrawable()).start();
+ AnimatorSet anim = new AnimatorSet();
+ anim.playTogether(
+ ViewAnimationUtils.createCircularReveal(mIconTextView, 0,
+ mIconTextView.getHeight() / 2, mIconTextMinWidth, mIconTextMaxWidth),
+ ObjectAnimator.ofFloat(mIconViewBackground, SCALE_X,
+ mMaxIconBackgroundWidth / (float) mMinIconBackgroundWidth),
+ ObjectAnimator.ofFloat(mIconViewBackground, SCALE_Y,
+ mMaxIconBackgroundHeight / (float) mMinIconBackgroundHeight),
+ ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X,
+ isLayoutRtl() ? -mArrowMaxTranslationX : mArrowMaxTranslationX));
+ anim.setDuration(MENU_BACKGROUND_REVEAL_DURATION);
+ anim.setInterpolator(EMPHASIZED);
+ anim.start();
+ } else {
+ ((AnimatedVectorDrawable) mIconArrowView.getDrawable()).reverse();
+ AnimatorSet anim = new AnimatorSet();
+ Animator textRevealAnim = ViewAnimationUtils.createCircularReveal(mIconTextView, 0,
+ mIconTextView.getHeight() / 2, mIconTextMaxWidth, mIconTextMinWidth);
+ textRevealAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // createCircularReveal removes clip on finish, restore it here to clip text.
+ mIconTextView.setRevealClip(true, 0, mIconTextView.getHeight() / 2f,
+ mIconTextMinWidth);
+ }
+ });
+ anim.playTogether(
+ textRevealAnim,
+ ObjectAnimator.ofFloat(mIconViewBackground, SCALE_X, 1),
+ ObjectAnimator.ofFloat(mIconViewBackground, SCALE_Y, 1),
+ ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, 0));
+ anim.setDuration(MENU_BACKGROUND_HIDE_DURATION);
+ anim.setInterpolator(EMPHASIZED);
+ anim.start();
+ }
+ }
+
+ @Override
+ public View asView() {
+ return this;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
index 5895c05..222f9ca 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.views;
+import static com.android.launcher3.config.FeatureFlags.enableOverviewIconMenu;
+
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -22,16 +24,21 @@
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.widget.FrameLayout;
import androidx.annotation.Nullable;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.util.RecentsOrientedState;
/**
* A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
* when the drawable changes.
*/
-public class IconView extends View {
+public class IconView extends View implements TaskViewIcon {
@Nullable
private Drawable mDrawable;
@@ -52,6 +59,7 @@
/**
* Sets a {@link Drawable} to be displayed.
*/
+ @Override
public void setDrawable(@Nullable Drawable d) {
if (mDrawable != null) {
mDrawable.setCallback(null);
@@ -67,6 +75,7 @@
/**
* Sets the size of the icon drawable.
*/
+ @Override
public void setDrawableSize(int iconWidth, int iconHeight) {
mDrawableWidth = iconWidth;
mDrawableHeight = iconHeight;
@@ -82,15 +91,18 @@
mDrawable.setBounds(drawableRect);
}
+ @Override
@Nullable
public Drawable getDrawable() {
return mDrawable;
}
+ @Override
public int getDrawableWidth() {
return mDrawableWidth;
}
+ @Override
public int getDrawableHeight() {
return mDrawableHeight;
}
@@ -147,9 +159,41 @@
* @param color to blend in.
* @param amount [0,1] 0 no tint, 1 full tint
*/
+ @Override
public void setIconColorTint(int color, float amount) {
if (mDrawable != null) {
mDrawable.setColorFilter(Utilities.makeColorTintingColorFilter(color, amount));
}
}
+
+ @Override
+ public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) {
+ PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ DeviceProfile deviceProfile =
+ ActivityContext.lookupContext(getContext()).getDeviceProfile();
+
+ FrameLayout.LayoutParams iconParams = (FrameLayout.LayoutParams) getLayoutParams();
+
+ int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+ int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
+ int taskMargin = deviceProfile.overviewTaskMarginPx;
+
+ orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight,
+ thumbnailTopMargin, isRtl);
+ iconParams.width = iconParams.height = taskIconHeight;
+ setLayoutParams(iconParams);
+
+ setRotation(orientationHandler.getDegreesRotated());
+ int iconDrawableSize = enableOverviewIconMenu()
+ ? deviceProfile.overviewTaskIconAppChipMenuDrawableSizePx
+ : isGridTask ? deviceProfile.overviewTaskIconDrawableSizeGridPx
+ : deviceProfile.overviewTaskIconDrawableSizePx;
+ setDrawableSize(iconDrawableSize, iconDrawableSize);
+ }
+
+ @Override
+ public View asView() {
+ return this;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 1867fe9..fb9e640 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -137,16 +137,16 @@
@Override
public void onStateTransitionStart(LauncherState toState) {
setOverviewStateEnabled(toState.overviewUi);
- if (toState.overviewUi) {
- // If overview is enabled, we want to update at the start
- updateOverviewStateForDesktop(true);
- }
+
setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
if (toState == OVERVIEW_MODAL_TASK) {
setOverviewSelectEnabled(true);
}
setFreezeViewVisibility(true);
+ if (mActivity.getDesktopVisibilityController() != null) {
+ mActivity.getDesktopVisibilityController().onLauncherStateChanged(toState);
+ }
}
@Override
@@ -167,11 +167,6 @@
runActionOnRemoteHandles(remoteTargetHandle ->
remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
}
-
- if (!finalState.overviewUi) {
- // If overview is disabled, we want to update at the end
- updateOverviewStateForDesktop(false);
- }
}
@Override
@@ -183,9 +178,6 @@
& CLEAR_ALL_BUTTON) != 0;
setDisallowScrollToClearAll(!hasClearAllButton);
}
- if (mActivity.getDesktopVisibilityController() != null) {
- mActivity.getDesktopVisibilityController().setOverviewStateEnabled(enabled);
- }
}
@Override
@@ -282,11 +274,4 @@
null /* transition */);
}
}
-
- private void updateOverviewStateForDesktop(boolean enabled) {
- DesktopVisibilityController controller = mActivity.getDesktopVisibilityController();
- if (controller != null) {
- controller.setOverviewStateEnabled(enabled);
- }
- }
}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 9141c99..7f1d619 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -29,9 +29,9 @@
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiValueAlpha;
@@ -289,7 +289,7 @@
return 0;
}
- if (mDp.isTablet && FeatureFlags.enableGridOnlyOverview()) {
+ if (mDp.isTablet && Flags.enableGridOnlyOverview()) {
return mDp.stashedTaskbarHeight;
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 825c0ae..2aba4d0 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -42,7 +42,7 @@
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
@@ -217,6 +217,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -3263,7 +3264,10 @@
mSplitSelectStateController.setFirstFloatingTaskView(firstFloatingTaskView);
// Allow user to click staged app to launch into fullscreen
- firstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
+ firstFloatingTaskView.setOnClickListener(view ->
+ mSplitSelectStateController.getSplitAnimationController().
+ playAnimPlaceholderToFullscreen(mActivity, view,
+ Optional.of(() -> resetFromSplitSelectionState())));
// SplitInstructionsView: animate in
safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
@@ -3316,41 +3320,6 @@
});
}
- private void animateToFullscreen(View view) {
- FloatingTaskView stagedTaskView = (FloatingTaskView) view;
-
- boolean isTablet = mActivity.getDeviceProfile().isTablet;
- int duration = isTablet
- ? SplitAnimationTimings.TABLET_CONFIRM_DURATION
- : SplitAnimationTimings.PHONE_CONFIRM_DURATION;
-
- PendingAnimation pendingAnimation = new PendingAnimation(duration);
-
- Rect firstTaskStartingBounds = new Rect();
- Rect firstTaskEndingBounds = new Rect();
-
- stagedTaskView.getBoundsOnScreen(firstTaskStartingBounds);
- mActivity.getDragLayer().getBoundsOnScreen(firstTaskEndingBounds);
-
- stagedTaskView.addConfirmAnimation(
- pendingAnimation,
- new RectF(firstTaskStartingBounds),
- firstTaskEndingBounds,
- false /* fadeWithThumbnail */,
- true /* isStagedTask */);
-
- pendingAnimation.addEndListener(animationSuccess ->
- mSplitSelectStateController.launchInitialAppFullscreen(launchSuccess -> {
- if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
- mSplitSelectStateController.resetState();
- } else {
- resetFromSplitSelectionState();
- }
- }));
-
- pendingAnimation.buildAnim().start();
- }
-
/**
* Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}.
* @param dismissedTaskView the {@link TaskView} to be dismissed
@@ -4980,12 +4949,13 @@
protected void maybeDrawEmptyMessage(Canvas canvas) {
if (mShowEmptyMessage && mEmptyTextLayout != null) {
- // Offset to center in the visible (non-padded) part of RecentsView
- mTempRect.set(mInsets.left + getPaddingLeft(), mInsets.top + getPaddingTop(),
- mInsets.right + getPaddingRight(), mInsets.bottom + getPaddingBottom());
+ // Offsets icon and text up so that the vertical center of screen (accounting for
+ // insets) is between icon and text.
+ int offset = (mEmptyIcon.getIntrinsicHeight() + mEmptyMessagePadding) / 2;
+
canvas.save();
- canvas.translate(getScrollX() + (mTempRect.left - mTempRect.right) / 2,
- (mTempRect.top - mTempRect.bottom) / 2);
+ canvas.translate(getScrollX() + (mInsets.left - mInsets.right) / 2f,
+ (mInsets.top - mInsets.bottom) / 2f - offset);
mEmptyIcon.draw(canvas);
canvas.translate(mEmptyMessagePadding,
mEmptyIcon.getBounds().bottom + mEmptyMessagePadding);
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 50b38ea..df907e7 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;
@@ -75,6 +77,7 @@
import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -112,6 +115,8 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
+import kotlin.Unit;
+
import java.lang.annotation.Retention;
import java.util.Arrays;
import java.util.Collections;
@@ -120,8 +125,6 @@
import java.util.function.Consumer;
import java.util.stream.Stream;
-import kotlin.Unit;
-
/**
* A task in the Recents view.
*/
@@ -352,7 +355,7 @@
@Nullable
protected Task mTask;
protected TaskThumbnailView mSnapshotView;
- protected IconView mIconView;
+ protected TaskViewIcon mIconView;
protected final DigitalWellBeingToast mDigitalWellBeingToast;
protected float mFullscreenProgress;
private float mGridProgress;
@@ -519,8 +522,14 @@
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
@@ -563,14 +572,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 +596,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 +643,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 +759,7 @@
return new TaskThumbnailView[]{mSnapshotView};
}
- public IconView getIconView() {
+ public TaskViewIcon getIconView() {
return mIconView;
}
@@ -1006,14 +1020,6 @@
mActivity.getStateManager(), recentsView,
recentsView.getDepthController());
anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- recentsView.runActionOnRemoteHandles(
- (Consumer<RemoteTargetHandle>) remoteTargetHandle ->
- remoteTargetHandle
- .getTaskViewSimulator()
- .setDrawsBelowRecents(false));
- }
@Override
public void onAnimationEnd(Animator animator) {
@@ -1075,6 +1081,7 @@
mIconLoadRequest = iconCache.updateIconInBackground(mTask,
(task) -> {
setIcon(mIconView, task.icon);
+ setText(mIconView, TaskUtils.getTitle(getContext(), task));
mDigitalWellBeingToast.initialize(task);
});
}
@@ -1090,6 +1097,7 @@
}
if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
setIcon(mIconView, null);
+ setText(mIconView, null);
}
}
}
@@ -1109,7 +1117,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,14 +1134,19 @@
}
}
- 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()) {
+ if (Flags.enableGridOnlyOverview()) {
// With no focused task, there is less available space below the tasks, so align
// the arrow to the third option in the menu.
alignedOptionIndex = 2;
@@ -1142,13 +1155,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 +1182,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 +1893,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 +1925,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/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
index 8c13fe3..f3115c6 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
@@ -24,11 +24,11 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class FallbackTaskbarUIControllerTest : TaskbarBaseTestCase() {
@@ -36,8 +36,8 @@
lateinit var fallbackTaskbarUIController: FallbackTaskbarUIController
lateinit var stateListener: StateManager.StateListener<RecentsState>
- @Mock lateinit var recentsActivity: RecentsActivity
- @Mock lateinit var stateManager: StateManager<RecentsState>
+ private val recentsActivity: RecentsActivity = mock()
+ private val stateManager: StateManager<RecentsState> = mock()
@Before
override fun setup() {
@@ -46,10 +46,10 @@
fallbackTaskbarUIController = FallbackTaskbarUIController(recentsActivity)
// Capture registered state listener to send events to in our tests
- val captor = ArgumentCaptor.forClass(StateManager.StateListener::class.java)
+ val captor = argumentCaptor<StateManager.StateListener<RecentsState>>()
fallbackTaskbarUIController.init(taskbarControllers)
verify(stateManager).addStateListener(captor.capture())
- stateListener = captor.value as StateManager.StateListener<RecentsState>
+ stateListener = captor.lastValue
}
@Test
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
index 148e36c..ed88c29 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
@@ -23,17 +23,17 @@
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
import org.junit.Before
import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
class TaskbarKeyguardControllerTest : TaskbarBaseTestCase() {
- @Mock lateinit var baseDragLayer: TaskbarDragLayer
- @Mock lateinit var keyguardManager: KeyguardManager
+ private val baseDragLayer: TaskbarDragLayer = mock()
+ private val keyguardManager: KeyguardManager = mock()
@Before
override fun setup() {
@@ -50,7 +50,7 @@
@Test
fun uninterestingFlags_noActions() {
setFlags(0)
- verify(navbarButtonsViewController, never()).setKeyguardVisible(anyBoolean(), anyBoolean())
+ verify(navbarButtonsViewController, never()).setKeyguardVisible(any(), any())
}
@Test
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..37fcf43 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -13,34 +13,35 @@
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
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class NavButtonLayoutFactoryTest {
- @Mock lateinit var mockDeviceProfile: DeviceProfile
- @Mock lateinit var mockParentButtonContainer: FrameLayout
- @Mock lateinit var mockNavLayout: LinearLayout
- @Mock lateinit var mockStartContextualLayout: ViewGroup
- @Mock lateinit var mockEndContextualLayout: ViewGroup
- @Mock lateinit var mockResources: Resources
- @Mock lateinit var mockBackButton: ImageView
- @Mock lateinit var mockRecentsButton: ImageView
- @Mock lateinit var mockHomeButton: ImageView
+ private val mockDeviceProfile: DeviceProfile = mock()
+ private val mockParentButtonContainer: FrameLayout = mock()
+ private val mockNavLayout: LinearLayout = mock()
+ private val mockStartContextualLayout: ViewGroup = mock()
+ private val mockEndContextualLayout: ViewGroup = mock()
+ private val mockResources: Resources = mock()
+ private val mockBackButton: ImageView = mock()
+ private val mockRecentsButton: ImageView = mock()
+ private val mockHomeButton: ImageView = mock()
+ private val mockImeSwitcher: ImageView = mock()
+ private val mockRotationButton: RotationButton = mock()
+ private val mockA11yButton: ImageView = mock()
private var surfaceRotation = Surface.ROTATION_0
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
-
// Init end nav buttons
whenever(mockNavLayout.childCount).thenReturn(3)
whenever(mockNavLayout.findViewById<View>(R.id.back)).thenReturn(mockBackButton)
@@ -151,13 +152,13 @@
mockDeviceProfile.isTaskbarPresent = false
setDeviceProfileLandscape()
val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
- getLayoutter(
- isKidsMode = false,
- isInSetup = false,
- isThreeButtonNav = true,
- phoneMode = true,
- surfaceRotation = ROTATION_270
- )
+ getLayoutter(
+ isKidsMode = false,
+ isInSetup = false,
+ isThreeButtonNav = true,
+ phoneMode = true,
+ surfaceRotation = ROTATION_270
+ )
assert(layoutter is PhoneSeascapeNavLayoutter)
}
@@ -196,7 +197,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/AbstractTaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
index 9c6c93d..efc00bb 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
@@ -95,6 +95,6 @@
launcher.enableTransientTaskbar(expectTransientTaskbar);
launcher.recreateTaskbar();
launcher.checkForAnomaly(true, true);
- AbstractLauncherUiTest.checkDetectedLeaks(launcher);
+ AbstractLauncherUiTest.checkDetectedLeaks(launcher, true);
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 7d82944..10602d3 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()).
@@ -168,7 +167,7 @@
public void tearDown() {
try {
// Limits UI tests affecting tests running after them.
- AbstractQuickStepTest.checkDetectedLeaks(mLauncher);
+ AbstractQuickStepTest.checkDetectedLeaks(mLauncher, true);
} finally {
mLauncher.onTestFinish();
}
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index eded1c9..a5d7724 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -185,7 +185,7 @@
+ launcher.getNavigationModeMismatchError(false),
() -> launcher.getNavigationModeMismatchError(false) == null,
WAIT_TIME_MS, launcher);
- AbstractLauncherUiTest.checkDetectedLeaks(launcher);
+ AbstractLauncherUiTest.checkDetectedLeaks(launcher, true);
return true;
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 3a28991..3039261 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -17,6 +17,7 @@
package com.android.quickstep;
import static com.android.launcher3.config.FeatureFlags.ENABLE_CURSOR_HOVER_STATES;
+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;
@@ -82,6 +83,7 @@
@After
public void tearDown() {
executeOnLauncher(launcher -> {
+ if (launcher == null) return;
RecentsView recentsView = launcher.getOverviewPanel();
recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
});
@@ -209,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();
}
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/TaskbarModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java
index 9e41f74..e5657fb 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskbarModeSwitchRule.java
@@ -123,7 +123,7 @@
assertTrue(launcher, "Couldn't set taskbar=" + expectTransientTaskbar,
isTaskbarTransientMode(context) == expectTransientTaskbar, description);
- AbstractLauncherUiTest.checkDetectedLeaks(launcher);
+ AbstractLauncherUiTest.checkDetectedLeaks(launcher, true);
}
private static void assertTrue(LauncherInstrumentation launcher, String message,
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/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 7e07b81..50803fe 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -32,39 +32,36 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class SplitAnimationControllerTest {
private val taskId = 9
- @Mock lateinit var mockSplitSelectStateController: SplitSelectStateController
+ private val mockSplitSelectStateController: SplitSelectStateController = mock()
// TaskView
- @Mock lateinit var mockTaskView: TaskView
- @Mock lateinit var mockThumbnailView: TaskThumbnailView
- @Mock lateinit var mockBitmap: Bitmap
- @Mock lateinit var mockIconView: IconView
- @Mock lateinit var mockTaskViewDrawable: Drawable
+ private val mockTaskView: TaskView = mock()
+ private val mockThumbnailView: TaskThumbnailView = mock()
+ private val mockBitmap: Bitmap = mock()
+ private val mockIconView: IconView = mock()
+ private val mockTaskViewDrawable: Drawable = mock()
// GroupedTaskView
- @Mock lateinit var mockGroupedTaskView: GroupedTaskView
- @Mock lateinit var mockTask: Task
- @Mock lateinit var mockTaskKey: Task.TaskKey
- @Mock lateinit var mockTaskIdAttributeContainer: TaskIdAttributeContainer
+ private val mockGroupedTaskView: GroupedTaskView = mock()
+ private val mockTask: Task = mock()
+ private val mockTaskKey: Task.TaskKey = mock()
+ private val mockTaskIdAttributeContainer: TaskIdAttributeContainer = mock()
// SplitSelectSource
- @Mock lateinit var splitSelectSource: SplitConfigurationOptions.SplitSelectSource
- @Mock lateinit var mockSplitSourceDrawable: Drawable
- @Mock lateinit var mockSplitSourceView: View
+ private val splitSelectSource: SplitConfigurationOptions.SplitSelectSource = mock()
+ private val mockSplitSourceDrawable: Drawable = mock()
+ private val mockSplitSourceView: View = mock()
lateinit var splitAnimationController: SplitAnimationController
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
-
whenever(mockTaskView.thumbnail).thenReturn(mockThumbnailView)
whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap)
whenever(mockTaskView.iconView).thenReturn(mockIconView)
@@ -85,12 +82,14 @@
// Missing taskView icon
whenever(mockIconView.drawable).thenReturn(null)
- val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
- splitAnimationController.getFirstAnimInitViews(
- { mockTaskView }, { splitSelectSource })
+ val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+ splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
- assertEquals("Did not fallback to use splitSource icon drawable",
- mockSplitSourceDrawable, splitAnimInitProps.iconDrawable)
+ assertEquals(
+ "Did not fallback to use splitSource icon drawable",
+ mockSplitSourceDrawable,
+ splitAnimInitProps.iconDrawable
+ )
}
@Test
@@ -99,12 +98,14 @@
whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true)
whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false)
- val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
- splitAnimationController.getFirstAnimInitViews(
- { mockTaskView }, { splitSelectSource })
+ val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+ splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
- assertEquals("Did not use taskView icon drawable", mockTaskViewDrawable,
- splitAnimInitProps.iconDrawable)
+ assertEquals(
+ "Did not use taskView icon drawable",
+ mockTaskViewDrawable,
+ splitAnimInitProps.iconDrawable
+ )
}
@Test
@@ -116,12 +117,14 @@
// Set split source to null
whenever(splitSelectSource.drawable).thenReturn(null)
- val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
- splitAnimationController.getFirstAnimInitViews(
- { mockTaskView }, { splitSelectSource })
+ val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+ splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
- assertEquals("Did not use taskView icon drawable", mockTaskViewDrawable,
- splitAnimInitProps.iconDrawable)
+ assertEquals(
+ "Did not use taskView icon drawable",
+ mockTaskViewDrawable,
+ splitAnimInitProps.iconDrawable
+ )
}
@Test
@@ -130,12 +133,14 @@
whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(false)
whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false)
- val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
- splitAnimationController.getFirstAnimInitViews(
- { mockTaskView }, { splitSelectSource })
+ val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+ splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
- assertEquals("Did not use splitSource icon drawable", mockSplitSourceDrawable,
- splitAnimInitProps.iconDrawable)
+ assertEquals(
+ "Did not use splitSource icon drawable",
+ mockSplitSourceDrawable,
+ splitAnimInitProps.iconDrawable
+ )
}
@Test
@@ -154,12 +159,17 @@
whenever(mockTaskKey.getId()).thenReturn(taskId)
whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)
whenever(mockGroupedTaskView.taskIdAttributeContainers)
- .thenReturn(Array(1) { mockTaskIdAttributeContainer })
- val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
- splitAnimationController.getFirstAnimInitViews(
- { mockGroupedTaskView }, { splitSelectSource })
+ .thenReturn(Array(1) { mockTaskIdAttributeContainer })
+ val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+ splitAnimationController.getFirstAnimInitViews(
+ { mockGroupedTaskView },
+ { splitSelectSource }
+ )
- assertEquals("Did not use splitSource icon drawable", mockSplitSourceDrawable,
- splitAnimInitProps.iconDrawable)
+ assertEquals(
+ "Did not use splitSource icon drawable",
+ mockSplitSourceDrawable,
+ splitAnimInitProps.iconDrawable
+ )
}
-}
\ No newline at end of file
+}
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index f198741..f292f9a 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -33,11 +33,11 @@
import com.android.launcher3.statemanager.StatefulActivity
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.launcher3.util.withArgCaptor
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
import com.android.systemui.shared.recents.model.Task
import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
+import java.util.function.Consumer
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -45,23 +45,22 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-import java.util.function.Consumer
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class SplitSelectStateControllerTest {
- @Mock lateinit var systemUiProxy: SystemUiProxy
- @Mock lateinit var depthController: DepthController
- @Mock lateinit var statsLogManager: StatsLogManager
- @Mock lateinit var stateManager: StateManager<LauncherState>
- @Mock lateinit var handler: Handler
- @Mock lateinit var context: StatefulActivity<*>
- @Mock lateinit var recentsModel: RecentsModel
- @Mock lateinit var pendingIntent: PendingIntent
+ private val systemUiProxy: SystemUiProxy = mock()
+ private val depthController: DepthController = mock()
+ private val statsLogManager: StatsLogManager = mock()
+ private val stateManager: StateManager<LauncherState> = mock()
+ private val handler: Handler = mock()
+ private val context: StatefulActivity<*> = mock()
+ private val recentsModel: RecentsModel = mock()
+ private val pendingIntent: PendingIntent = mock()
lateinit var splitSelectStateController: SplitSelectStateController
@@ -69,11 +68,12 @@
private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
private var taskIdCounter = 0
- private fun getUniqueId(): Int { return ++taskIdCounter }
+ private fun getUniqueId(): Int {
+ return ++taskIdCounter
+ }
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
splitSelectStateController =
SplitSelectStateController(
context,
@@ -111,13 +111,14 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTasksAndRunCallback(
- listOf(nonMatchingComponent),
- taskConsumer
- )
- verify(recentsModel).getTasks(capture())
- }
+ argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(nonMatchingComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+ .lastValue
// Send our mocked tasks
consumer.accept(tasks)
@@ -162,13 +163,14 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTasksAndRunCallback(
- listOf(matchingComponent),
- taskConsumer
- )
- verify(recentsModel).getTasks(capture())
- }
+ argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(matchingComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+ .lastValue
// Send our mocked tasks
consumer.accept(tasks)
@@ -201,13 +203,14 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTasksAndRunCallback(
- listOf(nonPrimaryUserComponent),
- taskConsumer
- )
- verify(recentsModel).getTasks(capture())
- }
+ argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(nonPrimaryUserComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+ .lastValue
// Send our mocked tasks
consumer.accept(tasks)
@@ -255,13 +258,14 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTasksAndRunCallback(
- listOf(nonPrimaryUserComponent),
- taskConsumer
- )
- verify(recentsModel).getTasks(capture())
- }
+ argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(nonPrimaryUserComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+ .lastValue
// Send our mocked tasks
consumer.accept(tasks)
@@ -306,13 +310,14 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTasksAndRunCallback(
- listOf(matchingComponent),
- taskConsumer
- )
- verify(recentsModel).getTasks(capture())
- }
+ argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(matchingComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+ .lastValue
// Send our mocked tasks
consumer.accept(tasks)
@@ -327,10 +332,7 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
- ComponentName("hotdog", "pie"),
- ComponentName("pumpkin", "pie")
- )
+ generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
@@ -361,13 +363,14 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTasksAndRunCallback(
- listOf(nonMatchingComponent, matchingComponent),
- taskConsumer
- )
- verify(recentsModel).getTasks(capture())
- }
+ argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(nonMatchingComponent, matchingComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+ .lastValue
// Send our mocked tasks
consumer.accept(tasks)
@@ -381,10 +384,7 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
- ComponentName("hotdog", "pie"),
- ComponentName("pumpkin", "pie")
- )
+ generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
generateGroupTask(
ComponentName("pomegranate", "juice"),
@@ -415,13 +415,14 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTasksAndRunCallback(
- listOf(matchingComponent, matchingComponent),
- taskConsumer
- )
- verify(recentsModel).getTasks(capture())
- }
+ argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(matchingComponent, matchingComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+ .lastValue
// Send our mocked tasks
consumer.accept(tasks)
@@ -479,13 +480,14 @@
// Capture callback from recentsModel#getTasks()
val consumer =
- withArgCaptor<Consumer<ArrayList<GroupTask>>> {
- splitSelectStateController.findLastActiveTasksAndRunCallback(
- listOf(matchingComponent, matchingComponent),
- taskConsumer
- )
- verify(recentsModel).getTasks(capture())
- }
+ argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+ splitSelectStateController.findLastActiveTasksAndRunCallback(
+ listOf(matchingComponent, matchingComponent),
+ taskConsumer
+ )
+ verify(recentsModel).getTasks(capture())
+ }
+ .lastValue
// Send our mocked tasks
consumer.accept(tasks)
@@ -531,7 +533,7 @@
@Test
fun secondPendingIntentSet() {
val itemInfo = ItemInfo()
- `when`(pendingIntent.creatorUserHandle).thenReturn(primaryUserHandle)
+ whenever(pendingIntent.creatorUserHandle).thenReturn(primaryUserHandle)
splitSelectStateController.setInitialTaskSelect(null, 0, itemInfo, null, 1)
splitSelectStateController.setSecondTask(pendingIntent)
assertTrue(splitSelectStateController.isBothSplitAppsConfirmed)
diff --git a/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt b/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt
index f73be72..6a418a4 100644
--- a/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt
@@ -21,19 +21,17 @@
import android.testing.TestableLooper.RunWithLooper
import android.util.Log
import androidx.test.filters.SmallTest
-import com.android.launcher3.util.any
-import com.android.launcher3.util.mock
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.inOrder
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -74,7 +72,7 @@
provider.preemptivelyStartTransition(initialProgress = null)
verify(listener).onTransitionStarted()
- verify(listener, never()).onTransitionProgress(anyFloat())
+ verify(listener, never()).onTransitionProgress(any())
}
@Test
@@ -90,7 +88,7 @@
provider.preemptivelyStartTransition()
provider.cancelPreemptiveStart()
- with(inOrder(listener)) {
+ inOrder(listener) {
verify(listener).onTransitionStarted()
verify(listener).onTransitionFinished()
}
@@ -111,7 +109,7 @@
source.onTransitionStarted()
source.onTransitionFinished()
- with(inOrder(listener)) {
+ inOrder(listener) {
verify(listener).onTransitionStarted()
verify(listener).onTransitionFinished()
}
@@ -152,7 +150,7 @@
provider.preemptivelyStartTransition()
source.onTransitionFinished()
- with(inOrder(listener)) {
+ inOrder(listener) {
verify(listener).onTransitionStarted()
verify(listener).onTransitionFinished()
}
@@ -165,7 +163,7 @@
testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1)
testableLooper.processAllMessages()
- with(inOrder(listener)) {
+ inOrder(listener) {
verify(listener).onTransitionStarted()
verify(listener).onTransitionFinished()
}
@@ -178,7 +176,7 @@
testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1)
testableLooper.processAllMessages()
- verify(testWtfHandler).onTerribleFailure(any(), any(), anyBoolean())
+ verify(testWtfHandler).onTerribleFailure(any(), any(), any())
}
@Test
@@ -225,7 +223,7 @@
source.onTransitionFinished()
- with(inOrder(listener)) {
+ inOrder(listener) {
verify(listener).onTransitionStarted()
verify(listener).onTransitionFinished()
}
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..070d024 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -245,8 +245,7 @@
<dimen name="keyboard_drag_stroke_width">4dp</dimen>
<!-- Folders -->
- <dimen name="page_indicator_dot_size">8dp</dimen>
- <dimen name="page_indicator_dot_size_v2">6dp</dimen>
+ <dimen name="page_indicator_dot_size">6dp</dimen>
<dimen name="page_indicator_size">10dp</dimen>
@@ -399,6 +398,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 ab9836f..d7b50a0 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;
@@ -387,10 +386,12 @@
setTag(itemInfo);
}
+ @VisibleForTesting
@UiThread
- protected void applyIconAndLabel(ItemInfoWithIcon info) {
+ public void applyIconAndLabel(ItemInfoWithIcon info) {
int flags = shouldUseTheme() ? FLAG_THEMED : 0;
- if (mHideBadge) {
+ // Remove badge on icons smaller than 48dp.
+ if (mHideBadge || mDisplay == DISPLAY_SEARCH_RESULT_SMALL) {
flags |= FLAG_NO_BADGE;
}
FastBitmapDrawable iconDrawable = info.newIcon(getContext(), flags);
@@ -814,6 +815,8 @@
float currentWordWidth, runningWidth = 0;
CharSequence currentWord;
StringBuilder newString = new StringBuilder();
+ // TODO: Remove when ENABLE_ICON_LABEL_AUTO_SCALING feature flag is being cleaned up.
+ paint.setLetterSpacing(MIN_LETTER_SPACING);
int stringPtr = 0;
for (int i = 0; i < breakPoints.size()+1; i++) {
if (i < breakPoints.size()) {
@@ -878,7 +881,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);
}
}
@@ -915,9 +918,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);
@@ -942,9 +943,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 d8804a1..c96e22d 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;
@@ -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.
*/
@@ -1754,7 +1772,7 @@
/** Gets the space that the overview actions will take, including bottom margin. */
public int getOverviewActionsClaimedSpace() {
- int overviewActionsSpace = isTablet && FeatureFlags.enableGridOnlyOverview()
+ int overviewActionsSpace = isTablet && Flags.enableGridOnlyOverview()
? 0
: (overviewActionsTopMarginPx + overviewActionsHeight);
return overviewActionsSpace + getOverviewActionsClaimedSpaceBelow();
@@ -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",
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4215e31..fd9ff50 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -26,8 +26,9 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
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.BuildConfig.QSB_ON_FIRST_SCREEN;
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,9 +44,11 @@
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.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
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;
@@ -182,7 +185,6 @@
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.popup.ArrowPopup;
-import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.qsb.QsbContainerView;
@@ -207,7 +209,6 @@
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
-import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
@@ -321,9 +322,11 @@
@Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500;
private static final String DISPLAY_WORKSPACE_TRACE_METHOD_NAME = "DisplayWorkspaceFirstFrame";
- private static final String DISPLAY_ALL_APPS_TRACE_METHOD_NAME = "DisplayAllApps";
+ public static final String DISPLAY_ALL_APPS_TRACE_METHOD_NAME = "DisplayAllApps";
public static final int DISPLAY_WORKSPACE_TRACE_COOKIE = 0;
public static final int DISPLAY_ALL_APPS_TRACE_COOKIE = 1;
+ private static final String COLD_STARTUP_TRACE_METHOD_NAME = "LauncherColdStartup";
+ public static final int COLD_STARTUP_TRACE_COOKIE = 2;
private static final FloatProperty<Workspace<?>> WORKSPACE_WIDGET_SCALE =
WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION);
@@ -333,7 +336,11 @@
private static final boolean DESKTOP_MODE_SUPPORTED =
"1".equals(Utilities.getSystemProperty("persist.wm.debug.desktop_mode_2", "0"));
- KeyboardShortcutsDelegate mKeyboardShortcutsDelegate = new KeyboardShortcutsDelegate(this);
+ private final ModelCallbacks mModelCallbacks = createModelCallbacks();
+
+ private final KeyboardShortcutsDelegate mKeyboardShortcutsDelegate =
+ new KeyboardShortcutsDelegate(this);
+
@Thunk
Workspace<?> mWorkspace;
@Thunk
@@ -420,11 +427,14 @@
private BaseSearchConfig mBaseSearchConfig;
private StartupLatencyLogger mStartupLatencyLogger;
private CellPosMapper mCellPosMapper = CellPosMapper.DEFAULT;
+ private boolean mIsFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN
+ && !ENABLE_SMARTSPACE_REMOVAL.get();
private final CannedAnimationCoordinator mAnimationCoordinator =
new CannedAnimationCoordinator(this);
private final List<BackPressHandler> mBackPressedHandlers = new ArrayList<>();
+ private boolean mIsColdStartupAfterReboot;
public static Launcher getLauncher(Context context) {
return fromContext(context);
@@ -439,6 +449,14 @@
? COLD
: COLD_DEVICE_REBOOTING
: WARM);
+
+ mIsColdStartupAfterReboot = sIsNewProcess
+ && !LockedUserState.get(this).isUserUnlockedAtLauncherStartup();
+ if (mIsColdStartupAfterReboot) {
+ Trace.beginAsyncSection(
+ COLD_STARTUP_TRACE_METHOD_NAME, COLD_STARTUP_TRACE_COOKIE);
+ }
+
sIsNewProcess = false;
mStartupLatencyLogger
.logStart(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
@@ -576,9 +594,6 @@
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onCreate(savedInstanceState);
- }
mOverlayManager = getDefaultOverlay();
PluginManagerWrapper.INSTANCE.get(this).addPluginListener(this,
LauncherOverlayPlugin.class, false /* allowedMultiple */);
@@ -593,6 +608,10 @@
mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
}
+ protected ModelCallbacks createModelCallbacks() {
+ return new ModelCallbacks(this);
+ }
+
/**
* Create {@link StartupLatencyLogger} that only collects launcher startup latency metrics
* without sending them anywhere. Child class can override this method to create logger
@@ -788,8 +807,6 @@
return true;
}
- private LauncherCallbacks mLauncherCallbacks;
-
@Override
public void invalidateParent(ItemInfo info) {
if (info.container >= 0) {
@@ -1304,7 +1321,9 @@
// Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
// default state, otherwise we will update to the wrong offsets in RTL
mWorkspace.lockWallpaperToDefaultPage();
- mWorkspace.bindAndInitFirstWorkspaceScreen();
+ if (!ENABLE_SMARTSPACE_REMOVAL.get()) {
+ mWorkspace.bindAndInitFirstWorkspaceScreen();
+ }
mDragController.addDragListener(mWorkspace);
// Get the search/delete/uninstall bar
@@ -1321,18 +1340,14 @@
mDropTargetBar.setup(mDragController);
mAllAppsController.setupViews(mScrimView, mAppsView);
- if (SHOW_DOT_PAGINATION.get()) {
- mWorkspace.getPageIndicator().setShouldAutoHide(true);
- mWorkspace.getPageIndicator().setPaintColor(
- Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)
- ? Color.BLACK
- : Color.WHITE);
- }
+ mWorkspace.getPageIndicator().setShouldAutoHide(true);
+ mWorkspace.getPageIndicator().setPaintColor(Themes.getAttrBoolean(
+ this, R.attr.isWorkspaceDarkText) ? Color.BLACK : Color.WHITE);
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
- if (SHOW_DOT_PAGINATION.get() && WorkspacePageIndicator.class.getName().equals(name)) {
+ if (WorkspacePageIndicator.class.getName().equals(name)) {
return LayoutInflater.from(context).inflate(R.layout.page_indicator_dots,
(ViewGroup) parent, false);
}
@@ -1581,9 +1596,6 @@
}
}
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.onHomeIntent(internalStateHandled);
- }
if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
handleSplitAnimationGoingToHome();
}
@@ -1752,28 +1764,6 @@
}
}
- /**
- * Indicates that we want global search for this activity by setting the globalSearch
- * argument for {@link #startSearch} to true.
- */
- @Override
- public void startSearch(String initialQuery, boolean selectInitialQuery,
- Bundle appSearchData, boolean globalSearch) {
- if (appSearchData == null) {
- appSearchData = new Bundle();
- appSearchData.putString("source", "launcher-search");
- }
-
- if (mLauncherCallbacks == null ||
- !mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData)) {
- // Starting search from the callbacks failed. Start the default global search.
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, true);
- }
-
- // We need to show the workspace after starting the search
- mStateManager.goToState(NORMAL);
- }
-
void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
WidgetAddFlowHandler addFlowHandler) {
if (LOGD) {
@@ -2174,14 +2164,24 @@
}
@Override
+ public void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) {
+ mIsFirstPagePinnedItemEnabled = isFirstPagePinnedItemEnabled;
+ mWorkspace.bindAndInitFirstWorkspaceScreen();
+ }
+
+ @Override
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
+ && mIsFirstPagePinnedItemEnabled
+ && !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 && !mIsFirstPagePinnedItemEnabled)
+ || shouldShowFirstPageWidget())
+ && orderedScreenIds.isEmpty()) {
// If there are no screens, we need to have an empty screen
mWorkspace.addExtraEmptyScreens();
}
@@ -2229,7 +2229,10 @@
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
+ && mIsFirstPagePinnedItemEnabled
+ && !shouldShowFirstPageWidget()
+ && screenId == FIRST_SCREEN_ID) {
// No need to bind the first screen, as its always bound.
continue;
}
@@ -2239,13 +2242,7 @@
@Override
public void preAddApps() {
- // If there's an undo snackbar, force it to complete to ensure empty screens are removed
- // before trying to add new items.
- mModelWriter.commitDelete();
- AbstractFloatingView snackbar = AbstractFloatingView.getOpenView(this, TYPE_SNACKBAR);
- if (snackbar != null) {
- snackbar.post(() -> snackbar.close(true));
- }
+ mModelCallbacks.preAddApps();
}
@Override
@@ -2659,6 +2656,11 @@
.logEnd(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
.log()
.reset();
+ if (mIsColdStartupAfterReboot) {
+ Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME,
+ COLD_STARTUP_TRACE_COOKIE);
+ }
+
MAIN_EXECUTOR.getHandler().postAtFrontOfQueue(
() -> getRootView().getViewTreeObserver()
.removeOnDrawListener(this));
@@ -2843,90 +2845,100 @@
public void onPageEndTransition() {}
/**
- * Add the icons for all apps.
- *
- * Implementation of the method from LauncherModel.Callbacks.
+ * See {@code LauncherBindingDelegate}
*/
@Override
@TargetApi(Build.VERSION_CODES.S)
@UiThread
public void bindAllApplications(AppInfo[] apps, int flags,
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
- Preconditions.assertUIThread();
- boolean hadWorkApps = mAppsView.shouldShowTabs();
- AllAppsStore<Launcher> appsStore = mAppsView.getAppsStore();
- appsStore.setApps(apps, flags, packageUserKeytoUidMap);
- PopupContainerWithArrow.dismissInvalidPopup(this);
- if (hadWorkApps != mAppsView.shouldShowTabs()) {
- getStateManager().goToState(NORMAL);
- }
-
+ mModelCallbacks.bindAllApplications(apps, flags, packageUserKeytoUidMap);
if (Utilities.ATLEAST_S) {
- Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
- DISPLAY_ALL_APPS_TRACE_COOKIE);
+ Trace.endAsyncSection(
+ Launcher.DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
+ Launcher.DISPLAY_ALL_APPS_TRACE_COOKIE
+ );
}
}
/**
- * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
- * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
+ * See {@code LauncherBindingDelegate}
*/
@Override
public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
- mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
+ mModelCallbacks.bindDeepShortcutMap(deepShortcutMapCopy);
}
@Override
public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
- mAppsView.getAppsStore().updateProgressBar(app);
+ mModelCallbacks.bindIncrementalDownloadProgressUpdated(app);
}
@Override
public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) {
- mWorkspace.widgetsRestored(widgets);
+ mModelCallbacks.bindWidgetsRestored(widgets);
}
/**
- * Some shortcuts were updated in the background.
- * Implementation of the method from LauncherModel.Callbacks.
- *
- * @param updated list of shortcuts which have changed.
+ * See {@code LauncherBindingDelegate}
*/
@Override
public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
- if (!updated.isEmpty()) {
- mWorkspace.updateWorkspaceItems(updated, this);
- PopupContainerWithArrow.dismissInvalidPopup(this);
- }
+ mModelCallbacks.bindWorkspaceItemsChanged(updated);
}
/**
- * Update the state of a package, typically related to install state.
- *
- * Implementation of the method from LauncherModel.Callbacks.
+ * See {@code LauncherBindingDelegate}
*/
@Override
public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
- mWorkspace.updateRestoreItems(updates, this);
+ mModelCallbacks.bindRestoreItemsChange(updates);
}
/**
- * A package was uninstalled/updated. We take both the super set of packageNames
- * in addition to specific applications to remove, the reason being that
- * this can be called when a package is updated as well. In that scenario,
- * we only remove specific components from the workspace and hotseat, where as
- * package-removal should clear all items by package name.
+ * See {@code LauncherBindingDelegate}
*/
@Override
public void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) {
- mWorkspace.removeItemsByMatcher(matcher);
- mDragController.onAppsRemoved(matcher);
- PopupContainerWithArrow.dismissInvalidPopup(this);
+ mModelCallbacks.bindWorkspaceComponentsRemoved(matcher);
+ }
+
+ /**
+ * See {@code LauncherBindingDelegate}
+ */
+ @Override
+ public void bindAllWidgets(final List<WidgetsListBaseEntry> allWidgets) {
+ mModelCallbacks.bindAllWidgets(allWidgets);
}
@Override
- public void bindAllWidgets(final List<WidgetsListBaseEntry> allWidgets) {
- mPopupDataProvider.setAllWidgets(allWidgets);
+ 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
@@ -2988,6 +3000,7 @@
mStateManager.dump(prefix, writer);
mPopupDataProvider.dump(prefix, writer);
mDeviceProfile.dump(this, prefix, writer);
+ mAppsView.getAppsStore().dump(prefix, writer);
try {
FileLog.flushAll(writer);
@@ -2996,10 +3009,6 @@
}
mModel.dumpState(prefix, fd, writer, args);
-
- if (mLauncherCallbacks != null) {
- mLauncherCallbacks.dump(prefix, fd, writer, args);
- }
mOverlayManager.dump(prefix, writer);
}
@@ -3243,11 +3252,6 @@
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.
@@ -3415,6 +3419,10 @@
// Overridden
}
+ public boolean getIsFirstPagePinnedItemEnabled() {
+ return mIsFirstPagePinnedItemEnabled;
+ }
+
/**
* Returns the animation coordinator for playing one-off animations
*/
@@ -3442,4 +3450,4 @@
}
// End of Getters and Setters
-}
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 9db8c82..ab41a31 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -21,6 +21,8 @@
import static com.android.launcher3.LauncherPrefs.ICON_STATE;
import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
+import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
@@ -123,6 +125,23 @@
.addUserEventListener(mModel::onUserEvent);
mOnTerminateCallback.add(userChangeListener::close);
+ if (ENABLE_SMARTSPACE_REMOVAL.get()) {
+ OnSharedPreferenceChangeListener firstPagePinnedItemListener =
+ new OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, String key) {
+ if (SMARTSPACE_ON_HOME_SCREEN.equals(key)) {
+ mModel.forceReload();
+ }
+ }
+ };
+ LauncherPrefs.getPrefs(mContext).registerOnSharedPreferenceChangeListener(
+ firstPagePinnedItemListener);
+ mOnTerminateCallback.add(() -> LauncherPrefs.getPrefs(mContext)
+ .unregisterOnSharedPreferenceChangeListener(firstPagePinnedItemListener));
+ }
+
LockedUserState.get(context).runOnUserUnlocked(() -> {
CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
cwm.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
deleted file mode 100644
index 0e529bd..0000000
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.os.Bundle;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * LauncherCallbacks is an interface used to extend the Launcher activity. It includes many hooks
- * in order to add additional functionality. Some of these are very general, and give extending
- * classes the ability to react to Activity life-cycle or specific user interactions. Others
- * are more specific and relate to replacing parts of the application, for example, the search
- * interface or the wallpaper picker.
- */
-public interface LauncherCallbacks {
-
- /*
- * Activity life-cycle methods. These methods are triggered after
- * the code in the corresponding Launcher method is executed.
- */
- void onCreate(Bundle savedInstanceState);
- void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args);
- void onHomeIntent(boolean internalStateHandled);
-
- /**
- * Starts a search with {@param initialQuery}. Return false if search was not started.
- */
- boolean startSearch(
- String initialQuery, boolean selectInitialQuery, Bundle appSearchData);
-}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index e8d5116..4db2fca 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,11 +60,11 @@
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) ||
- item == TASKBAR_PINNING
+ (moveStartupDataToDeviceProtectedStorageIsEnabled &&
+ item.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
+ isStartupDataMigrated) || item.encryptionType == EncryptionType.DEVICE_PROTECTED
)
bootAwarePrefs
else item.encryptedPrefs
@@ -138,13 +139,20 @@
private fun prepareToPutValues(
updates: Array<out Pair<Item, Any>>
): List<SharedPreferences.Editor> {
- val updatesPerPrefFile = updates.groupBy { it.first.encryptedPrefs }.toMutableMap()
+ val updatesPerPrefFile =
+ updates
+ .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED }
+ .groupBy { it.first.encryptedPrefs }
+ .toMutableMap()
- if (isBootAwareStartupDataEnabled) {
- val bootAwareUpdates = updates.filter { it.first.isBootAware }
- if (bootAwareUpdates.isNotEmpty()) {
- updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
+ val bootAwareUpdates =
+ updates.filter {
+ (it.first.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
+ moveStartupDataToDeviceProtectedStorageIsEnabled) ||
+ it.first.encryptionType == EncryptionType.DEVICE_PROTECTED
}
+ if (bootAwareUpdates.isNotEmpty()) {
+ updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
}
return updatesPerPrefFile.map { prefToItemValueList ->
@@ -237,13 +245,20 @@
* .apply() or .commit()
*/
private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
- val itemsPerFile = items.groupBy { it.encryptedPrefs }.toMutableMap()
+ val itemsPerFile =
+ items
+ .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED }
+ .groupBy { it.encryptedPrefs }
+ .toMutableMap()
- if (isBootAwareStartupDataEnabled) {
- val bootAwareUpdates = items.filter { it.isBootAware }
- if (bootAwareUpdates.isNotEmpty()) {
- itemsPerFile[bootAwarePrefs] = bootAwareUpdates
+ val bootAwareUpdates =
+ items.filter {
+ (it.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
+ moveStartupDataToDeviceProtectedStorageIsEnabled) ||
+ it.encryptionType == EncryptionType.DEVICE_PROTECTED
}
+ if (bootAwareUpdates.isNotEmpty()) {
+ itemsPerFile[bootAwarePrefs] = bootAwareUpdates
}
return itemsPerFile.map { (prefs, items) ->
@@ -254,7 +269,7 @@
}
fun migrateStartupDataToDeviceProtectedStorage() {
- if (!isBootAwareStartupDataEnabled) return
+ if (!moveStartupDataToDeviceProtectedStorageIsEnabled) return
Log.d(
TAG,
@@ -263,7 +278,7 @@
)
with(bootAwarePrefs.edit()) {
- BOOT_AWARE_ITEMS.forEach { putValue(it, get(it)) }
+ ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.forEach { putValue(it, get(it)) }
putBoolean(IS_STARTUP_DATA_MIGRATED.sharedPrefKey, true)
apply()
}
@@ -278,27 +293,67 @@
@JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
- @JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "", true)
+ const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
+ @JvmField
+ val ICON_STATE =
+ nonRestorableItem(
+ LauncherAppState.KEY_ICON_STATE,
+ "",
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
@JvmField
val ALL_APPS_OVERVIEW_THRESHOLD =
- nonRestorableItem(LauncherAppState.KEY_ALL_APPS_OVERVIEW_THRESHOLD, 180, true)
- @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, true)
+ nonRestorableItem(
+ LauncherAppState.KEY_ALL_APPS_OVERVIEW_THRESHOLD,
+ 180,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
+ @JvmField
+ val THEMED_ICONS =
+ backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
@JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
@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, true)
+ @JvmField
+ val WORKSPACE_SIZE =
+ backedUpItem(
+ DeviceGridState.KEY_WORKSPACE_SIZE,
+ "",
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
+ @JvmField
+ val HOTSEAT_COUNT =
+ backedUpItem(
+ DeviceGridState.KEY_HOTSEAT_COUNT,
+ -1,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
+ @JvmField
+ val TASKBAR_PINNING =
+ backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
@JvmField
val DEVICE_TYPE =
- backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, true)
- @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", true)
+ backedUpItem(
+ DeviceGridState.KEY_DEVICE_TYPE,
+ InvariantDeviceProfile.TYPE_PHONE,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
+ @JvmField
+ val DB_FILE =
+ backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.MOVE_TO_DEVICE_PROTECTED)
+ @JvmField
+ val SHOULD_SHOW_SMARTSPACE =
+ backedUpItem(
+ SHOULD_SHOW_SMARTSPACE_KEY,
+ WIDGET_ON_FIRST_SCREEN,
+ EncryptionType.DEVICE_PROTECTED
+ )
@JvmField
val RESTORE_DEVICE =
backedUpItem(
RestoreDbTask.RESTORED_DEVICE_TYPE,
InvariantDeviceProfile.TYPE_PHONE,
- true
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
)
@JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
@JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
@@ -308,7 +363,7 @@
"idp_grid_name",
isBackedUp = true,
defaultValue = null,
- isBootAware = true,
+ encryptionType = EncryptionType.MOVE_TO_DEVICE_PROTECTED,
type = String::class.java
)
@JvmField
@@ -322,7 +377,7 @@
"is_startup_data_boot_aware",
isBackedUp = false,
defaultValue = false,
- isBootAware = true
+ encryptionType = EncryptionType.DEVICE_PROTECTED
)
@VisibleForTesting
@@ -330,22 +385,22 @@
fun <T> backedUpItem(
sharedPrefKey: String,
defaultValue: T,
- isBootAware: Boolean = false
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED
): ConstantItem<T> =
- ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, isBootAware)
+ ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
@JvmStatic
fun <T> backedUpItem(
sharedPrefKey: String,
type: Class<out T>,
- isBootAware: Boolean = false,
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
defaultValueFromContext: (c: Context) -> T
): ContextualItem<T> =
ContextualItem(
sharedPrefKey,
isBackedUp = true,
defaultValueFromContext,
- isBootAware,
+ encryptionType,
type
)
@@ -354,9 +409,9 @@
fun <T> nonRestorableItem(
sharedPrefKey: String,
defaultValue: T,
- isBootAware: Boolean = false
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED
): ConstantItem<T> =
- ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, isBootAware)
+ ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
@Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
@JvmStatic
@@ -381,17 +436,16 @@
}
// It is a var because the unit tests are setting this to true so they can run.
-@VisibleForTesting
-var isBootAwareStartupDataEnabled: Boolean =
- com.android.launcher3.config.FeatureFlags.ENABLE_BOOT_AWARE_STARTUP_DATA.get()
+var moveStartupDataToDeviceProtectedStorageIsEnabled: Boolean =
+ com.android.launcher3.config.FeatureFlags.MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE.get()
-private val BOOT_AWARE_ITEMS: MutableSet<ConstantItem<*>> = mutableSetOf()
+private val ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE: MutableSet<ConstantItem<*>> = mutableSetOf()
abstract class Item {
abstract val sharedPrefKey: String
abstract val isBackedUp: Boolean
abstract val type: Class<*>
- abstract val isBootAware: Boolean
+ abstract val encryptionType: EncryptionType
val sharedPrefFile: String
get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
@@ -402,13 +456,16 @@
override val sharedPrefKey: String,
override val isBackedUp: Boolean,
val defaultValue: T,
- override val isBootAware: Boolean,
+ override val encryptionType: EncryptionType,
// The default value can be null. If so, the type needs to be explicitly stated, or else NPE
override val type: Class<out T> = defaultValue!!::class.java
) : Item() {
init {
- if (isBootAware && isBootAwareStartupDataEnabled) {
- BOOT_AWARE_ITEMS.add(this)
+ if (
+ encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED &&
+ moveStartupDataToDeviceProtectedStorageIsEnabled
+ ) {
+ ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.add(this)
}
}
}
@@ -417,7 +474,7 @@
override val sharedPrefKey: String,
override val isBackedUp: Boolean,
private val defaultSupplier: (c: Context) -> T,
- override val isBootAware: Boolean,
+ override val encryptionType: EncryptionType,
override val type: Class<out T>
) : Item() {
private var default: T? = null
@@ -429,3 +486,9 @@
return default!!
}
}
+
+enum class EncryptionType {
+ ENCRYPTED,
+ DEVICE_PROTECTED,
+ MOVE_TO_DEVICE_PROTECTED
+}
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
new file mode 100644
index 0000000..679f7d4
--- /dev/null
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -0,0 +1,96 @@
+package com.android.launcher3
+
+import androidx.annotation.UiThread
+import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.popup.PopupContainerWithArrow
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.Preconditions
+import com.android.launcher3.widget.model.WidgetsListBaseEntry
+import java.util.function.Predicate
+
+class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
+ override fun preAddApps() {
+ // If there's an undo snackbar, force it to complete to ensure empty screens are removed
+ // before trying to add new items.
+ launcher.modelWriter.commitDelete()
+ val snackbar =
+ AbstractFloatingView.getOpenView<AbstractFloatingView>(
+ launcher,
+ AbstractFloatingView.TYPE_SNACKBAR
+ )
+ snackbar?.post { snackbar.close(true) }
+ }
+
+ @UiThread
+ override fun bindAllApplications(
+ apps: Array<AppInfo?>?,
+ flags: Int,
+ packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?
+ ) {
+ Preconditions.assertUIThread()
+ val hadWorkApps = launcher.appsView.shouldShowTabs()
+ launcher.appsView.appsStore.setApps(apps, flags, packageUserKeytoUidMap)
+ PopupContainerWithArrow.dismissInvalidPopup(launcher)
+ if (hadWorkApps != launcher.appsView.shouldShowTabs()) {
+ launcher.stateManager.goToState(LauncherState.NORMAL)
+ }
+ }
+
+ /**
+ * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
+ * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
+ */
+ override fun bindDeepShortcutMap(deepShortcutMapCopy: HashMap<ComponentKey?, Int?>?) {
+ launcher.popupDataProvider.setDeepShortcutMap(deepShortcutMapCopy)
+ }
+
+ override fun bindIncrementalDownloadProgressUpdated(app: AppInfo?) {
+ launcher.appsView.appsStore.updateProgressBar(app)
+ }
+
+ override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
+ launcher.workspace.widgetsRestored(widgets)
+ }
+
+ /**
+ * Some shortcuts were updated in the background. Implementation of the method from
+ * LauncherModel.Callbacks.
+ *
+ * @param updated list of shortcuts which have changed.
+ */
+ override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
+ if (updated.isNotEmpty()) {
+ launcher.workspace.updateWorkspaceItems(updated, launcher)
+ PopupContainerWithArrow.dismissInvalidPopup(launcher)
+ }
+ }
+
+ /**
+ * Update the state of a package, typically related to install state. Implementation of the
+ * method from LauncherModel.Callbacks.
+ */
+ override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
+ launcher.workspace.updateRestoreItems(updates, launcher)
+ }
+
+ /**
+ * A package was uninstalled/updated. We take both the super set of packageNames in addition to
+ * specific applications to remove, the reason being that this can be called when a package is
+ * updated as well. In that scenario, we only remove specific components from the workspace and
+ * hotseat, where as package-removal should clear all items by package name.
+ */
+ override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>?) {
+ launcher.workspace.removeItemsByMatcher(matcher)
+ launcher.dragController.onAppsRemoved(matcher)
+ PopupContainerWithArrow.dismissInvalidPopup(launcher)
+ }
+
+ override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry?>?) {
+ launcher.popupDataProvider.allWidgets = allWidgets
+ }
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a1a3974..eeb5fe0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,7 +28,9 @@
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
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 +73,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,17 +597,19 @@
* Initializes and binds the first page
*/
public void bindAndInitFirstWorkspaceScreen() {
- if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ if ((!FeatureFlags.QSB_ON_FIRST_SCREEN
+ || !mLauncher.getIsFirstPagePinnedItemEnabled())
+ || shouldShowFirstPageWidget()) {
+ mFirstPagePinnedItem = null;
return;
}
// Add the first page
CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
- // Always add a first page pinned widget on the first screen.
if (mFirstPagePinnedItem == null) {
// In transposed layout, we add the first page pinned widget in the Grid.
// As workspace does not touch the edges, we do not need a full
- // width first page pinned widget.
+ // width first page pinned item.
mFirstPagePinnedItem = LayoutInflater.from(getContext())
.inflate(R.layout.search_container_workspace, firstPage, false);
}
@@ -624,7 +629,7 @@
// transition animations competing with us changing the scroll when we add pages
disableLayoutTransitions();
- // Recycle the first page pinned widget
+ // Recycle the first page pinned item
if (mFirstPagePinnedItem != null) {
((ViewGroup) mFirstPagePinnedItem.getParent()).removeView(mFirstPagePinnedItem);
}
@@ -635,12 +640,14 @@
mScreenOrder.clear();
mWorkspaceScreens.clear();
+ // Ensure that the first page is always present
+ if (!ENABLE_SMARTSPACE_REMOVAL.get()) {
+ bindAndInitFirstWorkspaceScreen();
+ }
+
// Remove any deferred refresh callbacks
mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
- // Ensure that the first page is always present
- bindAndInitFirstWorkspaceScreen();
-
// Re-enable the layout transitions
enableLayoutTransitions();
}
@@ -799,6 +806,13 @@
// and we store them as extra empty screens.
for (int i = 0; i < finalScreens.size(); i++) {
int screenId = finalScreens.keyAt(i);
+
+ // We don't want to remove the first screen even if it's empty because that's where
+ // first page pinned item would go if it gets turned back on.
+ if (ENABLE_SMARTSPACE_REMOVAL.get() && screenId == FIRST_SCREEN_ID) {
+ continue;
+ }
+
CellLayout screen = finalScreens.get(screenId);
mWorkspaceScreens.remove(screenId);
@@ -1012,7 +1026,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 +2874,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 378dbf3..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;
@@ -238,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 9fb175d..0b8bdeb 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,6 @@
// TODO(Block 8): Clean up flags
// TODO(Block 9): Clean up flags
- public static final BooleanFlag ENABLE_DOWNLOAD_APP_UX_V2 = getReleaseFlag(270395134,
- "ENABLE_DOWNLOAD_APP_UX_V2", ENABLED, "Updates the download app UX"
- + " to have better visuals");
-
- public static final BooleanFlag ENABLE_DOWNLOAD_APP_UX_V3 = getDebugFlag(270395186,
- "ENABLE_DOWNLOAD_APP_UX_V3", ENABLED, "Updates the download app UX"
- + " to have better visuals, improve contrast, and color");
-
- public static final BooleanFlag SHOW_DOT_PAGINATION = getDebugFlag(270395278,
- "SHOW_DOT_PAGINATION", ENABLED, "Enable showing dot pagination in workspace");
-
- 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 +163,14 @@
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;
+ }
+
+ public static final BooleanFlag ENABLE_SMARTSPACE_REMOVAL = getDebugFlag(290799975,
+ "ENABLE_SMARTSPACE_REMOVAL", DISABLED, "Enable SmartSpace removal for "
+ + "home screen");
+
// TODO(Block 10): Clean up flags
public static final BooleanFlag ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION = getDebugFlag(270614790,
"ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION", DISABLED,
@@ -270,10 +264,11 @@
"Enables taskbar pinning to allow user to switch between transient and persistent "
+ "taskbar flavors");
- public static final BooleanFlag ENABLE_BOOT_AWARE_STARTUP_DATA = getDebugFlag(251502424,
- "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED, "Marks LauncherPref data as (and allows it "
- + "to) available while the device is locked. Enabling this causes a 1-time "
- + "migration of certain SharedPreferences data. Improves startup latency.");
+ public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag(
+ 251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED,
+ "Marks LauncherPref data as (and allows it to) available while the device is"
+ + " locked. Enabling this causes a 1-time movement of certain SharedPreferences"
+ + " data. Improves startup latency.");
// TODO(Block 18): Clean up flags
public static final BooleanFlag ENABLE_APP_PAIRS = getDebugFlag(274189428,
@@ -317,14 +312,13 @@
+ "waiting for SystemUI and then merging the SystemUI progress whenever we "
+ "start receiving the events");
- // TODO(Block 23): Clean up flags
- // Aconfig migration complete for ENABLE_GRID_ONLY_OVERVIEW.
+ // Aconfig migration complete for ENABLE_OVERVIEW_ICON_MENU.
@VisibleForTesting
- public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206,
- "ENABLE_GRID_ONLY_OVERVIEW", TEAMFOOD,
- "Enable a grid-only overview without a focused task.");
- public static boolean enableGridOnlyOverview() {
- return ENABLE_GRID_ONLY_OVERVIEW.get() || Flags.enableGridOnlyOverview();
+ 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.
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index ae44f0a..3330448 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 && dataModel.isFirstPagePinnedItemEnabled
+ && !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..9b2344d 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -16,6 +16,7 @@
package com.android.launcher3.model;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -172,6 +173,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,
@@ -288,6 +294,10 @@
executeCallbacksTask(c -> {
c.clearPendingBinds();
c.startBinding();
+ if (ENABLE_SMARTSPACE_REMOVAL.get()) {
+ c.setIsFirstPagePinnedItemEnabled(
+ mBgDataModel.isFirstPagePinnedItemEnabled);
+ }
}, mUiExecutor);
// Bind workspace screens
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 5e88b9b..54ecc00 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -17,6 +17,9 @@
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
+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;
@@ -129,6 +132,8 @@
* Load id for which the callbacks were successfully bound
*/
public int lastLoadId = -1;
+ public boolean isFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN
+ && !ENABLE_SMARTSPACE_REMOVAL.get();
/**
* Clears all the data
@@ -152,7 +157,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();
@@ -486,6 +493,7 @@
default void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
default void bindScreens(IntArray orderedScreenIds) { }
+ default void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) { }
default void finishBindingItems(IntSet pagesBoundFirst) { }
default void preAddApps() { }
default void bindAppsAdded(IntArray newScreens,
@@ -505,6 +513,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..efd5574 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -18,6 +18,9 @@
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.ENABLE_SMARTSPACE_REMOVAL;
+import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
+import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
@@ -37,6 +40,7 @@
import androidx.annotation.NonNull;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
@@ -327,7 +331,11 @@
@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
+ && (!ENABLE_SMARTSPACE_REMOVAL.get() || LauncherPrefs.getPrefs(destReader.mContext)
+ .getBoolean(SMARTSPACE_ON_HOME_SCREEN, true))
+ && !shouldShowFirstPageWidget())
? 1 /* smartspace */ : 0);
List<DbEntry> existedEntries = destReader.mWorkspaceEntriesByScreenId.get(screenId);
if (existedEntries != null) {
@@ -465,6 +473,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 +577,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 +701,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..4370043 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;
@@ -470,7 +471,7 @@
// cause the item loading to get skipped
ShortcutKey.fromItemInfo(info);
}
- if (checkItemPlacement(info)) {
+ if (checkItemPlacement(info, dataModel.isFirstPagePinnedItemEnabled)) {
dataModel.addItem(mContext, info, false, logger);
} else {
markDeleted("Item position overlap");
@@ -480,7 +481,7 @@
/**
* check & update map of what's occupied; used to discard overlapping/invalid items
*/
- protected boolean checkItemPlacement(ItemInfo item) {
+ protected boolean checkItemPlacement(ItemInfo item, boolean isFirstPagePinnedItemEnabled) {
int containerIndex = item.screenId;
if (item.container == Favorites.CONTAINER_HOTSEAT) {
final GridOccupancy hotseatOccupancy =
@@ -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() && isFirstPagePinnedItemEnabled)) {
// 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..6ab8fc5 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,7 +16,11 @@
package com.android.launcher3.model;
+import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
+import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
+import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
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;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -57,6 +61,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;
@@ -115,6 +120,7 @@
*/
public class LoaderTask implements Runnable {
private static final String TAG = "LoaderTask";
+ public static final String SMARTSPACE_ON_HOME_SCREEN = "pref_smartspace_home_screen";
private static final boolean DEBUG = true;
@@ -296,6 +302,19 @@
logASplit("bindWidgets");
verifyNotStopped();
+ LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
+ if (SMARTSPACE_AS_A_WIDGET.get() && prefs.get(SHOULD_SHOW_SMARTSPACE)) {
+ mLauncherBinder.bindSmartspaceWidget();
+ // Turn off pref.
+ prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(false));
+ logASplit("bindSmartspaceWidget");
+ verifyNotStopped();
+ } else if (!SMARTSPACE_AS_A_WIDGET.get() && WIDGET_ON_FIRST_SCREEN
+ && !prefs.get(LauncherPrefs.SHOULD_SHOW_SMARTSPACE)) {
+ // Turn on pref.
+ prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(true));
+ }
+
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
logASplit("otherDelegateItems");
@@ -351,6 +370,9 @@
mModelDelegate.markActive();
logASplit("workspaceDelegateItems");
}
+ mBgDataModel.isFirstPagePinnedItemEnabled = FeatureFlags.QSB_ON_FIRST_SCREEN
+ && (!ENABLE_SMARTSPACE_REMOVAL.get() || LauncherPrefs.getPrefs(
+ mApp.getContext()).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true));
}
private void loadWorkspaceImpl(
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index e10e72d..139efc3 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -54,6 +54,7 @@
import com.android.launcher3.AutoInstallsLayout.SourceResources;
import com.android.launcher3.ConstantItem;
import com.android.launcher3.DefaultLayoutParser;
+import com.android.launcher3.EncryptionType;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherFiles;
@@ -499,11 +500,11 @@
private ConstantItem<Boolean> getEmptyDbCreatedKey(String dbName) {
if (mContext instanceof SandboxContext) {
return LauncherPrefs.nonRestorableItem(EMPTY_DATABASE_CREATED,
- false /* default value */, false /* boot aware */);
+ false /* default value */, EncryptionType.ENCRYPTED);
}
String key = TextUtils.equals(dbName, LauncherFiles.LAUNCHER_DB)
? EMPTY_DATABASE_CREATED : EMPTY_DATABASE_CREATED + "@" + dbName;
- return LauncherPrefs.backedUpItem(key, false /* default value */, false /* boot aware */);
+ return LauncherPrefs.backedUpItem(key, false /* default value */, EncryptionType.ENCRYPTED);
}
/**
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/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index ce71275..323b3a7 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -16,8 +16,6 @@
package com.android.launcher3.pageindicators;
-import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -69,7 +67,7 @@
private static final int PAGE_INDICATOR_ALPHA = 255;
private static final int DOT_ALPHA = 128;
private static final float DOT_ALPHA_FRACTION = 0.5f;
- private static final int DOT_GAP_FACTOR = SHOW_DOT_PAGINATION.get() ? 4 : 3;
+ private static final int DOT_GAP_FACTOR = 4;
private static final int VISIBLE_ALPHA = 255;
private static final int INVISIBLE_ALPHA = 0;
private Paint mPaginationPaint;
@@ -153,10 +151,7 @@
mPaginationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaginationPaint.setStyle(Style.FILL);
mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.folderPaginationColor));
- mDotRadius = (SHOW_DOT_PAGINATION.get()
- ? getResources().getDimension(R.dimen.page_indicator_dot_size_v2)
- : getResources().getDimension(R.dimen.page_indicator_dot_size))
- / 2;
+ mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
mCircleGap = DOT_GAP_FACTOR * mDotRadius;
setOutlineProvider(new MyOutlineProver());
mIsRtl = Utilities.isRtl(getResources());
@@ -164,7 +159,7 @@
@Override
public void setScroll(int currentScroll, int totalScroll) {
- if (SHOW_DOT_PAGINATION.get() && currentScroll == 0 && totalScroll == 0) {
+ if (currentScroll == 0 && totalScroll == 0) {
CURRENT_POSITION.set(this, (float) mActivePage);
return;
}
@@ -217,7 +212,7 @@
@Override
public void setShouldAutoHide(boolean shouldAutoHide) {
- mShouldAutoHide = shouldAutoHide && SHOW_DOT_PAGINATION.get();
+ mShouldAutoHide = shouldAutoHide;
if (shouldAutoHide && mPaginationPaint.getAlpha() > INVISIBLE_ALPHA) {
hideAfterDelay();
} else if (!shouldAutoHide) {
@@ -420,16 +415,14 @@
int alpha = mPaginationPaint.getAlpha();
// Here we draw the dots
- mPaginationPaint.setAlpha(SHOW_DOT_PAGINATION.get()
- ? ((int) (alpha * DOT_ALPHA_FRACTION))
- : DOT_ALPHA);
+ mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION));
for (int i = 0; i < mNumPages; i++) {
canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
x += circleGap;
}
// Here we draw the current page indicator
- mPaginationPaint.setAlpha(SHOW_DOT_PAGINATION.get() ? alpha : PAGE_INDICATOR_ALPHA);
+ mPaginationPaint.setAlpha(alpha);
canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
}
}
@@ -498,7 +491,7 @@
@Override
public void onAnimationEnd(Animator animation) {
if (!mCancelled) {
- if (mShouldAutoHide && SHOW_DOT_PAGINATION.get()) {
+ if (mShouldAutoHide) {
hideAfterDelay();
}
mAnimator = null;
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/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 7b4e248..6950fb5 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -25,14 +25,13 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
-import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Handler;
import android.os.Message;
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseActivity;
@@ -62,8 +61,8 @@
public static final int REQUEST_ROTATE = 1;
public static final int REQUEST_LOCK = 2;
- @Nullable
- private BaseActivity mActivity;
+ @NonNull
+ private final BaseActivity mActivity;
private final Handler mRequestOrientationHandler;
private boolean mIgnoreAutoRotateSettings;
@@ -92,14 +91,14 @@
// Initialize mLastActivityFlags to a value not used by SCREEN_ORIENTATION flags
private int mLastActivityFlags = -999;
- public RotationHelper(BaseActivity activity) {
+ public RotationHelper(@NonNull BaseActivity activity) {
mActivity = activity;
mRequestOrientationHandler =
new Handler(UI_HELPER_EXECUTOR.getLooper(), this::setOrientationAsync);
}
- private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings,
- DisplayController.Info info) {
+ private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings) {
+ if (mDestroyed) return;
// On large devices we do not handle auto-rotate differently.
mIgnoreAutoRotateSettings = ignoreAutoRotateSettings;
if (!mIgnoreAutoRotateSettings) {
@@ -122,58 +121,54 @@
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+ if (mDestroyed) return;
boolean ignoreAutoRotateSettings = info.isTablet(info.realBounds);
if (mIgnoreAutoRotateSettings != ignoreAutoRotateSettings) {
- setIgnoreAutoRotateSettings(ignoreAutoRotateSettings, info);
+ setIgnoreAutoRotateSettings(ignoreAutoRotateSettings);
notifyChange();
}
}
public void setStateHandlerRequest(int request) {
- if (mStateHandlerRequest != request) {
- mStateHandlerRequest = request;
- notifyChange();
- }
+ if (mDestroyed || mStateHandlerRequest == request) return;
+ mStateHandlerRequest = request;
+ notifyChange();
}
public void setCurrentTransitionRequest(int request) {
- if (mCurrentTransitionRequest != request) {
- mCurrentTransitionRequest = request;
- notifyChange();
- }
+ if (mDestroyed || mCurrentTransitionRequest == request) return;
+ mCurrentTransitionRequest = request;
+ notifyChange();
}
public void setCurrentStateRequest(int request) {
- if (mCurrentStateRequest != request) {
- mCurrentStateRequest = request;
- notifyChange();
- }
+ if (mDestroyed || mCurrentStateRequest == request) return;
+ mCurrentStateRequest = request;
+ notifyChange();
}
// Used by tests only.
public void forceAllowRotationForTesting(boolean allowRotation) {
+ if (mDestroyed) return;
mForceAllowRotationForTesting = allowRotation;
notifyChange();
}
public void initialize() {
- if (!mInitialized) {
- mInitialized = true;
- DisplayController displayController = DisplayController.INSTANCE.get(mActivity);
- DisplayController.Info info = displayController.getInfo();
- setIgnoreAutoRotateSettings(info.isTablet(info.realBounds), info);
- displayController.addChangeListener(this);
- notifyChange();
- }
+ if (mInitialized) return;
+ mInitialized = true;
+ DisplayController displayController = DisplayController.INSTANCE.get(mActivity);
+ DisplayController.Info info = displayController.getInfo();
+ setIgnoreAutoRotateSettings(info.isTablet(info.realBounds));
+ displayController.addChangeListener(this);
+ notifyChange();
}
public void destroy() {
- if (!mDestroyed) {
- mDestroyed = true;
- DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
- LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
- mActivity = null;
- }
+ if (mDestroyed) return;
+ mDestroyed = true;
+ DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
+ LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
}
private void notifyChange() {
@@ -206,10 +201,8 @@
@WorkerThread
private boolean setOrientationAsync(Message msg) {
- Activity activity = mActivity;
- if (activity != null) {
- activity.setRequestedOrientation(msg.what);
- }
+ if (mDestroyed) return true;
+ mActivity.setRequestedOrientation(msg.what);
return true;
}
@@ -228,8 +221,10 @@
public String toString() {
return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d, "
+ "mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, "
- + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b]",
+ + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b,"
+ + " mDestroyed=%b]",
mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
- mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting);
+ mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting,
+ mDestroyed);
}
}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index a75f326..e52e878 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -16,7 +16,7 @@
package com.android.launcher3.testing;
import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
-import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
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/Executors.java b/src/com/android/launcher3/util/Executors.java
index 07000ed..c622b71 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -49,6 +49,12 @@
POOL_SIZE, POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
/**
+ * An {@link LooperExecutor} to be used with async task where order is important.
+ */
+ public static final LooperExecutor ORDERED_BG_EXECUTOR = new LooperExecutor(
+ createAndStartNewLooper("BackgroundExecutor", THREAD_PRIORITY_BACKGROUND));
+
+ /**
* Returns the executor for running tasks on the main thread.
*/
public static final LooperExecutor MAIN_EXECUTOR =
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..37ec65c 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;
@@ -40,6 +39,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
+import android.view.WindowInsetsController;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.Button;
@@ -679,8 +679,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 +797,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 {
@@ -821,7 +819,10 @@
@Override
public void onDragStart(boolean start, float startDisplacement) {
super.onDragStart(start, startDisplacement);
- getWindowInsetsController().hide(WindowInsets.Type.ime());
+ WindowInsetsController insetsController = getWindowInsetsController();
+ if (insetsController != null) {
+ insetsController.hide(WindowInsets.Type.ime());
+ }
}
@Nullable private View getViewToShowEducationTip() {
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_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
index 582ab23..6b27503 100644
--- a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
+++ b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
@@ -41,10 +41,6 @@
default void hideOverlay(int duration) { }
- default boolean startSearch(byte[] config, Bundle extras) {
- return false;
- }
-
@Override
default void onActivityCreated(Activity activity, Bundle bundle) { }
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 654edad..da447b3 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -42,13 +42,16 @@
filegroup {
name: "launcher-oop-tests-src",
srcs: [
- "src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java",
+ "src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java",
+ "src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.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/ui/workspace/TaplWorkspaceTest.java",
"src/com/android/launcher3/util/LauncherLayoutBuilder.java",
"src/com/android/launcher3/util/TestUtil.java",
"src/com/android/launcher3/util/Wait.java",
@@ -58,8 +61,8 @@
"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",
"src/com/android/launcher3/testcomponent/BaseTestingActivity.java",
"src/com/android/launcher3/testcomponent/OtherBaseTestingActivity.java",
@@ -87,6 +90,7 @@
"androidx.test.espresso.contrib",
"androidx.test.espresso.intents",
"androidx.test.uiautomator_uiautomator",
+ "mockito-kotlin2",
"mockito-target-extended-minus-junit4",
"launcher_log_protos_lite",
"truth-prebuilt",
diff --git a/tests/assets/databases/workspace_items.sql b/tests/assets/databases/workspace_items.sql
new file mode 100644
index 0000000..68f7d50
--- /dev/null
+++ b/tests/assets/databases/workspace_items.sql
@@ -0,0 +1,79 @@
+DROP TABLE IF EXISTS 'favorites';
+CREATE TABLE favorites (_id INTEGER PRIMARY KEY,title TEXT,intent TEXT,container INTEGER,screen INTEGER,cellX INTEGER,cellY INTEGER,spanX INTEGER,spanY INTEGER,itemType INTEGER,appWidgetId INTEGER NOT NULL DEFAULT -1,icon BLOB,appWidgetProvider TEXT,modified INTEGER NOT NULL DEFAULT 0,restored INTEGER NOT NULL DEFAULT 0,profileId INTEGER DEFAULT 0,rank INTEGER NOT NULL DEFAULT 0,options INTEGER NOT NULL DEFAULT 0,appWidgetSource INTEGER NOT NULL DEFAULT -1);
+INSERT INTO 'favorites' VALUES(1,'Phone','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.dialer/.extensions.GoogleDialtactsActivity;end',-101,0,0,0,1,1,0,-1,NULL,NULL,0,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(2,'Messages','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.messaging/.ui.ConversationListActivity;end',-101,1,1,0,1,1,0,-1,NULL,NULL,0,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(3,'Play Store','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.vending/.AssetBrowserActivity;end',-101,2,2,0,1,1,0,-1,NULL,NULL,0,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(4,'Chrome','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.chrome/com.google.android.apps.chrome.Main;end',-101,3,3,0,1,1,0,-1,NULL,NULL,0,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(6,'Settings','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.settings/.Settings;end',-100,0,0,1,1,1,0,-1,X'',NULL,1693590010654,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(7,'Drive','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.docs/.app.NewMainProxyActivity;end',-100,0,1,2,1,1,0,-1,X'',NULL,1693589533751,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(8,'Better Bug','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.internal.betterbug/com.google.android.apps.betterbug.bugslist.BugsListActivity;end',16,0,0,0,1,1,0,-1,X'',NULL,1693589597917,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(9,'Flag Flipper','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.theflippinapp/.MainActivity;end',-100,0,3,2,1,1,0,-1,X'',NULL,1693589546863,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(10,NULL,NULL,-100,0,1,3,3,2,4,3,NULL,'com.google.android.apps.docs/com.google.android.apps.docs.drive.widget.suggestion.SuggestionAppWidgetProvider',1693589554018,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(11,'Scan','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_scan;end',-100,0,0,3,1,1,6,-1,X'',NULL,1693589559601,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(12,'Upload','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_upload;end',-100,0,0,4,1,1,6,-1,X'',NULL,1693589576040,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(13,'Search','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_search;end',-100,0,4,4,1,1,6,-1,X'',NULL,1693589582487,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(15,'BluetoothCompanionApp','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.bluetooth.companion/.MainActivity;end',-100,0,1,1,1,1,0,-1,X'',NULL,1693589594115,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(16,'Better Bug',NULL,-100,0,3,1,1,1,2,-1,NULL,NULL,1693589600675,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(17,'Better Bug','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.internal.betterbug/com.google.android.apps.betterbug.bugslist.BugsListActivity;end',16,0,1,0,1,1,0,-1,X'',NULL,1693589597936,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(18,'Better Bug','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.internal.betterbug/com.google.android.apps.betterbug.bugslist.BugsListActivity;end',16,0,0,1,1,1,0,-1,X'',NULL,1693589603123,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(19,'Incognito Tab','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.chrome;component=com.android.chrome/com.google.android.apps.chrome.Main;S.shortcut_id=dynamic-new-incognito-tab-shortcut;end',-100,0,2,2,1,1,6,-1,X'',NULL,1693589609963,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(20,NULL,NULL,-100,1,0,0,3,2,4,5,NULL,'com.google.android.deskclock/com.android.alarmclock.AnalogAppWidgetProvider',1693589630235,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(21,NULL,NULL,-100,1,1,3,2,2,4,6,NULL,'com.android.chrome/org.chromium.chrome.browser.quickactionsearchwidget.QuickActionSearchWidgetProvider$QuickActionSearchWidgetProviderDino',1693589677239,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(22,'New tab','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.chrome;component=com.android.chrome/com.google.android.apps.chrome.Main;S.shortcut_id=new-tab-shortcut;end',36,0,0,0,1,1,6,-1,X'',NULL,1693589694253,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(25,'Selfie','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.GoogleCamera;component=com.google.android.GoogleCamera/com.android.camera.CameraLauncher;S.shortcut_id=selfie;end',33,0,0,0,1,1,6,-1,X'',NULL,1693589686832,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(26,'Calculator','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.calculator/com.android.calculator2.Calculator;end',-100,1,4,2,1,1,0,-1,X'',NULL,1693589656954,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(27,'Calendar','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.calendar/com.android.calendar.AllInOneActivity;end',-100,1,4,0,1,1,0,-1,X'',NULL,1693589660516,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(28,'New event','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.calendar;component=com.google.android.calendar/com.android.calendar.AllInOneActivity;S.shortcut_id=launcher_shortcuts_shortcut_new_event;end',38,0,0,0,1,1,6,-1,X'',NULL,1693589698615,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(29,'New task','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.calendar;component=com.google.android.calendar/com.android.calendar.AllInOneActivity;S.shortcut_id=launcher_shortcuts_shortcut_create_task;end',-100,1,0,4,1,1,6,-1,X'',NULL,1693589677243,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(30,'Contacts','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.contacts/com.android.contacts.activities.PeopleActivity;end',-100,1,4,1,1,1,0,-1,X'',NULL,1693589671550,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(31,'Alex Eaves','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.contacts;component=com.google.android.contacts/com.android.contacts.activities.PeopleActivity;S.shortcut_id=822i60c772551678bd93;end',-100,1,4,3,1,1,6,-1,X'',NULL,1693589681873,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(32,'Add contact','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.contacts;component=com.google.android.contacts/com.android.contacts.activities.PeopleActivity;S.shortcut_id=shortcut-add-contact;end',-100,1,3,3,1,1,6,-1,X'',NULL,1693589681880,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(33,'Call',NULL,-100,1,3,2,1,1,2,-1,NULL,NULL,1693589687263,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(34,'Contacts','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.contacts/com.android.contacts.activities.PeopleActivity;end',33,0,1,0,1,1,0,-1,X'',NULL,1693589686853,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(35,'Gmail','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.gm/.ConversationListActivityGmail;end',33,0,0,1,1,1,0,-1,X'',NULL,1693589690561,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(36,'Files',NULL,-100,1,3,1,1,1,2,-1,NULL,NULL,1693589706696,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(37,'Files','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.nbu.files/.home.HomeActivity;end',36,0,1,0,1,1,0,-1,X'',NULL,1693589694261,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(38,NULL,NULL,-100,1,0,3,1,1,2,-1,NULL,NULL,1693589698611,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(39,'Scan','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_scan;end',38,0,1,0,1,1,6,-1,X'',NULL,1693589698621,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(40,'Search','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_search;end',-100,1,3,4,1,1,6,-1,X'',NULL,1693589702696,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(41,'Upload','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.docs;component=com.google.android.apps.docs/.app.NewMainProxyActivity;S.shortcut_id=launcher_shortcut_upload;end',-100,1,1,2,1,1,6,-1,X'',NULL,1693589706711,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(42,'Camera Obfuscator','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.internal.camera.imageobfuscator/.activities.MainActivity;end',-100,1,3,0,1,1,0,-1,X'',NULL,1693589710458,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(43,'Files','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.nbu.files/.home.HomeActivity;end',45,0,0,0,1,1,0,-1,X'',NULL,1693589727388,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(44,'Flag Flipper','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.theflippinapp/.MainActivity;end',-100,2,1,0,1,1,0,-1,X'',NULL,1693589724756,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(45,NULL,NULL,-100,2,0,0,1,1,2,-1,NULL,NULL,1693589727385,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(46,'Flag Flipper','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.theflippinapp/.MainActivity;end',45,0,1,0,1,1,0,-1,X'',NULL,1693589727398,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(47,'Gmail','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.gm/.ConversationListActivityGmail;end',-100,2,2,0,1,1,0,-1,X'',NULL,1693589730037,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(48,'Compose','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.gm;component=com.google.android.gm/.ConversationListActivityGmail;S.shortcut_id=manifest_compose_shortcut;end',-100,2,3,0,1,1,6,-1,X'',NULL,1693589733121,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(49,NULL,NULL,-100,2,0,1,3,2,4,7,NULL,'com.google.android.gm/com.google.android.gm.widget.GmailWidgetProvider',1693589740752,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(50,NULL,NULL,-100,3,1,0,4,5,4,8,NULL,'com.google.android.calendar/com.google.android.calendar.widgetmonth.MonthViewWidgetProvider',1693589746495,0,0,0,0,-111);
+INSERT INTO 'favorites' VALUES(54,'Maps','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.MapsActivity;end',-100,2,3,2,1,1,0,-1,X'',NULL,1693589785990,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(55,'Productivity',NULL,-100,2,3,4,1,1,2,-1,NULL,NULL,1693589797590,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(56,'Work','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.maps;component=com.google.android.apps.maps/com.google.android.maps.MapsActivity;S.shortcut_id=manifest_work;end',55,0,1,0,1,1,6,-1,X'',NULL,1693589789538,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(57,'Home','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.apps.maps;component=com.google.android.apps.maps/com.google.android.maps.MapsActivity;S.shortcut_id=manifest_home;end',-100,2,3,1,1,1,6,-1,X'',NULL,1693589793825,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(58,'Gyotaku','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.internal.gyotaku/.Launcher;end',-100,2,3,3,1,1,0,-1,X'',NULL,1693589797615,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(60,'Phone','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.dialer/.extensions.GoogleDialtactsActivity;end',-100,2,4,3,1,1,0,-1,X'',NULL,1693589805582,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(61,'Photos','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.photos/.home.HomeActivity;end',-100,2,4,4,1,1,0,-1,X'',NULL,1693589809050,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(63,'Pixel Logger',NULL,-100,3,0,3,1,1,2,-1,NULL,NULL,1693589820775,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(65,'Pixel Tips','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.tips/.TipsMain;end',-100,3,0,4,1,1,0,-1,X'',NULL,1693589823832,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(66,'Play Store','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.vending/.AssetBrowserActivity;end',-100,3,0,1,1,1,0,-1,X'',NULL,1693589834647,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(67,NULL,NULL,-100,4,2,0,3,2,4,10,NULL,'com.google.android.apps.youtube.music/com.google.android.apps.youtube.music.player.widget.gm3.FreeformMusicWidgetProvider',1693589842256,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(68,NULL,NULL,-100,4,0,2,4,3,4,11,NULL,'com.google.android.apps.youtube.music/com.google.android.apps.youtube.music.player.widget.gm3.FreeformMusicWidgetProvider',1693589854706,0,0,0,0,-112);
+INSERT INTO 'favorites' VALUES(69,'YouTube','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;end',-100,4,4,4,1,1,0,-1,X'',NULL,1693589859008,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(70,'Explore','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.youtube;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;S.shortcut_id=explore-shortcut;end',-100,4,4,3,1,1,6,-1,X'',NULL,1693589867283,0,0,3,0,-1);
+INSERT INTO 'favorites' VALUES(71,'Search','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.youtube;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;S.shortcut_id=search-shortcut;end',-100,4,4,2,1,1,6,-1,X'',NULL,1693589871989,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(72,'Shorts','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.youtube;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;S.shortcut_id=shorts-shortcut;end',-100,4,0,1,1,1,6,-1,X'',NULL,1693589882256,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(73,'Subscriptions','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.google.android.youtube;component=com.google.android.youtube/.app.honeycomb.Shell%24HomeActivity;S.shortcut_id=subscriptions-shortcut;end',-100,4,0,0,1,1,6,-1,X'',NULL,1693589888244,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(74,'Safety','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.safetyhub/.LauncherActivity;end',-100,4,1,1,1,1,0,-1,X'',NULL,1693589891720,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(75,'Wi‑Fi','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.settings;component=com.android.settings/.Settings;S.shortcut_id=manifest-shortcut-wifi;end',-100,5,2,0,1,1,6,-1,X'',NULL,1693589897994,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(76,'Data usage','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.settings;component=com.android.settings/.Settings;S.shortcut_id=manifest-shortcut-data-usage;end',-100,5,3,1,1,1,6,-1,X'',NULL,1693589904331,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(77,'Battery','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.settings;component=com.android.settings/.Settings;S.shortcut_id=manifest-shortcut-battery;end',-100,5,1,2,1,1,6,-1,X'',NULL,1693589907795,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(78,'Internet','#Intent;action=android.intent.action.MAIN;category=com.android.launcher3.DEEP_SHORTCUT;launchFlags=0x10200000;package=com.android.settings;component=com.android.settings/.Settings;S.shortcut_id=component-shortcut-com.android.settings%2F.Settings%24NetworkProviderSettingsActivity;end',-100,5,2,1,1,1,6,-1,X'',NULL,1693589914187,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(79,'Safety','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.safetyhub/.LauncherActivity;end',-100,5,2,3,1,1,0,-1,X'',NULL,1693589917447,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(80,'Recorder','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.recorder/.ui.recordings.MainActivity;end',-100,5,0,4,1,1,0,-1,X'',NULL,1693589920866,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(81,'Maps','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.apps.maps/com.google.android.maps.MapsActivity;end',82,0,0,0,1,1,0,-1,X'',NULL,1693589929103,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(82,NULL,NULL,-100,5,3,3,1,1,2,-1,NULL,NULL,1693589929099,0,0,0,0,-1);
+INSERT INTO 'favorites' VALUES(83,'Flag Flipper','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.theflippinapp/.MainActivity;end',82,0,1,0,1,1,0,-1,X'',NULL,1693589929134,0,0,1,0,-1);
+INSERT INTO 'favorites' VALUES(84,'Gmail','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.gm/.ConversationListActivityGmail;end',82,0,2,0,1,1,0,-1,X'',NULL,1693589938320,0,0,2,0,-1);
+INSERT INTO 'favorites' VALUES(85,'Google','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.googlequicksearchbox/.SearchActivity;end',82,0,0,1,1,1,0,-1,X'',NULL,1693589938321,0,0,3,0,-1);
+INSERT INTO 'favorites' VALUES(86,'Calendar','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.google.android.calendar/com.android.calendar.AllInOneActivity;end',82,0,1,1,1,1,0,-1,X'',NULL,1693589938316,0,0,4,0,-1);
+INSERT INTO 'favorites' VALUES(87,'Chrome','#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.chrome/com.google.android.apps.chrome.Main;end',82,0,2,1,1,1,0,-1,X'',NULL,1693589941181,0,0,5,0,-1);
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 f5aa820..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";
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index a52ba9e..84fa988 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -38,10 +38,10 @@
import org.junit.After
import org.junit.Before
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
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
/**
* This is an abstract class for DeviceProfile tests that create an InvariantDeviceProfile based on
@@ -52,8 +52,8 @@
abstract class AbstractDeviceProfileTest {
protected var context: Context? = null
protected open val runningContext: Context = ApplicationProvider.getApplicationContext()
- private var displayController: DisplayController = mock(DisplayController::class.java)
- private var windowManagerProxy: WindowManagerProxy = mock(WindowManagerProxy::class.java)
+ private val displayController: DisplayController = mock()
+ private val windowManagerProxy: WindowManagerProxy = mock()
private lateinit var originalDisplayController: DisplayController
private lateinit var originalWindowManagerProxy: WindowManagerProxy
@@ -288,11 +288,10 @@
) {
val windowsBounds = perDisplayBoundsCache[displayInfo]!!
val realBounds = windowsBounds[rotation]
- whenever(windowManagerProxy.getDisplayInfo(ArgumentMatchers.any())).thenReturn(displayInfo)
- whenever(windowManagerProxy.getRealBounds(ArgumentMatchers.any(), ArgumentMatchers.any()))
- .thenReturn(realBounds)
- whenever(windowManagerProxy.getRotation(ArgumentMatchers.any())).thenReturn(rotation)
- whenever(windowManagerProxy.getNavigationMode(ArgumentMatchers.any()))
+ whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
+ whenever(windowManagerProxy.getRealBounds(any(), any())).thenReturn(realBounds)
+ whenever(windowManagerProxy.getRotation(any())).thenReturn(rotation)
+ whenever(windowManagerProxy.getNavigationMode(any()))
.thenReturn(
if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS
)
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 42338bf..a421006 100644
--- a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -27,9 +27,9 @@
import java.io.PrintWriter
import java.io.StringWriter
import org.junit.Before
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
/**
* This is an abstract class for DeviceProfile tests that don't need the real Context and mock an
@@ -41,7 +41,7 @@
protected var context: Context? = null
protected var inv: InvariantDeviceProfile? = null
- protected var info: Info = mock(Info::class.java)
+ protected val info: Info = mock()
protected var windowBounds: WindowBounds? = null
protected var isMultiWindowMode: Boolean = false
protected var transposeLayoutWithOrientation: Boolean = false
diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
index d59e02a..88a430b 100644
--- a/tests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -33,7 +33,8 @@
private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
-private val TEST_CONTEXTUAL_ITEM = ContextualItem("4", true, { true }, false, Boolean::class.java)
+private val TEST_CONTEXTUAL_ITEM =
+ ContextualItem("4", true, { true }, EncryptionType.ENCRYPTED, Boolean::class.java)
private const val TEST_DEFAULT_VALUE = "default"
private const val TEST_PREF_KEY = "test_pref_key"
@@ -51,13 +52,13 @@
@BeforeClass
@JvmStatic
fun setup() {
- isBootAwareStartupDataEnabled = true
+ moveStartupDataToDeviceProtectedStorageIsEnabled = true
}
@AfterClass
@JvmStatic
fun teardown() {
- isBootAwareStartupDataEnabled = false
+ moveStartupDataToDeviceProtectedStorageIsEnabled = false
}
}
@@ -203,7 +204,11 @@
@Test
fun put_bootAwareItem_updatesDeviceProtectedStorage() {
val bootAwareItem =
- LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+ LauncherPrefs.backedUpItem(
+ TEST_PREF_KEY,
+ TEST_DEFAULT_VALUE,
+ EncryptionType.DEVICE_PROTECTED
+ )
val bootAwarePrefs: SharedPreferences =
context
@@ -220,7 +225,11 @@
@Test
fun put_bootAwareItem_updatesEncryptedStorage() {
val bootAwareItem =
- LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+ LauncherPrefs.backedUpItem(
+ TEST_PREF_KEY,
+ TEST_DEFAULT_VALUE,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
val encryptedPrefs: SharedPreferences =
context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
@@ -235,7 +244,11 @@
@Test
fun remove_bootAwareItem_removesFromDeviceProtectedStorage() {
val bootAwareItem =
- LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+ LauncherPrefs.backedUpItem(
+ TEST_PREF_KEY,
+ TEST_DEFAULT_VALUE,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
val bootAwarePrefs: SharedPreferences =
context
@@ -254,7 +267,11 @@
@Test
fun remove_bootAwareItem_removesFromEncryptedStorage() {
val bootAwareItem =
- LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+ LauncherPrefs.backedUpItem(
+ TEST_PREF_KEY,
+ TEST_DEFAULT_VALUE,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
val encryptedPrefs: SharedPreferences =
context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
@@ -271,7 +288,11 @@
@Test
fun migrate_bootAwareItemsToDeviceProtectedStorage_worksAsIntended() {
val bootAwareItem =
- LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
+ LauncherPrefs.backedUpItem(
+ TEST_PREF_KEY,
+ TEST_DEFAULT_VALUE,
+ EncryptionType.MOVE_TO_DEVICE_PROTECTED
+ )
launcherPrefs.removeSync(bootAwareItem)
val bootAwarePrefs: SharedPreferences =
@@ -303,7 +324,7 @@
LauncherPrefs.backedUpItem(
TEST_PREF_KEY + "_",
TEST_DEFAULT_VALUE + "_",
- isBootAware = false
+ EncryptionType.ENCRYPTED
)
val bootAwarePrefs: SharedPreferences =
diff --git a/tests/src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
similarity index 84%
rename from tests/src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java
rename to tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
index f9dadaa..39dbcb2 100644
--- a/tests/src/com/android/launcher3/allapps/OopTaplOpenCloseAllApps.java
+++ b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllApps.java
@@ -21,11 +21,17 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
+import android.content.Intent;
import android.platform.test.annotations.PlatinumTest;
+import androidx.test.filters.FlakyTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import com.android.launcher3.LauncherState;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.tapl.AllApps;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
@@ -37,7 +43,10 @@
* Test that we can open and close the all apps in multiple situations.
* The test runs in Out of process (Oop) and in process.
*/
-public class OopTaplOpenCloseAllApps extends AbstractLauncherUiTest {
+public class TaplOpenCloseAllApps extends AbstractLauncherUiTest {
+
+ public static final String READ_DEVICE_CONFIG_PERMISSION =
+ "android.permission.READ_DEVICE_CONFIG";
/**
* Calls static method initialize
@@ -188,4 +197,24 @@
allApps.unfreeze();
}
}
+
+ /**
+ * Makes sure that when pressing back when AllApps is open we go back to the Home screen.
+ */
+ @FlakyTest(bugId = 256615483)
+ @Test
+ @PortraitLandscape
+ public void testPressBackFromAllAppsToHome() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ READ_DEVICE_CONFIG_PERMISSION);
+ assumeFalse(FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get());
+ mLauncher.getWorkspace().switchToAllApps();
+ mLauncher.pressBack();
+ mLauncher.getWorkspace();
+ waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+ startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ mLauncher.pressBack();
+ mLauncher.getWorkspace();
+ waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+ }
}
diff --git a/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java b/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
new file mode 100644
index 0000000..fd4619e
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/TaplTestsAllAppsIconsWorking.java
@@ -0,0 +1,66 @@
+/*
+ * 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.allapps;
+
+import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.tapl.AppIcon;
+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;
+
+/**
+ * The test runs in Out of process (Oop) and in process.
+ * Makes sure the basic behaviors of Icons on AllApps are working.
+ */
+public class TaplTestsAllAppsIconsWorking extends AbstractLauncherUiTest {
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ initialize(this);
+ }
+
+ /**
+ * Makes sure we can launch an icon from All apps
+ */
+ @Test
+ @PortraitLandscape
+ public void testAppIconLaunchFromAllAppsFromHome() {
+ final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
+
+ allApps.freeze();
+ try {
+ final AppIcon app = allApps.getAppIcon("TestActivity7");
+ assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
+ executeOnLauncher(launcher -> assertTrue(
+ "Launcher activity is the top activity; expecting another activity to be the "
+ + "top one",
+ isInLaunchedApp(launcher)));
+ } 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/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
index 1155227..78c61d5 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -24,20 +24,19 @@
import com.android.launcher3.util.Executors
import com.android.launcher3.util.IntArray
import com.android.launcher3.util.TestUtil.runOnExecutorSync
-import com.android.launcher3.util.any
-import com.android.launcher3.util.eq
-import com.android.launcher3.util.same
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.same
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
/** Tests for [AddWorkspaceItemsTask] */
@SmallTest
@@ -46,12 +45,11 @@
private lateinit var mDataModelCallbacks: MyCallbacks
- @Mock private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder
+ private val mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder = mock()
@Before
override fun setup() {
super.setup()
- MockitoAnnotations.initMocks(this)
mDataModelCallbacks = MyCallbacks()
Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) }
.get()
diff --git a/tests/src/com/android/launcher3/model/FactitiousDbController.kt b/tests/src/com/android/launcher3/model/FactitiousDbController.kt
new file mode 100644
index 0000000..664f23e
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/FactitiousDbController.kt
@@ -0,0 +1,64 @@
+package com.android.launcher3.model
+
+import android.content.Context
+import android.database.Cursor
+import android.database.sqlite.SQLiteDatabase
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.BufferedReader
+import java.io.InputStreamReader
+
+private val All_COLUMNS =
+ arrayOf(
+ "_id",
+ "title",
+ "intent",
+ "container",
+ "screen",
+ "cellX",
+ "cellY",
+ "spanX",
+ "spanY",
+ "itemType",
+ "appWidgetId",
+ "icon",
+ "appWidgetProvider",
+ "modified",
+ "restored",
+ "profileId",
+ "rank",
+ "options",
+ "appWidgetSource"
+ )
+private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql"
+
+class FactitiousDbController(context: Context) : ModelDbController(context) {
+
+ private val inMemoryDb: SQLiteDatabase by lazy {
+ SQLiteDatabase.createInMemory(SQLiteDatabase.OpenParams.Builder().build()).also { db ->
+ BufferedReader(
+ InputStreamReader(
+ InstrumentationRegistry.getInstrumentation()
+ .context
+ .assets
+ .open(INSERTION_STATEMENT_FILE)
+ )
+ )
+ .lines()
+ .forEach { sqlStatement -> db.execSQL(sqlStatement) }
+ }
+ }
+
+ override fun query(
+ table: String,
+ projection: Array<out String>?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ sortOrder: String?
+ ): Cursor {
+ return inMemoryDb.query(table, All_COLUMNS, selection, selectionArgs, null, null, sortOrder)
+ }
+
+ override fun loadDefaultFavoritesIfNecessary() {
+ // No-Op
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 544ed6b..389ec5c 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -175,7 +175,7 @@
// Item outside screen bounds are not placed
assertFalse(mLoaderCursor.checkItemPlacement(
- newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1)));
+ newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1), true));
}
@Test
@@ -186,22 +186,22 @@
// Overlapping mItems are not placed
assertTrue(mLoaderCursor.checkItemPlacement(
- newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1)));
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), true));
assertFalse(mLoaderCursor.checkItemPlacement(
- newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1)));
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), true));
assertTrue(mLoaderCursor.checkItemPlacement(
- newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2)));
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), true));
assertFalse(mLoaderCursor.checkItemPlacement(
- newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2)));
+ newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), true));
assertTrue(mLoaderCursor.checkItemPlacement(
- newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1)));
+ newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1), true));
assertTrue(mLoaderCursor.checkItemPlacement(
- newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1)));
+ newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1), true));
assertFalse(mLoaderCursor.checkItemPlacement(
- newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1)));
+ newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1), true));
}
@Test
@@ -212,12 +212,12 @@
// Hotseat mItems are only placed based on screenId
assertTrue(mLoaderCursor.checkItemPlacement(
- newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1)));
+ newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1), true));
assertTrue(mLoaderCursor.checkItemPlacement(
- newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2)));
+ newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2), true));
assertFalse(mLoaderCursor.checkItemPlacement(
- newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3)));
+ newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3), true));
}
private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
new file mode 100644
index 0000000..1421087
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -0,0 +1,104 @@
+package com.android.launcher3.model
+
+import android.appwidget.AppWidgetManager
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherModel
+import com.android.launcher3.LauncherModel.LoaderTransaction
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.cache.CachingLogic
+import com.android.launcher3.icons.cache.IconCacheUpdateHandler
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.LooperIdleLock
+import com.google.common.truth.Truth
+import java.util.concurrent.CountDownLatch
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LoaderTaskTest {
+ @Mock private lateinit var app: LauncherAppState
+ @Mock private lateinit var bgAllAppsList: AllAppsList
+ @Mock private lateinit var modelDelegate: ModelDelegate
+ @Mock private lateinit var launcherBinder: LauncherBinder
+ @Mock private lateinit var launcherModel: LauncherModel
+ @Mock private lateinit var transaction: LoaderTransaction
+ @Mock private lateinit var iconCache: IconCache
+ @Mock private lateinit var idleLock: LooperIdleLock
+ @Mock private lateinit var iconCacheUpdateHandler: IconCacheUpdateHandler
+ @Mock private lateinit var appWidgetManager: AppWidgetManager
+
+ @Before
+ fun setup() {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val idp =
+ InvariantDeviceProfile.INSTANCE[context].apply {
+ numRows = 5
+ numColumns = 6
+ numDatabaseHotseatIcons = 5
+ }
+
+ MockitoAnnotations.initMocks(this)
+ `when`(app.context).thenReturn(context)
+ `when`(app.model).thenReturn(launcherModel)
+ `when`(launcherModel.beginLoader(any(LoaderTask::class.java))).thenReturn(transaction)
+ `when`(app.iconCache).thenReturn(iconCache)
+ `when`(launcherModel.modelDbController).thenReturn(FactitiousDbController(context))
+ `when`(app.invariantDeviceProfile).thenReturn(idp)
+ `when`(launcherBinder.newIdleLock(any(LoaderTask::class.java))).thenReturn(idleLock)
+ `when`(idleLock.awaitLocked(1000)).thenReturn(false)
+ `when`(iconCache.updateHandler).thenReturn(iconCacheUpdateHandler)
+ `when`(appWidgetManager.getInstalledProvidersForProfile(any(UserHandle::class.java)))
+ .thenReturn(emptyList())
+ }
+
+ @Test
+ fun loadsDataProperly() =
+ with(BgDataModel()) {
+ LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder)
+ .runSyncOnBackgroundThread()
+ Truth.assertThat(workspaceItems.size).isAtLeast(25)
+ Truth.assertThat(appWidgets.size).isAtLeast(7)
+ Truth.assertThat(folders.size()).isAtLeast(8)
+ Truth.assertThat(itemsIdMap.size()).isAtLeast(40)
+ }
+
+ @Test
+ fun bindsLoadedDataCorrectly() {
+ LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+ .runSyncOnBackgroundThread()
+
+ verify(launcherBinder).bindWorkspace(true, false)
+ verify(modelDelegate).workspaceLoadComplete()
+ verify(modelDelegate).loadAndBindAllAppsItems(any(), any(), any())
+ verify(launcherBinder).bindAllApps()
+ verify(iconCacheUpdateHandler, times(4)).updateIcons(any(), any<CachingLogic<Any>>(), any())
+ verify(launcherBinder).bindDeepShortcuts()
+ verify(launcherBinder).bindWidgets()
+ verify(modelDelegate).loadAndBindOtherItems(any())
+ verify(iconCacheUpdateHandler).finish()
+ verify(modelDelegate).modelLoadComplete()
+ verify(transaction).commit()
+ }
+}
+
+private fun LoaderTask.runSyncOnBackgroundThread() {
+ val latch = CountDownLatch(1)
+ MODEL_EXECUTOR.execute {
+ run()
+ latch.countDown()
+ }
+ latch.await()
+}
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 f0c4fdb..6c2950c 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -20,21 +20,30 @@
import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS;
import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
+import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT;
+import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT_SMALL;
+
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Typeface;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.os.UserHandle;
import android.view.ViewGroup;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Flags;
import com.android.launcher3.Utilities;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.search.StringMatcherUtility;
import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.views.BaseDragLayer;
@@ -55,6 +64,8 @@
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final StringMatcherUtility.StringMatcher
MATCHER = StringMatcherUtility.StringMatcher.getInstance();
+ private static final UserHandle WORK_HANDLE = new UserHandle(13);
+ private static final int WORK_FLAG = 1;
private static final int ONE_LINE = 1;
private static final int TWO_LINE = 2;
private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT = "Battery Stats";
@@ -81,12 +92,14 @@
private ItemInfoWithIcon mItemInfoWithIcon;
private Context mContext;
private int mLimitedWidth;
+ private AppInfo mGmailAppInfo;
@Before
public void setUp() throws Exception {
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);
@@ -109,6 +122,9 @@
return null;
}
};
+ ComponentName componentName = new ComponentName(mContext,
+ "com.android.launcher3.tests.Activity" + "Gmail");
+ mGmailAppInfo = new AppInfo(componentName, "Gmail", WORK_HANDLE, new Intent());
}
@Test
@@ -358,4 +374,28 @@
assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
+
+ @Test
+ public void applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_SMALL_noBadge() {
+ FlagOp op = FlagOp.NO_OP;
+ // apply the WORK bitmap flag to show work badge
+ mGmailAppInfo.bitmap.flags = op.apply(WORK_FLAG);
+ mBubbleTextView.setDisplay(DISPLAY_SEARCH_RESULT_SMALL);
+
+ mBubbleTextView.applyIconAndLabel(mGmailAppInfo);
+
+ assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(false);
+ }
+
+ @Test
+ public void applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_hasBadge() {
+ FlagOp op = FlagOp.NO_OP;
+ // apply the WORK bitmap flag to show work badge
+ mGmailAppInfo.bitmap.flags = op.apply(WORK_FLAG);
+ mBubbleTextView.setDisplay(DISPLAY_SEARCH_RESULT);
+
+ mBubbleTextView.applyIconAndLabel(mGmailAppInfo);
+
+ assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(true);
+ }
}
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index ad11268..b8ca43f 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -74,7 +74,7 @@
private void evaluateInPortrait() throws Throwable {
mTest.mDevice.setOrientationNatural();
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
- AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
+ AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher, true);
base.evaluate();
mTest.getDevice().pressHome();
}
@@ -82,7 +82,7 @@
private void evaluateInLandscape() throws Throwable {
mTest.mDevice.setOrientationLeft();
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
- AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher);
+ AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher, true);
base.evaluate();
mTest.getDevice().pressHome();
}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index bc53d6d..f37b676 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -16,44 +16,16 @@
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;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import android.content.Intent;
-import android.platform.test.annotations.PlatinumTest;
-
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
-import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.tapl.AllApps;
-import com.android.launcher3.tapl.AppIcon;
-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;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -65,13 +37,6 @@
public static final String MAPS_APP_NAME = "Maps";
public static final String STORE_APP_NAME = "Play Store";
public static final String GMAIL_APP_NAME = "Gmail";
- private static final String READ_DEVICE_CONFIG_PERMISSION =
- "android.permission.READ_DEVICE_CONFIG";
-
- @Rule
- public TISBindRule mTISBindRule = new TISBindRule();
-
- private AutoCloseable mLauncherLayout;
@Before
public void setUp() throws Exception {
@@ -93,14 +58,7 @@
test.waitForResumed("Launcher internal state is still Background");
// Check that we switched to home.
test.mLauncher.getWorkspace();
- AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher);
- }
-
- @After
- public void tearDown() throws Exception {
- if (mLauncherLayout != null) {
- mLauncherLayout.close();
- }
+ AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher, true);
}
// Please don't add negative test cases for methods that fail only after a long wait.
@@ -114,18 +72,6 @@
assertTrue(message, failed);
}
- public static boolean isWorkspaceScrollable(Launcher launcher) {
- return launcher.getWorkspace().getPageCount() > launcher.getWorkspace().getPanelCount();
- }
-
- private int getCurrentWorkspacePage(Launcher launcher) {
- return launcher.getWorkspace().getCurrentPage();
- }
-
- private WidgetsRecyclerView getWidgetsView(Launcher launcher) {
- return WidgetsFullSheet.getWidgetsView(launcher);
- }
-
@Test
public void testDevicePressMenu() throws Exception {
mDevice.pressMenu();
@@ -137,130 +83,6 @@
mLauncher.goHome();
}
- @PlatinumTest(focusArea = "launcher")
- @Test
- public void testWorkspace() throws Exception {
- // Set workspace that includes the chrome Activity app icon on the hotseat.
- LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
- .atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main");
- mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
- reinitializeLauncherData();
-
- final Workspace workspace = mLauncher.getWorkspace();
-
- // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
- executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
- isWorkspaceScrollable(launcher)));
- assertEquals("Initial workspace doesn't have the correct page", workspace.pagesPerScreen(),
- workspace.getPageCount());
- workspace.verifyWorkspaceAppIconIsGone("Chrome app was found on empty workspace", "Chrome");
- workspace.ensureWorkspaceIsScrollable();
-
- executeOnLauncher(
- launcher -> assertEquals(
- "Ensuring workspace scrollable didn't switch to next screen",
- workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
- executeOnLauncher(
- launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
- isWorkspaceScrollable(launcher)));
- assertNotNull("ensureScrollable didn't add Chrome app",
- workspace.getWorkspaceAppIcon("Chrome"));
-
- // Test flinging workspace.
- workspace.flingBackward();
- assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
- executeOnLauncher(
- launcher -> assertEquals("Flinging back didn't switch workspace to page #0",
- 0, getCurrentWorkspacePage(launcher)));
-
- workspace.flingForward();
- executeOnLauncher(
- launcher -> assertEquals("Flinging forward didn't switch workspace to next screen",
- workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
- assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
-
- // Test starting a workspace app.
- final HomeAppIcon app = workspace.getWorkspaceAppIcon("Chrome");
- assertNotNull("No Chrome app in workspace", app);
- }
-
- public static void runIconLaunchFromAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
- allApps.freeze();
- try {
- final AppIcon app = allApps.getAppIcon("TestActivity7");
- assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
- test.executeOnLauncher(launcher -> assertTrue(
- "Launcher activity is the top activity; expecting another activity to be the "
- + "top one",
- test.isInLaunchedApp(launcher)));
- } finally {
- allApps.unfreeze();
- }
- }
-
- @Test
- @PortraitLandscape
- public void testAppIconLaunchFromAllAppsFromHome() throws Exception {
- final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
- assertTrue("Launcher internal state is not All Apps",
- isInState(() -> LauncherState.ALL_APPS));
-
- 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();
- }
-
- @FlakyTest(bugId = 256615483)
- @Test
- @PortraitLandscape
- public void testPressBack() throws Exception {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
- READ_DEVICE_CONFIG_PERMISSION);
- assumeFalse(FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get());
- mLauncher.getWorkspace().switchToAllApps();
- mLauncher.pressBack();
- mLauncher.getWorkspace();
- waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
- startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
- mLauncher.pressBack();
- mLauncher.getWorkspace();
- waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
- }
-
@Test
@PortraitLandscape
public void testAddDeleteShortcutOnHotseat() {
@@ -272,40 +94,4 @@
mLauncher.getWorkspace().deleteAppIcon(
mLauncher.getWorkspace().getHotseatAppIcon(APP_NAME));
}
-
- @Test
- public void testGetAppIconName() {
- HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
- allApps.freeze();
- try {
- // getAppIcon() already verifies that the icon is not null and is the correct icon name.
- allApps.getAppIcon(APP_NAME);
- } finally {
- allApps.unfreeze();
- }
- }
-
- @PlatinumTest(focusArea = "launcher")
- @Test
- public void testAddAndDeletePageAndFling() {
- Workspace workspace = mLauncher.getWorkspace();
- // Get the first app from the hotseat
- HomeAppIcon hotSeatIcon = workspace.getHotseatAppIcon(0);
- String appName = hotSeatIcon.getIconName();
-
- // Add one page by dragging app to page 1.
- workspace.dragIcon(hotSeatIcon, workspace.pagesPerScreen());
- assertEquals("Incorrect Page count Number",
- workspace.pagesPerScreen() * 2,
- workspace.getPageCount());
-
- // Delete one page by dragging app to hot seat.
- workspace.getWorkspaceAppIcon(appName).dragToHotseat(0);
-
- // Refresh workspace to avoid using stale container error.
- workspace = mLauncher.getWorkspace();
- assertEquals("Incorrect Page count Number",
- workspace.pagesPerScreen(),
- workspace.getPageCount());
- }
}
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/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
new file mode 100644
index 0000000..d8ae99c
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -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.launcher3.ui.workspace;
+
+import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.PlatinumTest;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.tapl.HomeAppIcon;
+import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.LauncherLayoutBuilder;
+import com.android.launcher3.util.TestUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test the basic interactions of the Workspace, adding pages, moving the pages and removing pages.
+ */
+public class TaplWorkspaceTest extends AbstractLauncherUiTest {
+
+ private AutoCloseable mLauncherLayout;
+
+ private static boolean isWorkspaceScrollable(Launcher launcher) {
+ return launcher.getWorkspace().getPageCount() > launcher.getWorkspace().getPanelCount();
+ }
+
+ private int getCurrentWorkspacePage(Launcher launcher) {
+ return launcher.getWorkspace().getCurrentPage();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ initialize(this);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mLauncherLayout != null) {
+ mLauncherLayout.close();
+ }
+ }
+
+ /**
+ * Add an icon and add a page to ensure the Workspace is scrollable and also make sure we can
+ * move between workspaces. After, make sure we can launch an app from the Workspace.
+ * @throws Exception if we can't set the defaults icons that will appear at the beginning.
+ */
+ @PlatinumTest(focusArea = "launcher")
+ @Test
+ public void testWorkspace() throws Exception {
+ // Set workspace that includes the chrome Activity app icon on the hotseat.
+ LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
+ .atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main");
+ mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
+ reinitializeLauncherData();
+
+ final Workspace workspace = mLauncher.getWorkspace();
+
+ // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
+ executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
+ isWorkspaceScrollable(launcher)));
+ assertEquals("Initial workspace doesn't have the correct page", workspace.pagesPerScreen(),
+ workspace.getPageCount());
+ workspace.verifyWorkspaceAppIconIsGone("Chrome app was found on empty workspace", "Chrome");
+ workspace.ensureWorkspaceIsScrollable();
+
+ executeOnLauncher(
+ launcher -> assertEquals(
+ "Ensuring workspace scrollable didn't switch to next screen",
+ workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
+ executeOnLauncher(
+ launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
+ isWorkspaceScrollable(launcher)));
+ assertNotNull("ensureScrollable didn't add Chrome app",
+ workspace.getWorkspaceAppIcon("Chrome"));
+
+ // Test flinging workspace.
+ workspace.flingBackward();
+ assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
+ executeOnLauncher(
+ launcher -> assertEquals("Flinging back didn't switch workspace to page #0",
+ 0, getCurrentWorkspacePage(launcher)));
+
+ workspace.flingForward();
+ executeOnLauncher(
+ launcher -> assertEquals("Flinging forward didn't switch workspace to next screen",
+ workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
+ assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
+
+ // Test starting a workspace app.
+ final HomeAppIcon app = workspace.getWorkspaceAppIcon("Chrome");
+ assertNotNull("No Chrome app in workspace", app);
+ }
+
+
+ /**
+ * Similar to {@link TaplWorkspaceTest#testWorkspace} but here we also make sure we can delete
+ * the pages.
+ */
+ @PlatinumTest(focusArea = "launcher")
+ @Test
+ public void testAddAndDeletePageAndFling() {
+ Workspace workspace = mLauncher.getWorkspace();
+ // Get the first app from the hotseat
+ HomeAppIcon hotSeatIcon = workspace.getHotseatAppIcon(0);
+ String appName = hotSeatIcon.getIconName();
+
+ // Add one page by dragging app to page 1.
+ workspace.dragIcon(hotSeatIcon, workspace.pagesPerScreen());
+ assertEquals("Incorrect Page count Number",
+ workspace.pagesPerScreen() * 2,
+ workspace.getPageCount());
+
+ // Delete one page by dragging app to hot seat.
+ workspace.getWorkspaceAppIcon(appName).dragToHotseat(0);
+
+ // Refresh workspace to avoid using stale container error.
+ workspace = mLauncher.getWorkspace();
+ assertEquals("Incorrect Page count Number",
+ workspace.pagesPerScreen(),
+ workspace.getPageCount());
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
index a94dd2e..8670d40 100644
--- a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -42,11 +42,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
import org.mockito.stubbing.Answer
/** Unit tests for {@link DisplayController} */
@@ -56,13 +58,13 @@
private val appContext: Context = ApplicationProvider.getApplicationContext()
- @Mock private lateinit var context: SandboxContext
- @Mock private lateinit var windowManagerProxy: WindowManagerProxy
- @Mock private lateinit var launcherPrefs: LauncherPrefs
- @Mock private lateinit var displayManager: DisplayManager
- @Mock private lateinit var display: Display
- @Mock private lateinit var resources: Resources
- @Mock private lateinit var displayInfoChangeListener: DisplayInfoChangeListener
+ private val context: SandboxContext = mock()
+ private val windowManagerProxy: WindowManagerProxy = mock()
+ private val launcherPrefs: LauncherPrefs = mock()
+ private val displayManager: DisplayManager = mock()
+ private val display: Display = mock()
+ private val resources: Resources = mock()
+ private val displayInfoChangeListener: DisplayInfoChangeListener = mock()
private lateinit var displayController: DisplayController
@@ -88,7 +90,6 @@
@Before
fun setUp() {
- 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)
@@ -112,10 +113,10 @@
whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON)
// Mock context
- whenever(context.createWindowContext(any(), any(), nullable())).thenReturn(context)
+ whenever(context.createWindowContext(any(), any(), anyOrNull())).thenReturn(context)
whenever(context.getSystemService(eq(DisplayManager::class.java)))
.thenReturn(displayManager)
- doNothing().`when`(context).registerComponentCallbacks(any())
+ doNothing().whenever(context).registerComponentCallbacks(any())
// Mock display
whenever(display.rotation).thenReturn(displayInfo.rotation)
diff --git a/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt b/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt
deleted file mode 100644
index c9c9616..0000000
--- a/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util
-
-/**
- * Kotlin versions of popular mockito methods that can return null in situations when Kotlin expects
- * a non-null value. Kotlin will throw an IllegalStateException when this takes place ("x must not
- * be null"). To fix this, we can use methods that modify the return type to be nullable. This
- * causes Kotlin to skip the null checks.
- */
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito
-
-/**
- * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when null is
- * returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
-
-/**
- * Returns Mockito.same() as nullable type to avoid java.lang.IllegalStateException when null is
- * returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> same(obj: T): T = Mockito.same<T>(obj)
-
-/**
- * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when null is
- * returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
-
-inline fun <reified T> any(): T = any(T::class.java)
-
-/** Kotlin type-inferred version of Mockito.nullable() */
-inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
-
-/**
- * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException when
- * null is returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
-
-/**
- * Helper function for creating an argumentCaptor in kotlin.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
- ArgumentCaptor.forClass(T::class.java)
-
-/**
- * Helper function for creating new mocks, without the need to pass in a [Class] instance.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)
-
-/**
- * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
- * kotlin tests are mocking kotlin objects and the methods take non-null parameters:
- * ```
- * java.lang.NullPointerException: capture() must not be null
- * ```
- */
-class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
- private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz)
- fun capture(): T = wrapped.capture()
- val value: T
- get() = wrapped.value
-}
-
-/**
- * Helper function for creating an argumentCaptor in kotlin.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> =
- KotlinArgumentCaptor(T::class.java)
-
-/**
- * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
- *
- * ```
- * val captor = argumentCaptor<Foo>()
- * verify(...).someMethod(captor.capture())
- * val captured = captor.value
- * ```
- *
- * becomes:
- * ```
- * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
- * ```
- *
- * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
- */
-inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
- kotlinArgumentCaptor<T>().apply { block() }.value
diff --git a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
index 92ab2cb..2c4a54f 100644
--- a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
+++ b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -26,29 +26,27 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
/** Unit tests for {@link LockedUserState} */
@SmallTest
@RunWith(AndroidJUnit4::class)
class LockedUserStateTest {
- @Mock lateinit var userManager: UserManager
- @Mock lateinit var context: Context
+ private val userManager: UserManager = mock()
+ private val context: Context = mock()
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
- `when`(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
+ whenever(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
}
@Test
fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
- `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
+ whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
val action: Runnable = mock()
LockedUserState(context).runOnUserUnlocked(action)
verify(action).run()
@@ -56,7 +54,7 @@
@Test
fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
- `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
+ whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
val action: Runnable = mock()
val state = LockedUserState(context)
state.runOnUserUnlocked(action)
@@ -67,13 +65,13 @@
@Test
fun isUserUnlocked_returns_true_when_user_is_unlocked() {
- `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
+ whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
assertThat(LockedUserState(context).isUserUnlocked).isTrue()
}
@Test
fun isUserUnlocked_returns_false_when_user_is_locked() {
- `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
+ whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
assertThat(LockedUserState(context).isUserUnlocked).isFalse()
}
}
diff --git a/tests/src/com/android/launcher3/util/rule/TISBindRule.java b/tests/src/com/android/launcher3/util/rule/TISBindRule.java
deleted file mode 100644
index 3ec4a29..0000000
--- a/tests/src/com/android/launcher3/util/rule/TISBindRule.java
+++ /dev/null
@@ -1,81 +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.launcher3.util.rule;
-
-import android.app.UiAutomation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-public class TISBindRule implements TestRule {
- public static String TAG = "TISBindRule";
- public static String INTENT_FILTER = "android.intent.action.QUICKSTEP_SERVICE";
- public static String TIS_PERMISSIONS = "android.permission.STATUS_BAR_SERVICE";
-
- private String getLauncherPackageName(Context context) {
- return ComponentName.unflattenFromString(context.getString(
- com.android.internal.R.string.config_recentsComponentName)).getPackageName();
- }
-
- private ServiceConnection createConnection() {
- return new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- Log.d(TAG, "Connected to TouchInteractionService");
- }
-
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- Log.d(TAG, "Disconnected from TouchInteractionService");
- }
- };
- }
-
- @NonNull
- @Override
- public Statement apply(@NonNull Statement base, @NonNull Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- final ServiceConnection connection = createConnection();
- UiAutomation uiAutomation =
- InstrumentationRegistry.getInstrumentation().getUiAutomation();
- uiAutomation.adoptShellPermissionIdentity(TIS_PERMISSIONS);
- Intent launchIntent = new Intent(INTENT_FILTER);
- launchIntent.setPackage(getLauncherPackageName(context));
- context.bindService(launchIntent, connection, Context.BIND_AUTO_CREATE);
- uiAutomation.dropShellPermissionIdentity();
- try {
- base.evaluate();
- } finally {
- context.unbindService(connection);
- }
- }
- };
- }
-}
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/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
index 4b65439..51b7b18 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
@@ -62,18 +62,6 @@
+ "NexusOverviewActionsView:id/overview_actions_view|FrameLayout:id"
+ "/select_mode_buttons|ImageButton:id/close",
DRAG_LAYER
- + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
- + "/action_buttons|Button:id/action_screenshot",
- DRAG_LAYER
- + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
- + "/action_buttons|Button:id/action_select",
- DRAG_LAYER
- + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
- + "/action_buttons|Button:id/action_split",
- DRAG_LAYER
- + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
- + "/action_buttons|Space:id/action_split_space",
- DRAG_LAYER
+ "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
+ "/deep_shortcuts_container|DeepShortcutView:id/deep_shortcut_material"
+ "|DeepShortcutTextView:id/bubble_text",
@@ -116,23 +104,14 @@
RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
DRAG_LAYER
+ "NexusOverviewActionsView:id/overview_actions_view"
- + "|LinearLayout:id/action_buttons|Button:id/action_screenshot",
+ + "|LinearLayout:id/action_buttons",
RECENTS_DRAG_LAYER
+ "NexusOverviewActionsView:id/overview_actions_view"
- + "|LinearLayout:id/action_buttons|Button:id/action_screenshot",
+ + "|LinearLayout:id/action_buttons",
+ DRAG_LAYER + "IconView",
DRAG_LAYER
- + "NexusOverviewActionsView:id/overview_actions_view"
- + "|LinearLayout:id/action_buttons|Button:id/action_select",
- RECENTS_DRAG_LAYER
- + "NexusOverviewActionsView:id/overview_actions_view"
- + "|LinearLayout:id/action_buttons|Button:id/action_select",
- DRAG_LAYER
- + "NexusOverviewActionsView:id/overview_actions_view"
- + "|LinearLayout:id/action_buttons|Button:id/action_split",
- RECENTS_DRAG_LAYER
- + "NexusOverviewActionsView:id/overview_actions_view"
- + "|LinearLayout:id/action_buttons|Button:id/action_split",
- DRAG_LAYER + "IconView"
+ + "OptionsPopupView:id/popup_container|DeepShortcutView:id/system_shortcut"
+ + "|BubbleTextView:id/bubble_text"
));
// Minimal increase or decrease of view's alpha between frames that triggers the error.
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
index 8b88ace..e333074 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
@@ -38,7 +38,8 @@
private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of(
CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
- DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView|TextView",
+ DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView",
+ DRAG_LAYER + "LauncherRecentsView:id/overview_panel|ClearAllButton:id/clear_all",
DRAG_LAYER
+ "LauncherAllAppsContainerView:id/apps_view|AllAppsRecyclerView:id"
+ "/apps_list_view|BubbleTextView:id/icon",
@@ -53,7 +54,9 @@
+ "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
+ "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell"
+ "|WidgetCellPreview:id/widget_preview_container|ImageView:id/widget_badge",
- RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView|IconView:id/icon",
+ RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView",
+ RECENTS_DRAG_LAYER
+ + "FallbackRecentsView:id/overview_panel|ClearAllButton:id/clear_all",
DRAG_LAYER + "SearchContainerView:id/apps_view",
DRAG_LAYER + "LauncherDragView",
DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail",
@@ -64,7 +67,8 @@
+ "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container|LinearLayout:id"
+ "/linear_layout_container|FrameLayout:id/recycler_view_container"
+ "|FrameLayout:id/widgets_two_pane_sheet_recyclerview|WidgetsRecyclerView:id"
- + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header"
+ + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header",
+ DRAG_LAYER + "NexusOverviewActionsView:id/overview_actions_view"
));
// Per-AnalysisNode data that's specific to this detector.
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index b82fa35..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;
@@ -370,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/KeyboardQuickSwitch.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
index 2a98a24..a1d8059 100644
--- a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
@@ -37,7 +37,6 @@
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");
@@ -50,7 +49,10 @@
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(
+ private static final Pattern EVENT_KQS_ALT_LEFT_UP = Pattern.compile(
+ "KeyboardQuickSwitchView key event: KeyEvent.*?action=ACTION_UP"
+ + ".*?keyCode=KEYCODE_ALT_LEFT");
+ private static final Pattern EVENT_HOME_ALT_LEFT_UP = Pattern.compile(
"Key event: KeyEvent.*?action=ACTION_UP"
+ ".*?keyCode=KEYCODE_ALT_LEFT");
@@ -82,22 +84,21 @@
*/
public KeyboardQuickSwitch moveFocusForward() {
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
- "want to move keyboard quick switch focus forward")) {
+ "want to move keyboard quick switch focus forward");
+ LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
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));
+ 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);
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "pressed alt+tab")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
- return this;
- }
+ return this;
}
}
}
@@ -117,23 +118,22 @@
*/
public KeyboardQuickSwitch moveFocusBackward() {
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
- "want to move keyboard quick switch focus backward")) {
+ "want to move keyboard quick switch focus backward");
+ LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
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));
+ 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);
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "pressed alt+shift+tab")) {
+ mLauncher.waitForLauncherObject(KEYBOARD_QUICK_SWITCH_RES_ID);
- return this;
- }
+ return this;
}
}
}
@@ -146,27 +146,28 @@
*/
public void dismiss() {
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
- "want to dismiss keyboard quick switch view")) {
+ "want to dismiss keyboard quick switch view");
+ LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
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));
+ 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);
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "pressed alt+esc")) {
+ mLauncher.waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
- // Verify the final state is the same as the initial state
- mLauncher.verifyContainerType(mStartingContainerType);
+ // Verify the final state is the same as the initial state
+ mLauncher.verifyContainerType(mStartingContainerType);
+
+ // Wait until the device has fully settled before unpressing the key code
+ if (mExpectHomeKeyEventsOnDismiss) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_HOME_ALT_LEFT_UP);
}
+ mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
}
}
}
@@ -180,7 +181,9 @@
* @param expectedPackageName the package name of the expected launched app
*/
public LaunchedAppState launchFocusedAppTask(@NonNull String expectedPackageName) {
- return (LaunchedAppState) launchFocusedTask(expectedPackageName);
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ return (LaunchedAppState) launchFocusedTask(expectedPackageName);
+ }
}
/**
@@ -190,22 +193,29 @@
* {@link #launchFocusedAppTask(String)}.
*/
public Overview launchFocusedOverviewTask() {
- return (Overview) launchFocusedTask(null);
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ return (Overview) launchFocusedTask(null);
+ }
}
private LauncherInstrumentation.VisibleContainer launchFocusedTask(
@Nullable String expectedPackageName) {
- try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"want to launch focused task: "
+ (expectedPackageName == null ? "Overview" : expectedPackageName))) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KQS_ALT_LEFT_UP);
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();
+ try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+ "un-pressed left alt")) {
+ 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
index b7e3d38..677ed04 100644
--- a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitchSource.java
@@ -32,7 +32,8 @@
LauncherInstrumentation launcher = getLauncher();
try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer(
- "want to show keyboard quick switch object")) {
+ "want to show keyboard quick switch object");
+ LauncherInstrumentation.Closable e = launcher.eventsCheck()) {
launcher.pressAndHoldKeyCode(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_LEFT_ON);
try (LauncherInstrumentation.Closable c2 = launcher.addContextLayer(
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 130bd3a..d7f9c78 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -704,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.
*/
@@ -1343,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);
@@ -1779,7 +1790,6 @@
private void injectEvent(InputEvent event) {
assertTrue("injectInputEvent failed: event=" + event,
mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
- event.recycle();
}
public void sendPointer(long downTime, long currentTime, int action, Point point,
@@ -1812,8 +1822,8 @@
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) {
@@ -2043,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);
@@ -2057,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() {
@@ -2202,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/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/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.
*