From e4ca19100107ab965bac0a2f0b3df85dd578297e Mon Sep 17 00:00:00 2001 From: Shamali P Date: Mon, 17 Jun 2024 14:37:44 +0000 Subject: [PATCH 1/2] [part-1] Perform doMeasure only once in WidgetsFullSheet We already know the details used to calculate the insets while in onMeasure, so, instead of measuring everything twice, we initialize the maxSpansPerRow in onMeasure and then call doMeasure. Bug: 346341156 Flag: EXEMPT bugfix Test: Manual and screenshot Change-Id: If080f1f91c69d2d54139d876c36eac8743a224ee --- .../launcher3/widget/BaseWidgetSheet.java | 19 +++++++--- .../widget/picker/WidgetsFullSheet.java | 37 +++++++++---------- .../widget/picker/WidgetsTwoPaneSheet.java | 29 ++++++++++++--- 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java index 13680846f9..c59e295c5f 100644 --- a/src/com/android/launcher3/widget/BaseWidgetSheet.java +++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java @@ -331,8 +331,21 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView * status bar, into account. */ protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthUsed = getInsetsWidth(); + DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); + measureChildWithMargins(mContent, widthMeasureSpec, + widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding); + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.getSize(heightMeasureSpec)); + } + + /** + * Returns the width used on left and right by the insets / padding. + */ + protected int getInsetsWidth() { int widthUsed; + DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); if (deviceProfile.isTablet) { widthUsed = Math.max(2 * getTabletHorizontalMargin(deviceProfile), 2 * (mInsets.left + mInsets.right)); @@ -343,11 +356,7 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView widthUsed = Math.max(padding.left + padding.right, 2 * (mInsets.left + mInsets.right)); } - - measureChildWithMargins(mContent, widthMeasureSpec, - widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding); - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), - MeasureSpec.getSize(heightMeasureSpec)); + return widthUsed; } /** Returns the horizontal margins to be applied to the widget sheet. **/ diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index 9929892e28..fd15677ab7 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -55,7 +55,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.compat.AccessibilityManagerCompat; @@ -416,19 +415,18 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int availableWidth = MeasureSpec.getSize(widthMeasureSpec); + updateMaxSpansPerRow(availableWidth); doMeasure(widthMeasureSpec, heightMeasureSpec); - - if (updateMaxSpansPerRow()) { - doMeasure(widthMeasureSpec, heightMeasureSpec); - } } - /** Returns {@code true} if the max spans have been updated. */ - private boolean updateMaxSpansPerRow() { - if (getMeasuredWidth() == 0) return false; - - @Px int maxHorizontalSpan = getContentView().getMeasuredWidth() - - (2 * mContentHorizontalMargin); + /** Returns {@code true} if the max spans have been updated. + * + * @param availableWidth Total width available within parent (includes insets). + */ + private void updateMaxSpansPerRow(int availableWidth) { + @Px int maxHorizontalSpan = getAvailableWidthForSuggestions( + availableWidth - getInsetsWidth()); if (mMaxSpanPerRow != maxHorizontalSpan) { mMaxSpanPerRow = maxHorizontalSpan; mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow( @@ -439,16 +437,15 @@ public class WidgetsFullSheet extends BaseWidgetSheet mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow( maxHorizontalSpan); } - onRecommendedWidgetsBound(); - return true; + post(this::onRecommendedWidgetsBound); } - return false; } - protected View getContentView() { - return mHasWorkProfile - ? mViewPager - : mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView; + /** + * Returns the width available to display suggestions. + */ + protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) { + return pickerAvailableWidth - (2 * mContentHorizontalMargin); } @Override @@ -493,7 +490,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet .mWidgetsListAdapter.hasVisibleEntries()); if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) { mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded; - onRecommendedWidgetsBound(); + post(this::onRecommendedWidgetsBound); } } @@ -549,7 +546,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE); // Visibility of recommended widgets, recycler views and headers are handled in methods // below. - onRecommendedWidgetsBound(); + post(this::onRecommendedWidgetsBound); onWidgetsBound(); } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java index 5d71db6e39..7eaa0f8476 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java @@ -312,6 +312,30 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { * RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE; } + @Override + @Px + protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) { + int rightPaneWidth = (int) Math.ceil(0.67 * pickerAvailableWidth); + + if (mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) { + // See onLayout + int leftPaneWidth = (int) (0.33 * pickerAvailableWidth); + @Px int minLeftPaneWidthPx = Utilities.dpToPx(MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP); + @Px int maxLeftPaneWidthPx = Utilities.dpToPx(MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP); + if (leftPaneWidth < minLeftPaneWidthPx) { + leftPaneWidth = minLeftPaneWidthPx; + } else if (leftPaneWidth > maxLeftPaneWidthPx) { + leftPaneWidth = maxLeftPaneWidthPx; + } + rightPaneWidth = pickerAvailableWidth - leftPaneWidth; + } + + // Since suggestions are shown in right pane, the available width is 2/3 of total width of + // bottom sheet. + return rightPaneWidth - getResources().getDimensionPixelSize( + R.dimen.widget_list_horizontal_margin_two_pane); // right pane end margin. + } + @Override public void onActivePageChanged(int currentActivePage) { super.onActivePageChanged(currentActivePage); @@ -372,11 +396,6 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet { } - @Override - protected View getContentView() { - return mRightPane; - } - private HeaderChangeListener getHeaderChangeListener() { return new HeaderChangeListener() { @Override From 558afdafe2134da5da91e955daf655c8a521cec2 Mon Sep 17 00:00:00 2001 From: Shamali P Date: Mon, 24 Jun 2024 16:11:57 +0000 Subject: [PATCH 2/2] [part 2] Update standalone picker to align widget loading and animations In usual picker, widgets are already available before picker opens, so, the work that happens during animations doesn't interrupt animations on the scale it interrupt animations in standalone picker. Bug: 346341156 Flag: EXEMPT bugfix Test: Manual / see before after video Change-Id: I7c8701447bd519b306799b28341d8f2f03daf59f --- .../launcher3/WidgetPickerActivity.java | 27 +++++++++++++------ .../model/WidgetPredictionsRequester.java | 16 +++++++---- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java index 7e52ea1372..7cdca746da 100644 --- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java +++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java @@ -120,10 +120,6 @@ public class WidgetPickerActivity extends BaseActivity { WindowInsetsController wc = mDragLayer.getWindowInsetsController(); wc.hide(navigationBars() + statusBars()); - BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true); - widgetSheet.disableNavBarScrim(true); - widgetSheet.addOnCloseListener(this::finish); - parseIntentExtras(); refreshAndBindWidgets(); } @@ -224,9 +220,10 @@ public class WidgetPickerActivity extends BaseActivity { }; } - /** Updates the model with widgets and provides them after applying the provided filter. */ + /** Updates the model with widgets, applies filters and launches the widgets sheet once + * widgets are available */ private void refreshAndBindWidgets() { - MODEL_EXECUTOR.execute(() -> { + MODEL_EXECUTOR.getHandler().postDelayed(() -> { LauncherAppState app = LauncherAppState.getInstance(this); mModel.update(app, null); final List allWidgets = @@ -240,6 +237,9 @@ public class WidgetPickerActivity extends BaseActivity { } ); bindWidgets(allWidgets); + // Open sheet once widgets are available, so that it doesn't interrupt the open + // animation. + openWidgetsSheet(); if (mUiSurface != null) { Map allWidgetItems = allWidgets.stream() .filter(entry -> entry instanceof WidgetsListContentEntry) @@ -253,15 +253,26 @@ public class WidgetPickerActivity extends BaseActivity { mUiSurface, allWidgetItems); mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets); } - }); + }, mDeviceProfile.bottomSheetOpenDuration); } private void bindWidgets(List widgets) { MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets)); } + private void openWidgetsSheet() { + MAIN_EXECUTOR.execute(() -> { + BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true); + widgetSheet.disableNavBarScrim(true); + widgetSheet.addOnCloseListener(this::finish); + }); + } + private void bindRecommendedWidgets(List recommendedWidgets) { - MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets)); + // Bind recommendations once picker has finished open animation. + MAIN_EXECUTOR.getHandler().postDelayed( + () -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets), + mDeviceProfile.bottomSheetOpenDuration); } @Override diff --git a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java index 5730273d88..41fcf61d81 100644 --- a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java +++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java @@ -65,6 +65,7 @@ public class WidgetPredictionsRequester { private final Context mContext; @NonNull private final String mUiSurface; + private boolean mPredictionsAvailable; @NonNull private final Map mAllWidgets; @@ -76,8 +77,8 @@ public class WidgetPredictionsRequester { } /** - * Requests predictions from the app predictions manager and registers the provided callback to - * receive updates when predictions are available. + * Requests one time predictions from the app predictions manager and invokes provided callback + * once predictions are available. * * @param existingWidgets widgets that are currently added to the surface; * @param callback consumer of prediction results to be called when predictions are @@ -159,10 +160,14 @@ public class WidgetPredictionsRequester { @WorkerThread private void bindPredictions(List targets, Predicate filter, Consumer> callback) { - List filteredPredictions = filterPredictions(targets, mAllWidgets, filter); - List mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions); + if (!mPredictionsAvailable) { + mPredictionsAvailable = true; + List filteredPredictions = filterPredictions(targets, mAllWidgets, filter); + List mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions); - MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions)); + MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions)); + MODEL_EXECUTOR.execute(this::clear); + } } /** @@ -214,5 +219,6 @@ public class WidgetPredictionsRequester { mAppPredictor.destroy(); mAppPredictor = null; } + mPredictionsAvailable = false; } }