From 002317f60cc460dfbde8c2506d6c0874919a3af0 Mon Sep 17 00:00:00 2001 From: Federico Baron Date: Fri, 3 Mar 2023 00:42:00 -0800 Subject: [PATCH] Add subclass for two pane widget picker Since the regular widget picker and the widget picker for tablet diverge quite a bit, we added a child class for it that extends WidgetsFullSheet.java There should be no functionality changes with this CL, it's purely changes in code structure. Bug: 256684299 Test: Turn on LARGE_SCREEN_WIDGET_PICKER flag and verify nothing changes compared to ag/20508230. Change-Id: Ie0e93534d7b93017cc11a05f67728155d22951f9 --- .../widgets_full_sheet_large_screen.xml | 6 +- .../launcher3/widget/BaseWidgetSheet.java | 2 - .../widget/picker/WidgetsFullSheet.java | 359 ++++++------------ .../widget/picker/WidgetsListAdapter.java | 12 +- .../widget/picker/WidgetsTwoPaneSheet.java | 271 +++++++++++++ 5 files changed, 389 insertions(+), 261 deletions(-) create mode 100644 src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java diff --git a/res/layout/widgets_full_sheet_large_screen.xml b/res/layout/widgets_full_sheet_large_screen.xml index 0626e60cc5..f563baa70e 100644 --- a/res/layout/widgets_full_sheet_large_screen.xml +++ b/res/layout/widgets_full_sheet_large_screen.xml @@ -12,8 +12,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java index cfd241c130..07c6ba4628 100644 --- a/src/com/android/launcher3/widget/BaseWidgetSheet.java +++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java @@ -32,7 +32,6 @@ import android.view.WindowInsets; import android.view.animation.Interpolator; import android.widget.Toast; -import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import androidx.annotation.Px; import androidx.core.view.ViewCompat; @@ -182,7 +181,6 @@ public abstract class BaseWidgetSheet extends AbstractSlideInView * Measures the dimension of this view and its children by taking system insets, navigation bar, * status bar, into account. */ - @GuardedBy("MainThread") protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) { DeviceProfile deviceProfile = mActivityContext.getDeviceProfile(); int widthUsed; diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index bd75ce6061..5293c3dda0 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -30,7 +30,6 @@ import android.content.Context; import android.content.pm.LauncherApps; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Outline; import android.graphics.Rect; import android.os.Build; import android.os.Process; @@ -43,13 +42,10 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.ViewOutlineProvider; import android.view.WindowInsets; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.LinearLayout; import android.widget.TextView; import android.window.BackEvent; @@ -68,10 +64,7 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.model.UserManagerState; import com.android.launcher3.model.WidgetItem; -import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.pm.UserCache; -import com.android.launcher3.recyclerview.ViewHolderBinder; -import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.views.ArrowTipView; import com.android.launcher3.views.RecyclerViewFastScroller; import com.android.launcher3.views.SpringRelativeLayout; @@ -80,8 +73,6 @@ import com.android.launcher3.views.WidgetsEduView; import com.android.launcher3.widget.BaseWidgetSheet; import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener; import com.android.launcher3.widget.model.WidgetsListBaseEntry; -import com.android.launcher3.widget.model.WidgetsListContentEntry; -import com.android.launcher3.widget.model.WidgetsListHeaderEntry; import com.android.launcher3.widget.picker.search.SearchModeListener; import com.android.launcher3.widget.picker.search.WidgetsSearchBar; import com.android.launcher3.widget.util.WidgetsTableUtils; @@ -89,7 +80,6 @@ import com.android.launcher3.workprofile.PersonalWorkPagedView; import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Predicate; import java.util.stream.IntStream; @@ -105,27 +95,22 @@ public class WidgetsFullSheet extends BaseWidgetSheet private static final long EDUCATION_TIP_DELAY_MS = 200; private static final long EDUCATION_DIALOG_DELAY_MS = 500; private static final float VERTICAL_START_POSITION = 0.3f; - private static final int PERSONAL_TAB = 0; - private static final int WORK_TAB = 1; - private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry"; // The widget recommendation table can easily take over the entire screen on devices with small // resolution or landscape on phone. This ratio defines the max percentage of content area that // the table can display. private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f; - private static final String KEY_WIDGETS_EDUCATION_DIALOG_SEEN = "launcher.widgets_education_dialog_seen"; private final UserManagerState mUserManagerState = new UserManagerState(); - - private final boolean mHasWorkProfile; - private final SparseArray mAdapters = new SparseArray(); private final UserHandle mCurrentUser = Process.myUserHandle(); private final Predicate mPrimaryWidgetsFilter = entry -> mCurrentUser.equals(entry.mPkgItem.user); private final Predicate mWorkWidgetsFilter = entry -> !mCurrentUser.equals(entry.mPkgItem.user) && !mUserManagerState.isUserQuiet(entry.mPkgItem.user); + protected final boolean mHasWorkProfile; + protected final SparseArray mAdapters = new SparseArray(); @Nullable private ArrowTipView mLatestEducationalTip; private final OnLayoutChangeListener mLayoutChangeListenerToShowTips = new OnLayoutChangeListener() { @@ -175,52 +160,27 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Px private final int mTabsHeight; @Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView; - @Nullable private PersonalWorkPagedView mViewPager; + @Nullable private WidgetsRecyclerView mCurrentTouchEventRecyclerView; + @Nullable PersonalWorkPagedView mViewPager; private boolean mIsInSearchMode; private boolean mIsNoWidgetsViewNeeded; @Px private int mMaxSpanPerRow; - private TextView mNoWidgetsView; - - private StickyHeaderLayout mSearchScrollView; - private WidgetsRecommendationTableLayout mRecommendedWidgetsTable; - private LinearLayout mSuggestedWidgetsContainer; - private WidgetsListHeader mSuggestedWidgetsHeader; - private View mTabBar; - private View mSearchBarContainer; - private WidgetsSearchBar mSearchBar; - private TextView mHeaderTitle; - private LinearLayout mRightPane; - private FrameLayout mRightPaneScrollView; - private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder; private DeviceProfile mDeviceProfile; - private final boolean mIsTwoPane; private int mOrientation; - private @Nullable WidgetsRecyclerView mCurrentTouchEventRecyclerView; - private RecyclerViewFastScroller mFastScroller; - - private final ViewOutlineProvider mViewOutlineProviderRightPane = new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect( - 0, - 0, - view.getMeasuredWidth(), - view.getMeasuredHeight() - getResources().getDimensionPixelSize( - R.dimen.widget_list_horizontal_margin_large_screen), - view.getResources().getDimensionPixelSize( - R.dimen.widget_list_top_bottom_corner_radius) - ); - } - }; + protected TextView mNoWidgetsView; + protected StickyHeaderLayout mSearchScrollView; + protected WidgetsRecommendationTableLayout mRecommendedWidgetsTable; + protected View mTabBar; + protected View mSearchBarContainer; + protected WidgetsSearchBar mSearchBar; + protected TextView mHeaderTitle; + protected RecyclerViewFastScroller mFastScroller; public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile(); - mIsTwoPane = mDeviceProfile.isTablet - && mDeviceProfile.isLandscape - && LARGE_SCREEN_WIDGET_PICKER.get(); mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1; mOrientation = Launcher.getLauncher(context).getOrientation(); mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY)); @@ -243,37 +203,39 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Override protected void onFinishInflate() { super.onFinishInflate(); + mContent = findViewById(R.id.container); setContentBackgroundWithParent(getContext().getDrawable(R.drawable.bg_widgets_full_sheet), mContent); - mContent.setOutlineProvider(mViewOutlineProvider); mContent.setClipToOutline(true); + setupSheet(); + } + protected void setupSheet() { LayoutInflater layoutInflater = LayoutInflater.from(getContext()); + int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view + : R.layout.widgets_full_sheet_recyclerview; + layoutInflater.inflate(contentLayoutRes, mContent, true); - if (mIsTwoPane) { - layoutInflater.inflate( - mHasWorkProfile - ? R.layout.widgets_full_sheet_paged_view_large_screen - : R.layout.widgets_full_sheet_recyclerview_large_screen, - findViewById(R.id.recycler_view_container), - true); - } else { - layoutInflater.inflate( - mHasWorkProfile - ? R.layout.widgets_full_sheet_paged_view - : R.layout.widgets_full_sheet_recyclerview, - mContent, - true); - } + setupViews(); + mRecommendedWidgetsTable = mSearchScrollView.findViewById(R.id.recommended_widget_table); + mRecommendedWidgetsTable.setWidgetCellLongClickListener(this); + mRecommendedWidgetsTable.setWidgetCellOnClickListener(this); + mHeaderTitle = mSearchScrollView.findViewById(R.id.title); + + onRecommendedWidgetsBound(); + onWidgetsBound(); + setUpEducationViewsIfNeeded(); + } + + protected void setupViews() { + mSearchScrollView = findViewById(R.id.search_and_recommendations_container); + mSearchScrollView.setCurrentRecyclerView(findViewById(R.id.primary_widgets_list_view)); + mNoWidgetsView = findViewById(R.id.no_widgets_text); mFastScroller = findViewById(R.id.fast_scroller); - if (mIsTwoPane) { - mFastScroller.setVisibility(GONE); - } mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup)); - mAdapters.get(AdapterHolder.PRIMARY).setup(findViewById(R.id.primary_widgets_list_view)); mAdapters.get(AdapterHolder.SEARCH).setup(findViewById(R.id.search_widgets_list_view)); if (mHasWorkProfile) { @@ -294,78 +256,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet mViewPager = null; } - mNoWidgetsView = findViewById(R.id.no_widgets_text); - - mSearchScrollView = findViewById(R.id.search_and_recommendations_container); - mSearchScrollView.setCurrentRecyclerView(findViewById(R.id.primary_widgets_list_view)); - - mRecommendedWidgetsTable = mIsTwoPane - ? mContent.findViewById(R.id.recommended_widget_table) - : mSearchScrollView.findViewById(R.id.recommended_widget_table); - - mRecommendedWidgetsTable.setWidgetCellLongClickListener(this); - mRecommendedWidgetsTable.setWidgetCellOnClickListener(this); - - // Add suggested widgets. - if (mIsTwoPane) { - mSuggestedWidgetsContainer = mSearchScrollView.findViewById(R.id.suggestions_header); - - // Inflate the suggestions header. - mSuggestedWidgetsHeader = (WidgetsListHeader) layoutInflater.inflate( - R.layout.widgets_list_row_header_two_pane, - mSuggestedWidgetsContainer, - false); - mSuggestedWidgetsHeader.setExpanded(true); - - PackageItemInfo packageItemInfo = new PackageItemInfo( - /* packageName= */ SUGGESTIONS_PACKAGE_NAME, - Process.myUserHandle()) { - @Override - public boolean usingLowResIcon() { - return false; - } - }; - packageItemInfo.title = getContext().getString(R.string.suggested_widgets_header_title); - WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create( - packageItemInfo, - getContext().getString(R.string.suggested_widgets_header_title), - mActivityContext.getPopupDataProvider().getRecommendedWidgets()) - .withWidgetListShown(); - - mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry); - mSuggestedWidgetsHeader.setIcon( - getContext().getDrawable(R.drawable.widget_suggestions_icon)); - mSuggestedWidgetsHeader.setOnClickListener(view -> { - mSuggestedWidgetsHeader.setExpanded(true); - resetExpandedHeaders(); - mRightPane.removeAllViews(); - mRightPane.addView(mRecommendedWidgetsTable); - }); - mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader); - } - mTabBar = mSearchScrollView.findViewById(R.id.tabs); mSearchBarContainer = mSearchScrollView.findViewById(R.id.search_bar_container); mSearchBar = mSearchScrollView.findViewById(R.id.widgets_search_bar); - mHeaderTitle = mIsTwoPane - ? mContent.findViewById(R.id.title) - : mSearchScrollView.findViewById(R.id.title); - mRightPane = mIsTwoPane ? mContent.findViewById(R.id.right_pane) : null; - if (mRightPane != null) { - mRightPane.setOutlineProvider(mViewOutlineProviderRightPane); - } - mRightPaneScrollView = mIsTwoPane - ? mContent.findViewById(R.id.right_pane_scroll_view) : null; - - mWidgetsListTableViewHolderBinder = - new WidgetsListTableViewHolderBinder(mActivityContext, layoutInflater, this, this); - onRecommendedWidgetsBound(); - onWidgetsBound(); mSearchBar.initialize( mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this); - - setUpEducationViewsIfNeeded(); } private void setDeviceManagementResources() { @@ -380,14 +276,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Override public void onActivePageChanged(int currentActivePage) { - - // if the current active page changes to personal or work we set suggestions - // to be the selected widget - if (mIsTwoPane && mSuggestedWidgetsHeader != null - && (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB)) { - mSuggestedWidgetsHeader.callOnClick(); - } - AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage); WidgetsRecyclerView currentRecyclerView = mAdapters.get(currentActivePage).mWidgetsRecyclerView; @@ -415,14 +303,9 @@ public class WidgetsFullSheet extends BaseWidgetSheet } } - private void updateRecyclerViewVisibility(AdapterHolder adapterHolder) { + protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) { // The first item is always an empty space entry. Look for any more items. boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries(); - - if (mIsTwoPane) { - mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE); - } - adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE); if (adapterHolder.mAdapterType == AdapterHolder.SEARCH) { @@ -468,9 +351,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet super.onAttachedToWindow(); mActivityContext.getAppWidgetHolder().addProviderChangeListener(this); notifyWidgetProvidersChanged(); - if (!mIsTwoPane) { - onRecommendedWidgetsBound(); - } + onRecommendedWidgetsBound(); } @Override @@ -558,14 +439,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet private boolean updateMaxSpansPerRow() { if (getMeasuredWidth() == 0) return false; - View content = mHasWorkProfile - ? mViewPager - : mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView; - if (mIsTwoPane && mRightPane != null) { - content = mRightPane; - } - - @Px int maxHorizontalSpan = content.getMeasuredWidth() - (2 * mContentHorizontalMargin); + @Px int maxHorizontalSpan = getContentView().getMeasuredWidth() + - (2 * mContentHorizontalMargin); if (mMaxSpanPerRow != maxHorizontalSpan) { mMaxSpanPerRow = maxHorizontalSpan; mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow( @@ -582,6 +457,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet return false; } + protected View getContentView() { + return mHasWorkProfile + ? mViewPager + : mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView; + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int width = r - l; @@ -656,19 +537,13 @@ public class WidgetsFullSheet extends BaseWidgetSheet public void onSearchResults(List entries) { mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries); updateRecyclerViewVisibility(mAdapters.get(AdapterHolder.SEARCH)); - if (mIsTwoPane) { - mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.selectFirstHeaderEntry(); - } mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop(); } - private void setViewVisibilityBasedOnSearch(boolean isInSearchMode) { + protected void setViewVisibilityBasedOnSearch(boolean isInSearchMode) { mIsInSearchMode = isInSearchMode; if (isInSearchMode) { mRecommendedWidgetsTable.setVisibility(GONE); - if (mIsTwoPane) { - mSuggestedWidgetsContainer.setVisibility(GONE); - } if (mHasWorkProfile) { mViewPager.setVisibility(GONE); mTabBar.setVisibility(GONE); @@ -680,10 +555,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet mNoWidgetsView.setVisibility(GONE); } else { mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE); - if (mIsTwoPane) { - mSuggestedWidgetsContainer.setVisibility(VISIBLE); - mSuggestedWidgetsHeader.callOnClick(); - } // Visibility of recommended widgets, recycler views and headers are handled in methods // below. onRecommendedWidgetsBound(); @@ -691,7 +562,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet } } - private void resetExpandedHeaders() { + protected void resetExpandedHeaders() { mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.resetExpandedHeader(); mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.resetExpandedHeader(); } @@ -713,14 +584,14 @@ public class WidgetsFullSheet extends BaseWidgetSheet mNoWidgetsView.getText().length(), noWidgetsViewTextBounds); noWidgetsViewHeight = noWidgetsViewTextBounds.height(); } - doMeasure( - makeMeasureSpec(mActivityContext.getDeviceProfile().availableWidthPx, - MeasureSpec.EXACTLY), - makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx, - MeasureSpec.EXACTLY)); - float maxTableHeight = mIsTwoPane ? Float.MAX_VALUE : (mContent.getMeasuredHeight() - - mTabsHeight - getHeaderViewHeight() - - noWidgetsViewHeight) * RECOMMENDATION_TABLE_HEIGHT_RATIO; + if (!isTwoPane()) { + doMeasure( + makeMeasureSpec(mActivityContext.getDeviceProfile().availableWidthPx, + MeasureSpec.EXACTLY), + makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx, + MeasureSpec.EXACTLY)); + } + float maxTableHeight = getMaxTableHeight(noWidgetsViewHeight); List> recommendedWidgetsInTable = WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering( @@ -732,13 +603,21 @@ public class WidgetsFullSheet extends BaseWidgetSheet mRecommendedWidgetsTable.setRecommendedWidgets( recommendedWidgetsInTable, maxTableHeight); } else { - mRecommendedWidgetsTable.setVisibility(GONE); - if (mSuggestedWidgetsContainer != null) { - mSuggestedWidgetsContainer.setVisibility(GONE); - } + hideRecommendations(); } } + protected void hideRecommendations() { + mRecommendedWidgetsTable.setVisibility(GONE); + } + + protected float getMaxTableHeight(float noWidgetsViewHeight) { + return (mContent.getMeasuredHeight() + - mTabsHeight - getHeaderViewHeight() + - noWidgetsViewHeight) + * RECOMMENDATION_TABLE_HEIGHT_RATIO; + } + private void open(boolean animate) { if (animate) { if (getPopupContainer().getInsets().bottom > 0) { @@ -779,37 +658,49 @@ public class WidgetsFullSheet extends BaseWidgetSheet @Override public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - // Disable swipe down when recycler view is scrolling or scroll view is scrolling if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mNoIntercept = false; - WidgetsRecyclerView recyclerView = getRecyclerView(); - RecyclerViewFastScroller scroller = recyclerView.getScrollbar(); - - if (scroller.getThumbOffsetY() >= 0 - && getPopupContainer().isEventOverView(scroller, ev)) { - mNoIntercept = true; - } else if (getPopupContainer().isEventOverView(recyclerView, ev)) { - mNoIntercept = !recyclerView.shouldContainerScroll(ev, getPopupContainer()); - } else if (mIsTwoPane && mRightPaneScrollView != null - && getPopupContainer().isEventOverView(mRightPaneScrollView, ev)) { - mNoIntercept = mRightPaneScrollView.canScrollVertically(-1); - } + mNoIntercept = shouldScroll(ev); if (mSearchBar.isSearchBarFocused() && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)) { mSearchBar.clearSearchBarFocus(); } } + return super.onControllerInterceptTouchEvent(ev); } + protected boolean shouldScroll(MotionEvent ev) { + boolean intercept = false; + WidgetsRecyclerView recyclerView = getRecyclerView(); + RecyclerViewFastScroller scroller = recyclerView.getScrollbar(); + // Disable swipe down when recycler view is scrolling + if (scroller.getThumbOffsetY() >= 0 && getPopupContainer().isEventOverView(scroller, ev)) { + intercept = true; + } else if (getPopupContainer().isEventOverView(recyclerView, ev)) { + intercept = !recyclerView.shouldContainerScroll(ev, getPopupContainer()); + } + return intercept; + } + /** Shows the {@link WidgetsFullSheet} on the launcher. */ public static WidgetsFullSheet show(Launcher launcher, boolean animate) { - WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater() - .inflate(LARGE_SCREEN_WIDGET_PICKER.get() - && launcher.getDeviceProfile().isTablet - && launcher.getDeviceProfile().isLandscape - ? R.layout.widgets_full_sheet_large_screen - : R.layout.widgets_full_sheet, launcher.getDragLayer(), false); + boolean isTwoPane = LARGE_SCREEN_WIDGET_PICKER.get() + && launcher.getDeviceProfile().isTablet + && launcher.getDeviceProfile().isLandscape; + + WidgetsFullSheet sheet; + if (isTwoPane) { + sheet = (WidgetsTwoPaneSheet) launcher.getLayoutInflater().inflate( + R.layout.widgets_full_sheet_large_screen, + launcher.getDragLayer(), + false); + } else { + sheet = (WidgetsFullSheet) launcher.getLayoutInflater().inflate( + R.layout.widgets_full_sheet, + launcher.getDragLayer(), + false); + } + sheet.attachToContainer(); sheet.mIsOpen = true; sheet.open(animate); @@ -973,7 +864,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet || Utilities.isRunningInTestHarness(); } - private void setUpEducationViewsIfNeeded() { + protected void setUpEducationViewsIfNeeded() { if (!hasSeenEducationDialog()) { postDelayed(() -> { WidgetsEduView eduDialog = showEducationDialog(); @@ -991,14 +882,18 @@ public class WidgetsFullSheet extends BaseWidgetSheet } } + protected boolean isTwoPane() { + return false; + } + /** A holder class for holding adapters & their corresponding recycler view. */ - private final class AdapterHolder { + final class AdapterHolder { static final int PRIMARY = 0; static final int WORK = 1; static final int SEARCH = 2; private final int mAdapterType; - private final WidgetsListAdapter mWidgetsListAdapter; + final WidgetsListAdapter mWidgetsListAdapter; private final DefaultItemAnimator mWidgetsListItemAnimator; private WidgetsRecyclerView mWidgetsRecyclerView; @@ -1006,43 +901,14 @@ public class WidgetsFullSheet extends BaseWidgetSheet AdapterHolder(int adapterType) { mAdapterType = adapterType; Context context = getContext(); - HeaderChangeListener headerChangeListener = new HeaderChangeListener() { - @Override - public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) { - WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider() - .getSelectedAppWidgets(selectedHeader); - if (contentEntry == null || mRightPane == null) { - return; - } - - if (mSuggestedWidgetsHeader != null) { - mSuggestedWidgetsHeader.setExpanded(false); - } - WidgetsRowViewHolder widgetsRowViewHolder = - mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane); - mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder, - contentEntry, - ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST, - Collections.EMPTY_LIST); - widgetsRowViewHolder.mDataCallback = data -> { - mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder, - contentEntry.withMaxSpanSize(mMaxSpanPerRow), - ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST, - Collections.singletonList(data)); - }; - - mRightPane.removeAllViews(); - mRightPane.addView(widgetsRowViewHolder.itemView); - } - }; mWidgetsListAdapter = new WidgetsListAdapter( context, LayoutInflater.from(context), this::getEmptySpaceHeight, /* iconClickListener= */ WidgetsFullSheet.this, /* iconLongClickListener= */ WidgetsFullSheet.this, - mIsTwoPane ? headerChangeListener : null); + isTwoPane()); mWidgetsListAdapter.setHasStableIds(true); switch (mAdapterType) { case PRIMARY: @@ -1073,7 +939,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet mWidgetsRecyclerView.bindFastScrollbar(mFastScroller); mWidgetsRecyclerView.setItemAnimator(mWidgetsListItemAnimator); mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this); - if (!mIsTwoPane) { + if (!isTwoPane()) { mWidgetsRecyclerView.setEdgeEffectFactory( ((SpringRelativeLayout) mContent).createEdgeEffectFactory()); } @@ -1086,15 +952,4 @@ public class WidgetsFullSheet extends BaseWidgetSheet mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(mMaxSpanPerRow); } } - - /** - * This is a listener for when the selected header gets changed in the left pane. - */ - public interface HeaderChangeListener { - /** - * Sets the right pane to have the widgets for the currently selected header from - * the left pane. - */ - void onHeaderChanged(@NonNull PackageUserKey selectedHeader); - } } diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index c89eea8521..723ea17147 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -89,7 +89,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC private final SparseArray mViewHolderBinders = new SparseArray<>(); private final WidgetListBaseRowEntryComparator mRowComparator = new WidgetListBaseRowEntryComparator(); - @Nullable private final WidgetsFullSheet.HeaderChangeListener mHeaderChangeListener; + @Nullable private WidgetsTwoPaneSheet.HeaderChangeListener mHeaderChangeListener; private final List mAllEntries = new ArrayList<>(); private ArrayList mVisibleEntries = new ArrayList<>(); @@ -107,8 +107,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC public WidgetsListAdapter(Context context, LayoutInflater layoutInflater, IntSupplier emptySpaceHeightProvider, OnClickListener iconClickListener, OnLongClickListener iconLongClickListener, - WidgetsFullSheet.HeaderChangeListener headerChangeListener) { - mHeaderChangeListener = headerChangeListener; + boolean isTwoPane) { mContext = context; mMaxHorizontalSpan = WidgetSizes.getWidgetSizePx( ActivityContext.lookupContext(context).getDeviceProfile(), @@ -122,12 +121,17 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC VIEW_TYPE_WIDGETS_HEADER, new WidgetsListHeaderViewHolderBinder( layoutInflater, /* onHeaderClickListener= */ this, - headerChangeListener != null)); + isTwoPane)); mViewHolderBinders.put( VIEW_TYPE_WIDGETS_SPACE, new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider)); } + public void setHeaderChangeListener(WidgetsTwoPaneSheet.HeaderChangeListener + headerChangeListener) { + mHeaderChangeListener = headerChangeListener; + } + @Override public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { mRecyclerView = recyclerView; diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java new file mode 100644 index 0000000000..de7497a1a4 --- /dev/null +++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java @@ -0,0 +1,271 @@ +/* + * 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.widget.picker; + +import android.content.Context; +import android.graphics.Outline; +import android.os.Process; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; + +import com.android.launcher3.R; +import com.android.launcher3.model.data.PackageItemInfo; +import com.android.launcher3.recyclerview.ViewHolderBinder; +import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.widget.model.WidgetsListBaseEntry; +import com.android.launcher3.widget.model.WidgetsListContentEntry; +import com.android.launcher3.widget.model.WidgetsListHeaderEntry; + +import java.util.Collections; +import java.util.List; + +/** + * Popup for showing the full list of available widgets with a two-pane layout. + */ +public class WidgetsTwoPaneSheet extends WidgetsFullSheet { + + private static final int PERSONAL_TAB = 0; + private static final int WORK_TAB = 1; + private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry"; + + private LinearLayout mSuggestedWidgetsContainer; + private WidgetsListHeader mSuggestedWidgetsHeader; + private LinearLayout mRightPane; + + private FrameLayout mRightPaneScrollView; + private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder; + + private final ViewOutlineProvider mViewOutlineProviderRightPane = new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect( + 0, + 0, + view.getMeasuredWidth(), + view.getMeasuredHeight() - getResources().getDimensionPixelSize( + R.dimen.widget_list_horizontal_margin_large_screen), + view.getResources().getDimensionPixelSize( + R.dimen.widget_list_top_bottom_corner_radius) + ); + } + }; + + public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public WidgetsTwoPaneSheet(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void setupSheet() { + // Set the header change listener in the adapter + mAdapters.get(AdapterHolder.PRIMARY) + .mWidgetsListAdapter.setHeaderChangeListener(getHeaderChangeListener()); + mAdapters.get(AdapterHolder.WORK) + .mWidgetsListAdapter.setHeaderChangeListener(getHeaderChangeListener()); + mAdapters.get(AdapterHolder.SEARCH) + .mWidgetsListAdapter.setHeaderChangeListener(getHeaderChangeListener()); + + LayoutInflater layoutInflater = LayoutInflater.from(getContext()); + + int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view_large_screen + : R.layout.widgets_full_sheet_recyclerview_large_screen; + layoutInflater.inflate(contentLayoutRes, findViewById(R.id.recycler_view_container), true); + + setupViews(); + + mWidgetsListTableViewHolderBinder = + new WidgetsListTableViewHolderBinder(mActivityContext, layoutInflater, this, this); + mRecommendedWidgetsTable = mContent.findViewById(R.id.recommended_widget_table); + mRecommendedWidgetsTable.setWidgetCellLongClickListener(this); + mRecommendedWidgetsTable.setWidgetCellOnClickListener(this); + mHeaderTitle = mContent.findViewById(R.id.title); + mRightPane = mContent.findViewById(R.id.right_pane); + mRightPane.setOutlineProvider(mViewOutlineProviderRightPane); + mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view); + mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); + + setupSuggestedWidgets(layoutInflater); + onRecommendedWidgetsBound(); + onWidgetsBound(); + setUpEducationViewsIfNeeded(); + + // Set the fast scroller as not visible for two pane layout. + mFastScroller.setVisibility(GONE); + } + + @Override + protected void hideRecommendations() { + super.hideRecommendations(); + mSuggestedWidgetsContainer.setVisibility(GONE); + } + + private void setupSuggestedWidgets(LayoutInflater layoutInflater) { + // Add suggested widgets. + mSuggestedWidgetsContainer = mSearchScrollView.findViewById(R.id.suggestions_header); + + // Inflate the suggestions header. + mSuggestedWidgetsHeader = (WidgetsListHeader) layoutInflater.inflate( + R.layout.widgets_list_row_header_two_pane, + mSuggestedWidgetsContainer, + false); + mSuggestedWidgetsHeader.setExpanded(true); + + PackageItemInfo packageItemInfo = new PackageItemInfo( + /* packageName= */ SUGGESTIONS_PACKAGE_NAME, + Process.myUserHandle()) { + @Override + public boolean usingLowResIcon() { + return false; + } + }; + packageItemInfo.title = getContext().getString(R.string.suggested_widgets_header_title); + WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create( + packageItemInfo, + getContext().getString(R.string.suggested_widgets_header_title), + mActivityContext.getPopupDataProvider().getRecommendedWidgets()) + .withWidgetListShown(); + + mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry); + mSuggestedWidgetsHeader.setIcon( + getContext().getDrawable(R.drawable.widget_suggestions_icon)); + mSuggestedWidgetsHeader.setOnClickListener(view -> { + mSuggestedWidgetsHeader.setExpanded(true); + resetExpandedHeaders(); + mRightPane.removeAllViews(); + mRightPane.addView(mRecommendedWidgetsTable); + }); + mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader); + } + + @Override + protected float getMaxTableHeight(float noWidgetsViewHeight) { + return Float.MAX_VALUE; + } + + @Override + public void onActivePageChanged(int currentActivePage) { + // if the current active page changes to personal or work we set suggestions + // to be the selected widget + if (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB) { + mSuggestedWidgetsHeader.callOnClick(); + } + + super.onActivePageChanged(currentActivePage); + } + + @Override + protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) { + // The first item is always an empty space entry. Look for any more items. + boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries(); + + mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE); + + super.updateRecyclerViewVisibility(adapterHolder); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mSuggestedWidgetsContainer.setVisibility(VISIBLE); + } + + @Override + public void onSearchResults(List entries) { + super.onSearchResults(entries); + mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.selectFirstHeaderEntry(); + } + + @Override + protected boolean shouldScroll(MotionEvent ev) { + return getPopupContainer().isEventOverView(mRightPaneScrollView, ev) + ? mRightPaneScrollView.canScrollVertically(-1) + : super.shouldScroll(ev); + } + + @Override + protected void setViewVisibilityBasedOnSearch(boolean isInSearchMode) { + if (isInSearchMode) { + mSuggestedWidgetsContainer.setVisibility(GONE); + } else { + mSuggestedWidgetsContainer.setVisibility(VISIBLE); + mSuggestedWidgetsHeader.callOnClick(); + } + super.setViewVisibilityBasedOnSearch(isInSearchMode); + } + + @Override + protected View getContentView() { + return mRightPane; + } + + private HeaderChangeListener getHeaderChangeListener() { + return new HeaderChangeListener() { + @Override + public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) { + WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider() + .getSelectedAppWidgets(selectedHeader); + + if (contentEntry == null || mRightPane == null) { + return; + } + + if (mSuggestedWidgetsHeader != null) { + mSuggestedWidgetsHeader.setExpanded(false); + } + WidgetsRowViewHolder widgetsRowViewHolder = + mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane); + mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder, + contentEntry, + ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST, + Collections.EMPTY_LIST); + widgetsRowViewHolder.mDataCallback = data -> { + mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder, + contentEntry, + ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST, + Collections.singletonList(data)); + }; + mRightPane.removeAllViews(); + mRightPane.addView(widgetsRowViewHolder.itemView); + } + }; + } + + @Override + protected boolean isTwoPane() { + return true; + } + + /** + * This is a listener for when the selected header gets changed in the left pane. + */ + public interface HeaderChangeListener { + /** + * Sets the right pane to have the widgets for the currently selected header from + * the left pane. + */ + void onHeaderChanged(@NonNull PackageUserKey selectedHeader); + } +}