Merge "Moving various nav-bar configs out of feature flags" into main
diff --git a/quickstep/res/layout/task_menu_with_arrow.xml b/quickstep/res/layout/task_menu_with_arrow.xml
index 38573fd..c9108a5 100644
--- a/quickstep/res/layout/task_menu_with_arrow.xml
+++ b/quickstep/res/layout/task_menu_with_arrow.xml
@@ -23,11 +23,17 @@
android:orientation="vertical"
android:visibility="invisible">
- <LinearLayout
- android:id="@+id/menu_option_layout"
+ <ScrollView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:showDividers="middle" />
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:id="@+id/menu_option_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:showDividers="middle" />
+
+ </ScrollView>
</com.android.quickstep.views.TaskMenuViewWithArrow>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index bae30e3..a2c1b07 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -241,7 +241,8 @@
// we're on the main executor now, so check that the overflow hasn't been created
// again to avoid races.
if (mOverflowBubble == null) {
- mBubbleBarViewController.addBubble(overflow);
+ mBubbleBarViewController.addBubble(
+ overflow, /* isExpanding= */ false, /* suppressAnimation= */ true);
mOverflowBubble = overflow;
}
});
@@ -310,6 +311,10 @@
private void applyViewChanges(BubbleBarViewUpdate update) {
final boolean isCollapsed = (update.expandedChanged && !update.expanded)
|| (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
+ final boolean isExpanding = update.expandedChanged && update.expanded;
+ // don't animate bubbles if this is the initial state because we may be unfolding or
+ // enabling gesture nav
+ final boolean suppressAnimation = update.initialState;
BubbleBarItem previouslySelectedBubble = mSelectedBubble;
BubbleBarBubble bubbleToSelect = null;
if (!update.removedBubbles.isEmpty()) {
@@ -326,7 +331,7 @@
}
if (update.addedBubble != null) {
mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
- mBubbleBarViewController.addBubble(update.addedBubble);
+ mBubbleBarViewController.addBubble(update.addedBubble, isExpanding, suppressAnimation);
if (isCollapsed) {
// If we're collapsed, the most recently added bubble will be selected.
bubbleToSelect = update.addedBubble;
@@ -339,7 +344,7 @@
BubbleBarBubble bubble = update.currentBubbles.get(i);
if (bubble != null) {
mBubbles.put(bubble.getKey(), bubble);
- mBubbleBarViewController.addBubble(bubble);
+ mBubbleBarViewController.addBubble(bubble, isExpanding, suppressAnimation);
if (isCollapsed) {
// If we're collapsed, the most recently added bubble will be selected.
bubbleToSelect = bubble;
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 06769c5..aa1b4df 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -329,16 +329,21 @@
/**
* Adds the provided bubble to the bubble bar.
*/
- public void addBubble(BubbleBarItem b) {
+ public void addBubble(BubbleBarItem b, boolean isExpanding, boolean suppressAnimation) {
if (b != null) {
mBarView.addView(b.getView(), 0,
new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
b.getView().setOnClickListener(mBubbleClickListener);
mBubbleDragController.setupBubbleView(b.getView());
+ if (suppressAnimation) {
+ return;
+ }
+
boolean isStashedOrGone =
mBubbleStashController.isStashed() || mBarView.getVisibility() != VISIBLE;
- if (b instanceof BubbleBarBubble && isStashedOrGone) {
+ // don't animate the new bubble if we're auto expanding from stashed
+ if (b instanceof BubbleBarBubble && isStashedOrGone && !isExpanding) {
mBubbleBarViewAnimator.animateBubbleInForStashed((BubbleBarBubble) b);
}
} else {
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index 0476fe8..1be908b 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -213,7 +213,8 @@
@Override
public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
float taskMenuX, float taskMenuY) {
- return (int) (deviceProfile.availableHeightPx - taskInsetMargin - taskMenuY);
+ return (int) (deviceProfile.heightPx - deviceProfile.getInsets().top - taskMenuY
+ - deviceProfile.getOverviewActionsClaimedSpaceBelow());
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 59bf105..3ed3e40 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -45,7 +45,6 @@
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.allapps.AllAppsStore;
import com.android.launcher3.apppairs.AppPairIcon;
@@ -158,8 +157,6 @@
member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
iconCache.getTitleAndIcon(member, member.usingLowResIcon());
});
- newAppPair.title = getDefaultTitle(newAppPair.getFirstApp().title,
- newAppPair.getSecondApp().title);
MAIN_EXECUTOR.execute(() -> {
LauncherAccessibilityDelegate delegate =
Launcher.getLauncher(mContext).getAccessibilityDelegate();
@@ -489,13 +486,6 @@
}
/**
- * 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);
- }
-
- /**
* Gets the TopTaskTracker, which is a cached record of the top running Task.
*/
@VisibleForTesting
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
index 6431bdf..486fc2c 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetView.java
@@ -305,7 +305,8 @@
*/
public static int getDefaultBackgroundColor(
Context context, RemoteAnimationTarget target) {
- return (target != null && target.taskInfo.taskDescription != null)
+ return (target != null && target.taskInfo != null
+ && target.taskInfo.taskDescription != null)
? target.taskInfo.taskDescription.getBackgroundColor()
: Themes.getColorBackground(context);
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index e86b7d8..956dd26 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -140,9 +140,14 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int maxMenuHeight = calculateMaxHeight();
- if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST);
+ if (!(enableOverviewIconMenu()
+ && ((RecentsView) mActivity.getOverviewPanel()).isOnGridBottomRow(mTaskView))) {
+ // TODO(b/326952853): Cap menu height for grid bottom row in a way that doesn't break
+ // additionalTranslationY.
+ int maxMenuHeight = calculateMaxHeight();
+ if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST);
+ }
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 12b8b6f..dcf681c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -124,6 +124,25 @@
optionLayout = requireViewById(R.id.menu_option_layout)
}
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val maxMenuHeight: Int = calculateMaxHeight()
+ val newHeightMeasureSpec =
+ if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
+ MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST)
+ } else heightMeasureSpec
+ super.onMeasure(widthMeasureSpec, newHeightMeasureSpec)
+ }
+
+ private fun calculateMaxHeight(): Int {
+ val taskInsetMargin = resources.getDimension(R.dimen.task_card_margin)
+ return taskView.pagedOrientationHandler.getTaskMenuHeight(
+ taskInsetMargin,
+ mActivityContext.deviceProfile,
+ translationX,
+ translationY
+ )
+ }
+
private fun populateAndShowForTask(
taskContainer: TaskIdAttributeContainer,
alignedOptionIndex: Int
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
similarity index 100%
rename from quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
diff --git a/quickstep/tests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
diff --git a/quickstep/tests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index ec8e00d..2a54057 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -117,18 +117,21 @@
}
@Test
- public void testSaveAppPairMenuItemExistsOnSplitPair() throws Exception {
+ public void testSaveAppPairMenuItemOrActionExistsOnSplitPair() {
assumeTrue("App pairs feature is currently not enabled, no test needed",
Flags.enableAppPairs());
createAndLaunchASplitPair();
- assertTrue("Save app pair menu item is missing",
- mLauncher.goHome()
- .switchToOverview()
- .getCurrentTask()
- .tapMenu()
- .hasMenuItem("Save app pair"));
+ Overview overview = mLauncher.goHome().switchToOverview();
+ if (mLauncher.isGridOnlyOverviewEnabled() || !mLauncher.isTablet()) {
+ assertTrue("Save app pair menu item is missing",
+ overview.getCurrentTask()
+ .tapMenu()
+ .hasMenuItem("Save app pair"));
+ } else {
+ overview.getOverviewActions().assertHasAction("Save app pair");
+ }
}
@Test
diff --git a/res/layout/widget_recommendations.xml b/res/layout/widget_recommendations.xml
index 531db2e..5879b0f 100644
--- a/res/layout/widget_recommendations.xml
+++ b/res/layout/widget_recommendations.xml
@@ -33,6 +33,9 @@
android:textColor="?attr/widgetPickerTitleColor"
android:textFontWeight="500"
android:textSize="16sp"
+ android:maxLines="1"
+ android:paddingHorizontal="8dp"
+ android:ellipsize="end"
android:visibility="gone" />
<!-- Shown when there are more than one pages -->
<com.android.launcher3.pageindicators.PageIndicatorDots
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 8e82d89..0e955ad 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -110,22 +110,42 @@
// For some reason, app icons have setIncludeFontPadding(false) inside folders, so we set it
// here to match that.
icon.mAppPairName.setIncludeFontPadding(container != DISPLAY_FOLDER);
- icon.mAppPairName.applyLabel(appPairInfo);
+ // Set title text and accessibility title text.
+ icon.updateTitleAndA11yTitle();
- // Set up accessibility
- icon.setContentDescription(icon.getAccessibilityTitle(appPairInfo));
icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
return icon;
}
/**
- * Returns a formatted accessibility title for app pairs.
+ * Updates the title and a11y title of the app pair. Called on creation and when packages
+ * change, to reflect app name changes or user language changes.
*/
- public String getAccessibilityTitle(AppPairInfo appPairInfo) {
- CharSequence app1 = appPairInfo.getFirstApp().title;
- CharSequence app2 = appPairInfo.getSecondApp().title;
- return getContext().getString(R.string.app_pair_name_format, app1, app2);
+ public void updateTitleAndA11yTitle() {
+ updateTitleAndTextView();
+ updateAccessibilityTitle();
+ }
+
+ /**
+ * Updates AppPairInfo with a formatted app pair title, and sets it on the BubbleTextView.
+ */
+ public void updateTitleAndTextView() {
+ CharSequence newTitle = getInfo().generateTitle(getContext());
+ mAppPairName.setText(newTitle);
+ }
+
+ /**
+ * Updates the accessibility title with a formatted string template.
+ */
+ public void updateAccessibilityTitle() {
+ CharSequence app1 = getInfo().getFirstApp().title;
+ CharSequence app2 = getInfo().getSecondApp().title;
+ String a11yTitle = getContext().getString(R.string.app_pair_name_format, app1, app2);
+ setContentDescription(
+ getInfo().shouldDrawAsDisabled(getContext())
+ ? getContext().getString(R.string.disabled_app_label, a11yTitle)
+ : a11yTitle);
}
// Required for DraggableView
@@ -200,6 +220,7 @@
// If either of the app pair icons return true on the predicate (i.e. in the list of
// updated apps), redraw the icon graphic (icon background and both icons).
if (getInfo().anyMatch(itemCheck)) {
+ updateTitleAndA11yTitle();
mIconGraphic.redraw();
}
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index a974133..7809102 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -42,13 +42,7 @@
private val TAG = "AppPairIconGraphic"
companion object {
- /**
- * Composes a drawable for this icon, consisting of a background and 2 app icons. The app
- * pair will draw as "disabled" if either of the following is true:
- * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is
- * paused or can't be launched for some other reason).
- * 2) One of the member apps can't be launched due to screen size requirements.
- */
+ /** Composes a drawable for this icon, consisting of a background and 2 app icons. */
@JvmStatic
fun composeDrawable(
appPairInfo: AppPairInfo,
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index d27be72..a900d1f 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -31,6 +31,7 @@
import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.Utilities
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
+import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.logging.FileLog
import com.android.launcher3.model.data.AppPairInfo
import com.android.launcher3.model.data.FolderInfo
@@ -372,6 +373,12 @@
// If we generated a placeholder Folder before this point, it may need to be replaced with
// an app pair.
if (c.itemType == Favorites.ITEM_TYPE_APP_PAIR && collection is FolderInfo) {
+ if (!FeatureFlags.enableAppPairs()) {
+ // If app pairs are not enabled, stop loading.
+ Log.e(TAG, "app pairs flag is off, did not load app pair")
+ return
+ }
+
val folderInfo: FolderInfo = collection
val newAppPair = AppPairInfo()
// Move the placeholder's contents over to the new app pair.
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index 63c77bb..fad365c 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -18,6 +18,7 @@
import android.content.Context
import com.android.launcher3.LauncherSettings
+import com.android.launcher3.R
import com.android.launcher3.icons.IconCache
import com.android.launcher3.logger.LauncherAtom
import com.android.launcher3.views.ActivityContext
@@ -81,6 +82,24 @@
}
}
+ /**
+ * App pairs will draw as "disabled" if either of the following is true:
+ * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is paused
+ * or can't be launched for some other reason).
+ * 2) One of the member apps can't be launched due to screen size requirements.
+ */
+ fun shouldDrawAsDisabled(context: Context): Boolean {
+ return isDisabled || !isLaunchable(context)
+ }
+
+ /** Generates a default title for the app pair and sets it. */
+ fun generateTitle(context: Context): CharSequence? {
+ val app1: CharSequence? = getFirstApp().title
+ val app2: CharSequence? = getSecondApp().title
+ title = context.getString(R.string.app_pair_default_title, app1, app2)
+ return title
+ }
+
/** Generates an ItemInfo for logging. */
override fun buildProto(cInfo: CollectionInfo?): LauncherAtom.ItemInfo {
val appPairIcon = LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size)
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index 255a6d2..4f73e66 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -18,7 +18,9 @@
import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering;
+import android.content.ComponentName;
import android.content.Context;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,14 +33,19 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* A {@link PagedView} that displays widget recommendations in categories with dots as paged
@@ -46,6 +53,8 @@
*/
public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots> {
private @Px float mAvailableHeight = Float.MAX_VALUE;
+ private static final String INITIALLY_DISPLAYED_WIDGETS_STATE_KEY =
+ "widgetRecommendationsView:mDisplayedWidgets";
private static final int MAX_CATEGORIES = 3;
private TextView mRecommendationPageTitle;
private final List<String> mCategoryTitles = new ArrayList<>();
@@ -57,6 +66,7 @@
private OnLongClickListener mWidgetCellOnLongClickListener;
@Nullable
private OnClickListener mWidgetCellOnClickListener;
+ private Set<ComponentName> mDisplayedWidgets = Collections.emptySet();
public WidgetRecommendationsView(Context context) {
this(context, /* attrs= */ null);
@@ -76,6 +86,38 @@
mRecommendationPageTitle = parent.findViewById(R.id.recommendations_page_title);
}
+ /**
+ * Saves the necessary state in the provided bundle. To be called in case of orientation /
+ * other config changes.
+ */
+ public void saveState(Bundle bundle) {
+ // Save the widgets that were displayed, so that, on rotation / fold / unfold, we can
+ // maintain the "initial" set of widgets that user first saw (if they fit).
+ bundle.putParcelableArrayList(INITIALLY_DISPLAYED_WIDGETS_STATE_KEY,
+ new ArrayList<>(mDisplayedWidgets));
+ }
+
+ /**
+ * Restores the state that was saved by the saveState method during orientation / other config
+ * changes.
+ */
+ public void restoreState(Bundle bundle) {
+ ArrayList<ComponentName> componentList;
+ if (Utilities.ATLEAST_T) {
+ componentList = bundle.getParcelableArrayList(
+ INITIALLY_DISPLAYED_WIDGETS_STATE_KEY, ComponentName.class);
+ } else {
+ componentList = bundle.getParcelableArrayList(
+ INITIALLY_DISPLAYED_WIDGETS_STATE_KEY);
+ }
+
+ // Restore the "initial" set of widgets that were displayed, so that, on rotation / fold /
+ // unfold, we can maintain the set of widgets that user first saw (if they fit).
+ if (componentList != null) {
+ mDisplayedWidgets = new HashSet<>(componentList);
+ }
+ }
+
/** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
mWidgetCellOnLongClickListener = onLongClickListener;
@@ -112,10 +154,18 @@
this.mAvailableHeight = availableHeight;
clear();
- int displayedWidgets = maybeDisplayInTable(recommendedWidgets, deviceProfile,
+ Set<ComponentName> displayedWidgets = maybeDisplayInTable(recommendedWidgets,
+ deviceProfile,
availableWidth, cellPadding);
+
+ if (mDisplayedWidgets.isEmpty()) {
+ // Save the widgets shown for the first time user opened the picker; so that, they can
+ // be maintained across orientation changes.
+ mDisplayedWidgets = displayedWidgets;
+ }
+
updateTitleAndIndicator(/* requestedPage= */ 0);
- return displayedWidgets;
+ return displayedWidgets.size();
}
/**
@@ -144,20 +194,21 @@
clear();
int displayedCategories = 0;
- int totalDisplayedWidgets = 0;
+ Set<ComponentName> allDisplayedWidgets = new HashSet<>();
// Render top MAX_CATEGORIES in separate tables. Each table becomes a page.
for (Map.Entry<WidgetRecommendationCategory, List<WidgetItem>> entry :
new TreeMap<>(recommendations).entrySet()) {
// If none of the recommendations for the category could fit in the mAvailableHeight, we
// don't want to add that category; and we look for the next one.
- int displayedCount = maybeDisplayInTable(entry.getValue(), deviceProfile,
+ Set<ComponentName> displayedWidgetsForCategory = maybeDisplayInTable(entry.getValue(),
+ deviceProfile,
availableWidth, cellPadding);
- if (displayedCount > 0) {
+ if (!displayedWidgetsForCategory.isEmpty()) {
mCategoryTitles.add(
context.getResources().getString(entry.getKey().categoryTitleRes));
displayedCategories++;
- totalDisplayedWidgets += displayedCount;
+ allDisplayedWidgets.addAll(displayedWidgetsForCategory);
}
if (displayedCategories == MAX_CATEGORIES) {
@@ -165,11 +216,17 @@
}
}
+ if (mDisplayedWidgets.isEmpty()) {
+ // Save the widgets shown for the first time user opened the picker; so that, they can
+ // be maintained across orientation changes.
+ mDisplayedWidgets = allDisplayedWidgets;
+ }
+
updateTitleAndIndicator(requestedPage);
// For purpose of recommendations section, we don't want paging dots to be halved in two
// pane display, so, we always provide isTwoPanels = "false".
mPageIndicator.setPauseScroll(/*pause=*/false, /*isTwoPanels=*/false);
- return totalDisplayedWidgets;
+ return allDisplayedWidgets.size();
}
private void clear() {
@@ -241,20 +298,25 @@
}
/**
- * Groups the provided recommendations into rows and displays them in a table if at least one
- * fits.
- * <p>Returns false if none of the recommendations could fit.</p>
+ * Groups the provided recommendations into rows and displays ones that fit in a table.
+ * <p>Returns the set of widgets that could fit.</p>
*/
- private int maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
+ private Set<ComponentName> maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
DeviceProfile deviceProfile,
final @Px int availableWidth, final @Px int cellPadding) {
+ List<WidgetItem> filteredRecommendedWidgets = recommendedWidgets;
+ // Show only those widgets that were displayed when user first opened the picker.
+ if (!mDisplayedWidgets.isEmpty()) {
+ filteredRecommendedWidgets = recommendedWidgets.stream().filter(
+ w -> mDisplayedWidgets.contains(w.componentName)).toList();
+ }
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Since we are limited by space, we don't sort recommendations - to show most relevant
// (if possible).
List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
- recommendedWidgets,
+ filteredRecommendedWidgets,
context,
deviceProfile,
availableWidth,
@@ -268,13 +330,17 @@
recommendationsTable.setWidgetCellOnClickListener(mWidgetCellOnClickListener);
recommendationsTable.setWidgetCellLongClickListener(mWidgetCellOnLongClickListener);
- int displayedCount = recommendationsTable.setRecommendedWidgets(rows,
+ List<ArrayList<WidgetItem>> displayedItems = recommendationsTable.setRecommendedWidgets(
+ rows,
deviceProfile, mAvailableHeight);
- if (displayedCount > 0) {
+
+ if (!displayedItems.isEmpty()) {
addView(recommendationsTable);
}
- return displayedCount;
+ return displayedItems.stream().flatMap(
+ items -> items.stream().map(w -> w.componentName))
+ .collect(Collectors.toSet());
}
/** Returns location of a widget cell for displaying the "touch and hold" education tip. */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 85375ee..c3067a5 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -633,15 +633,15 @@
}
@Px
- private float getMaxAvailableHeightForRecommendations() {
+ protected float getMaxAvailableHeightForRecommendations() {
// There isn't enough space to show recommendations in landscape orientation on phones with
// a full sheet design. Tablets use a two pane picker.
- if (!isTwoPane() && mDeviceProfile.isLandscape) {
+ if (mDeviceProfile.isLandscape) {
return 0f;
}
return (mDeviceProfile.heightPx - mDeviceProfile.bottomSheetTopPadding)
- * getRecommendationSectionHeightRatio();
+ * RECOMMENDATION_TABLE_HEIGHT_RATIO;
}
/** b/209579563: "Widgets" header should be focused first. */
@@ -650,14 +650,6 @@
return mHeaderTitle;
}
- /**
- * Ratio of recommendations section with respect to bottom sheet's height on scale of 0 to 1.
- */
- @Px
- protected float getRecommendationSectionHeightRatio() {
- return RECOMMENDATION_TABLE_HEIGHT_RATIO;
- }
-
private void open(boolean animate) {
if (animate) {
if (getPopupContainer().getInsets().bottom > 0) {
@@ -733,6 +725,7 @@
// picker and calls save/restore hierarchy state. We save the state of recommendations
// across those updates.
bundle.putInt(RECOMMENDATIONS_SAVED_STATE_KEY, mRecommendationsCurrentPage);
+ mWidgetRecommendationsView.saveState(bundle);
SparseArray<Parcelable> superState = new SparseArray<>();
super.saveHierarchyState(superState);
bundle.putSparseParcelableArray(SUPER_SAVED_STATE_KEY, superState);
@@ -744,6 +737,7 @@
Bundle state = (Bundle) sparseArray.get(0);
mRecommendationsCurrentPage = state.getInt(
RECOMMENDATIONS_SAVED_STATE_KEY, /*defaultValue=*/0);
+ mWidgetRecommendationsView.restoreState(state);
super.restoreHierarchyState(state.getSparseParcelableArray(SUPER_SAVED_STATE_KEY));
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 563894d..a565780 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -83,14 +83,15 @@
* <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
* last row from the {@code recommendedWidgets} until it fits or only one row left.
*
- * <p>Returns {@code false} if none of the widgets could fit</p>
+ * <p>Returns the list of widgets that could fit</p>
*/
- public int setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
+ public List<ArrayList<WidgetItem>> setRecommendedWidgets(
+ List<ArrayList<WidgetItem>> recommendedWidgets,
DeviceProfile deviceProfile, float recommendationTableMaxHeight) {
List<ArrayList<WidgetItem>> rows = selectRowsThatFitInAvailableHeight(recommendedWidgets,
recommendationTableMaxHeight, deviceProfile);
bindData(rows);
- return rows.stream().mapToInt(ArrayList::size).sum();
+ return rows;
}
private void bindData(List<ArrayList<WidgetItem>> recommendationTable) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 1bf813c..14985bf 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -276,8 +276,15 @@
@Override
@Px
- protected float getRecommendationSectionHeightRatio() {
- return RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE;
+ protected float getMaxAvailableHeightForRecommendations() {
+ if (mRecommendedWidgetsCount > 0) {
+ // If widgets were already selected for display, we show them all on orientation change
+ // in a two pane picker
+ return Float.MAX_VALUE;
+ }
+
+ return (mDeviceProfile.heightPx - mDeviceProfile.bottomSheetTopPadding)
+ * RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE;
}
@Override
diff --git a/tests/Android.bp b/tests/Android.bp
index 13a1cbb..3822ff8 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -194,6 +194,7 @@
"androidx.core_core-animation-testing",
"androidx.test.ext.junit",
"androidx.test.rules",
+ "uiautomator-helpers",
"mockito-robolectric-prebuilt",
"mockito-kotlin2",
"platform-parametric-runner-lib",
diff --git a/tests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
diff --git a/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt b/tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
index 2a0f614..b5707ff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
@@ -50,7 +50,6 @@
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.Workspace;
-import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Assert;
@@ -136,20 +135,13 @@
*/
public static Point[] getCornersAndCenterPositions(LauncherInstrumentation launcher) {
final Point dimensions = launcher.getWorkspace().getIconGridDimensions();
- if (TestStabilityRule.isPresubmit()) {
- // Return only center in presubmit to fit under the presubmit SLO.
- return new Point[]{
- new Point(dimensions.x / 2, dimensions.y / 2)
- };
- } else {
- return new Point[]{
- new Point(0, 1),
- new Point(0, dimensions.y - 2),
- new Point(dimensions.x - 1, 1),
- new Point(dimensions.x - 1, dimensions.y - 2),
- new Point(dimensions.x / 2, dimensions.y / 2)
- };
- }
+ return new Point[]{
+ new Point(0, 1),
+ new Point(0, dimensions.y - 2),
+ new Point(dimensions.x - 1, 1),
+ new Point(dimensions.x - 1, dimensions.y - 2),
+ new Point(dimensions.x / 2, dimensions.y / 2)
+ };
}
/**
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS b/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS
new file mode 100644
index 0000000..775b0c7
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS
@@ -0,0 +1,18 @@
+set noparent
+
+# Bug component: 1481801
+# People who can approve changes for submission
+#
+
+# Widget Picker OWNERS
+zakcohen@google.com
+shamalip@google.com
+wvk@google.com
+
+# For Tests
+vadimt@google.com
+
+# Launcher OWNERS
+captaincole@google.com
+sunnygoyal@google.com
+
diff --git a/tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
diff --git a/tests/src/com/android/launcher3/LauncherIntentTest.java b/tests/src/com/android/launcher3/LauncherIntentTest.java
index a3013c7..d1110c3 100644
--- a/tests/src/com/android/launcher3/LauncherIntentTest.java
+++ b/tests/src/com/android/launcher3/LauncherIntentTest.java
@@ -40,10 +40,6 @@
@Test
public void testAllAppsIntent() {
- // setup by moving to home
- mLauncher.goHome();
- assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
-
// Try executing ALL_APPS intent
executeOnLauncher(launcher -> launcher.onNewIntent(allAppsIntent));
// A-Z view with Main adapter should be loaded
@@ -56,10 +52,6 @@
executeOnLauncher(launcher -> launcher.onNewIntent(allAppsIntent));
// A-Z view with Main adapter should be loaded
assertOnMainAdapterAToZView();
-
- // finish
- mLauncher.goHome();
- assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
}
// Highlights the search bar, then fills text to display the SearchView.
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
index d7c40a0..486a63b 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
@@ -17,6 +17,7 @@
package com.android.launcher3.tapl;
import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
/**
@@ -110,4 +111,12 @@
}
}
}
+
+ /** Asserts that an item matching the given string is present in the overview actions. */
+ public void assertHasAction(String text) {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to check if the action [" + text + "] is present")) {
+ mLauncher.waitForObjectInContainer(mOverviewActions, By.text(text));
+ }
+ }
}